With all of the cool modern frameworks, database options and other technologies available to us it’s easier than ever for software solutions to become overly complex. Sometimes this is a reflection of the complicated problems we are solving. However, in many cases it is the result of poor planning or adding functionality without enough consideration for how it will affect the system as a whole. Striving for architectural simplicity from the beginning and throughout the life of a software system can be the difference between a scalable platform that can adapt as requirements change and a mess of legacy code that no one really understands.
Why do things get complicated?
Simplicity takes planning
When it comes to software architecture simple does not mean easy. It takes a generous amount of hard work, time and collaboration to achieve architectural simplicity. A deep understanding of the problem at hand, comprehensive knowledge of the tools and patterns available and a reasonable idea of where the software might go in the future are all necessary requirements to come up with an effective but not overly complex solution. Achieving all these things often necessitates a planning phase that can range from a few days to a few sprints. Some teams do not feel that this level of effort for planning is in keeping with Agile principles. Sometimes the reality of the situation simply does not afford the appropriate amount of time for planning or there isn’t enough buy in from decision makers to allocate that time. In these cases, it is the architecture (and the project itself sooner or later) that suffers.
“I love technology…”
It is very tempting to try to weave in that new framework or library everyone is talking about when architecting a new solution. It looks great on a resume or a slide deck and can often sell more work! We have to evaluate these choices objectively to ensure we do not make a software solution more complex without adding any (or enough) benefit. On the other hand, unnecessary complexity can also be introduced by using the tech stack we are most comfortable with instead of the one that best fits the problem.
Merge now, refactor “later”
We’ve all seen it (and are probably guilty of it). The TODO comment (committed 4 years ago) that acknowledges that although this code is terrible rest assured that it will be cleaned up in a later iteration. There will always be times when that fix or patch or feature just has to go out right now. It is up to architects and developers to stress the importance of a minimum standard of code quality and its positive effect on system performance, supportability, and the bottom line in the long run.
What are the benefits of architectural simplicity?
In most cases a simpler architecture produces software that:
- Is easier to understand, document, deploy, test and debug
- Is more likely to remain well organized and consistent as more and more developers contribute to it
- Has fewer boundaries between different moving parts and fewer opportunities for failure
- Is easier to learn and implement
- Can easily be modified and updated as requirements change and our understanding of the problem space deepens
How can architectural simplicity be achieved?
Avoid over engineering
While it is important to create an architecture that is flexible it is equally important not to try to account for every possible edge case (think YAGNI!) Another good rule of thumb is to seek the most general solution for the specific problem at hand.
Reduce ambiguity
Whenever possible avoid making design decisions based on assumptions or partial knowledge. Spikes, proofs concept, user studies and other tools can all help fill in the gaps of understanding before plowing ahead in the wrong direction.
Communicate, adjust, repeat
In order to end up with the ideal solution we need all the facts and buy in from the appropriate parties. The later it is revealed that a solution is not quite right for a particular use case or stakeholder the more expensive it is to change and the more likely it is to introduce unnecessary complexity. These pitfalls can be avoided by keeping lines of communication open, discussing the implications of design decisions often and generating at least enough documentation to facilitate discussion and record the motivations behind architectural decisions. There will inevitably be trade-offs between this pattern/technology/design choice and the other. Everyone involved in the decision-making process should have a clear understanding of the reasons and the risks.
How can architectural simplicity be maintained?
Refer back to the original goal
Architecture is never done. It is a living entity that needs attention in order to survive. With each new iteration in design or change in requirements we must ask:
- How does this fit with the original design?
- Does this architecture still meet our needs?
- Even if this is something we can do, should we?
Revisit trade-offs and choices
We can only make decisions with the data available at the time. As we move forward it is important to look back at previous design decisions with a fresh eye. Perhaps the reasons for or against a particular choice are no longer valid.
Empower the team
Although some companies have a title called Architect, good architecture is not the responsibility of one person or team. It takes a community to create and maintain an architecture. Everyone involved should feel free (better yet obligated) to ask questions about design decisions, propose changes, and offer his or her unique perspective on how the architecture does or does not address the problem at hand.
Sources: InfoQ, Chris Dzombak, Martin Fowler