One of the common themes i see teams struggle with is their architecture. In the technology world, needs and opinions change over time. Because of this, no architecture is truly future-proof. They inevitably need to be changed. The problem is that for architectural updates, "change" tends to mean "rewrite".
Software rewrites are almost never a good idea. You spends weeks or months writing features that already exist. When it is all said an done, the best case scenario is that no customer knows you did anything. Best case! More realistically is that your customers discover some new and exciting bugs now exist in the system. A month spent delivering no customer value while injecting new bugs. This is an unacceptable scenario.
Instead of a rewrite, what i propose is something i have come to call iterative architecture. This entails viewing your architecture as a set of traits and philosophies. When something needs to change, whether its a move to fat models, migrating to a notification queue or bringing in a new technology, it should be done like everything else: iteratively.
This is a controversial stance among devs. Most argue that architecture should be relatively static and entirely consistent throughout the codebase. And while i agree with those ideals, if i have to choose between rewrites and... temporary inconsistency, i choose the latter.
So how does this work? Having done this for the past few years, i can say it takes a fair amount of discipline to execute it without turning your codebase into some inconsistent mess.
Have clear goals
Make sure that when you are making a change, the entire team understands exactly what the system currently does, what we are changing it to and what are the pros and cons of this change. Without this detailed communication, you risk a great deal of misunderstanding about your architectural changes.
One change at a time
Have only 1 ongoing change at a time. You will be tempted to start coding in changes as you identify them. Don't. The con of the iterative approach is you sacrifice some architectural consistency in the name of architectural progress. This is mitigated by having well documented goals and, more importantly, by only tackling a single architectural change at a time.
Once you have identified the change you would like to enact, you will need to tackle the problem iteratively over time.
- Identify the change (or addition) that you will be making to your architecture
- Modify the architecture when adding a new feature (or updating an existing one). Make the change in an way that is as isolated as possible. The goal is not to rewrite anything else.
- All new features written that rely on this part of the architecture should use the new method.
- When updating existing code that uses the old architectural method, schedule time to update them to use the new code.
- Finally, when the vast majority of the code has been updated to the new trait, a story should be scheduled to move the final vestiges of the old trait to the new one.
Architectural changes enacted iteratively like this can sometimes takes months to complete. This is not a bad thing. The iterative cycle trades some consistency for both progress and quality. By releasing these changes over time, a lot of QA risk is alleviated. Major rewrites suffer the opposite.
Per usual, all of this is about making progress. Our codebases should be fun to work in and we should be able to make changes with confidence that we aren't breaking other things. Having a good architecture is at the foundation of those ideals. But for all their good, architectures are not good at changing. An iterative approach changes that.
I wrote recently about designing based on ideals. In many ways this approach is a logical extension of that. It frees a team to talk openly about the ideal architecture without needing to resort to a nuclear option. That is always a good thing.
tl;dr: Architectures are hard to change. Both time and major rewrites often stand in the way. This iterative method frees your from those constraints.