Key Takeaways
- Simple architectures are easier to communicate, build, deploy, operate, and evolve.
- Architectural simplicity is not easily encapsulated by one type of model or practice. Several practices can be applied in combination to drive simplicity.
- Agile practices stress simplicity.
- Architectural complexity can occur based on many factors such as design ability and focus, technology evolution, and organizational structure.
- Defining simple architectures is difficult and takes a concerted, continuous effort.
Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better. - Edsger W. Dijkstra (1984)
As an architect, it seems I am constantly fighting one battle over and over: driving simplicity. Simplicity is an often-overlooked system quality. Other qualities like performance, scalability, and monitorability are seen as first-class citizens of architectural design. However, designing simple solutions is often a foundation to providing all other system qualities. While it can be fun and exciting to solve challenging and complex problems with equally complex designs, I’ve found simple architectures are the ones that are most efficient and, subsequently, successful over their lifetime. As an industry, we need to focus more on the system quality of architectural simplicity.
Benefits of Architectural Simplicity
There are several key benefits to designing and maintaining a simple architecture. First, simple architectures are easier to communicate. Communication includes both documentation and comprehension. A simple architecture can be documented with a smaller model and fewer drawings/annotations which would lead to improved comprehension by stakeholders. Comprehension is critical for shared understanding, which some define as the architecture (from Martin’s Fowler’s seminal Who Needs an Architect?). A shared understanding is critical to maintaining alignment across teams and team members, and ensuring an efficient implementation.
Second, simple architectures are often easier to implement. They have fewer moving parts, fewer interactions, and fewer opportunities for failure. However, this isn’t always the case. Sometimes it may take more time to implement a simple architecture given the number of try/fail/learn iterations that go into the process of identifying a truly simple design.
Third, simple architectures are easier to deploy and operate. Fewer moving parts aligns to a more straightforward deployment. Then once in production, simple architectures are more easily scaled and monitored. Another great quote from Edsger Dijkstra is "Simplicity is a prerequisite for reliability."
Fourth, simple architectures are easier to modify and evolve. Simplicity is a key principle of Agile practices and a rule for XP development. Simple architectures allow the development team to be more productive since there is less complexity to track and fewer points of impact when making changes.
Ironically, one aspect of defining a simple architecture that isn’t necessarily easier, is the design process itself. Finding the simplest option often takes additional time and effort in addition to experience and expertise in multiple technologies. I’ll touch more on that later.
What is Architectural Simplicity?
So, what is meant by a simple architecture? Unfortunately, there isn’t one simple definition. However, there are number of concepts that can be applied to drive toward architectural simplicity.
Strive for the Simplest Option – I’ve used this generalization to represent the many different arguments for selecting the simplest of many options. This is captured in Occam’s Razor, "Of two otherwise equal solutions, the simplest is usually better", the XP Principle to "Do the simplest thing that could possible work", and a famous maxim often attributed to Albert Einstein, "Everything should be made as simple as possible, but not simpler." To attain simplicity, you must first understand that there are multiple possible designs, then consider selecting the most concise option that satisfies the function and non-functional needs.
Apply YAGNI – You Aint’t Gonna Need It is an often-contentious topic that argues for designing and building for what is needed now, not for what you think you may need in the future. Designing to future requirements often increases your implementation costs without immediate return on investment, increases your cost of carry, and assumes that future requirements are eventually needed (hint: humans are not good at predicting the future). Avoid over-engineering with the expectation that all the work today will pay off in the future. On the flip side of this topic, one must ensure the architecture includes enough runway to provide for the easy integration of near term capabilities. It can be a delicate balance to generate just enough architecture.
Practice Parsimony – Parsimony speaks to an extreme unwillingness to spend money or use resources. Resources can be interpreted in a number of ways here. Don’t provide more capability, use more technology, or spend more time than required to satisfy the defined solution’s needs.
Avoid Premature Optimization – Avoid applying too much abstraction to your design at the beginning. Apply the concept of triangulation, that is, don't start out with interfaces, start with concrete classes then refactor into interfaces as needed. Allow the design to emerge from known implementations. This will result in better interface definition and function names. It also results in a level of necessary complexity that fits the system. Rebecca Parsons of ThoughtWorks commented in a presentation on Evolutionary Architecture that "the best frameworks are extracted/harvested, not built up front." I’ve also heard this advice through Twitter and other channels, "You have no idea how to build an abstraction properly until you've built three different concrete implementations."
A final note about simplicity: it is not equivalent to familiar. The simplest solution may require some technology or technique with which you are not yet accustomed. Simple solutions must still be fit for purpose and solve the problem at hand. Avoid Maslow’s Hammer.
Reasons for Complexity
So, with all of the benefits of simple architecture, and with several known principles and techniques advocating simple solutions, why do we continue to find, and design ourselves, so many complex architectures? In my experience, I’ve found several reasons, as outlined below.
Complexity Sells – Complexity can be used to add more perceived value to a solution, display a designer’s cleverness, prove a highly capable designer’s talent, hide a less-capable designer’s lack of talent, or provide a scapegoat in case of future implementation failure. Another friend of complexity is the corporate budgeting process. Complex designs can require a larger budget and more people, which can support empire-building. Complex solutions are more difficult to build, maintain, and evolve, requiring more people involved in all those aspects of software delivery.
Developing is Fun – Many times solution developers are the ones completing the solution design. Developers like to develop (I hope). Putting forth a complex design can guarantee more time spent developing and learning new technologies. As an added "benefit," there are also usually several interesting bugs to troubleshoot.
Keeping Up With The Joneses – Technology is ever-changing. Many developers are constantly looking for ways to keep up with the latest and greatest technology and trends. One way to do this is to make sure to use as much new technology in an upcoming project as possible. I have seen designs proposed without a full understanding of how the technology truly delivers capabilities required by the problem and without any associated tradeoffs with the new technology. On the flip-side, as mentioned above, simple is not necessarily familiar. Architects need to mix in innovation and strategic technology evolution to ensure your platforms are moving forward with technology and not stuck on old, outdated systems and techniques that become more painful to modify and support as they age. This can be a tough balance to maintain. But including everything that is new and shiny could be a recipe for complexity.
Simple is Hard – Architecting simple solutions is often just more difficult than architecting complex solutions. It requires a better understanding of the problem space and available/applicable technologies and techniques. It requires experience with previous designs to better know what is truly required versus what is over-designing. It requires a concerted effort to relentlessly edit the design to eliminate unneeded complexity. And it requires ongoing effort to fight entropy and maintain that simplicity over the life of the system.
Organizational Structure – I couldn’t really write an architectural simplicity/complexity article without referencing Conway’s Law. Conway’s Law states that "organizations which design systems are constrained to produce designs which copy the communications structures of those organizations." Therefore, complex organizations will result in complex architectures.
Recommendations for Driving Architectural Simplicity
Below are several recommendations for driving architectural simplicity. None of these are silver bullets that can be applied once then forgotten. They are practices and techniques to be applied together and continuously throughout an effort. You’ll notice that many of the recommendations align to agile and evolutionary architecture practices.
Design Up Front – Let’s get this one out of the way first. I am an agilest at heart and believe in the benefits of a well-run agile team and organization. I don’t believe in either big up-front design (BUFD) or no up-front design (NUFD). I do, however, believe that some level of up-front planning and design is critical. This Dwight D. Eisenhower quote really applies here: "Plans are useless, but planning is indispensable." The action of planning your approach/architecture is imperative to setting an initial vision and aligning the delivery team and other stakeholders with that shared understanding. But expect the plan to change.
Design Throughout Delivery – We must be prepared to continuously evaluate the architectural approach throughout the lifecycle and be prepared to make course corrections based on changes in requirements or learnings gleaned from the implementation. Both designing up front and throughout the delivery process exercise the practice of intentional architecture. Intentional architecture occurs when a vision is set and progress is continuously monitored for alignment with the vision. If progress isn’t aligning to the vision then either the architecture is steered back on course or the vision in recalibrated based on the latest available knowledge. The key is that architectural decisions are made deliberately, accounting for the larger vision. The opposite of intentional architecture, accidental architecture, occurs when decisions are made in a vacuum, without consideration for a broader perspective or how those decisions may impact other parts of the architecture. Often the change is made in a silo as to not impact any existing design/functionality, and can become a bolt-on architecture approach. For architectures to be simple, and maintain simplicity over time, they must be intentionally architected.
Ask Questions, Often – I once heard a company’s Chief Architect remark that his primary job was to just ask questions. Why is this decision superior to the alternatives? Did you consider X? What happens if Y occurs? Can the same goal be accomplished more simply? Designers, or even a team of designers, cannot always see all the angles. Challenge assumptions, clarify ambiguities, raise concerns, and consider what-if scenarios. This practice also applies to understanding the problem at hand. Sometimes even the requestor doesn’t understand what they really want or need. They may not know what is possible. They may be translating someone else’s request. They may not understand the technical cost of their demands. In both the technical and non-technical case apply the "sniff test." If something doesn’t feel right then question it.
Understand Decision Tradeoffs – All decisions have tradeoffs. Rarely are technical decisions an obvious choice. Consider multiple options for any decision. Think through the different benefits, challenges, and risks of each. Make sure to consider different types of tradeoffs like implementation time, long term maintenance, and operational aspects. Do the same for non-technical decisions. Confirm the return on investment of specific requirements. Small capabilities may require large technical investments that may not be obvious to the requestor. Sometimes not implementing an edge case feature is the best way to simplify the architecture.
Create Proof of Concepts – Overdesign and complexity can be the result of doubts or a misunderstanding about how the architecture will perform once materialized. This uncertainty can lead designers to supplement their architectures with just-in-case features, increasing complexity. One way to quell those doubts and instill confidence is to try it out early with a proof of concept. Design the proof of concept minimally to verify only the part of the architecture in question to make it efficient to build, verify, and tear down.
Communicate Effectively and Often – Communicating is one of the most important aspects of delivering a successful architecture. Lack of clarity around the functional needs, non-functional requirements, and overall architectural vision can all lead to extra complexity by designing to the wrong goal or over-designing the solution. Clear and frequent communication builds the shared understanding across all the stakeholders which facilitates delivery alignment. Utilize concise documentation. I prefer simple documents with one target context, i.e. component interaction, data flow, security, etc. Simplicity starts here. But don’t depend only on documentation to convey the architecture. Favor face to face communication over communication through documentation only. Shared documentation does not mean shared understanding. Then review the architecture regularly with the delivery team. When development questions arise, I like to take the opportunity to review the relevant architectural views before tackling the question at hand. This helps reinforce the vision, keeps the team on track, and prevents undesirable architecture bloat caused by confusion or misunderstandings.
Embrace Minimum Viable – The minimum viable product is the lean concept of building just enough features to satisfy early customer needs and facilitate a feedback loop that informs future product releases. Apply similar lean practices to architecture and design. Apply aggressive, value-driven prioritization to architectural features, considering the most likely scenarios, not the edge cases. Many times I’ve seen a team laser-focused on designing a solution for a hypothetical issue that would likely not occur. Just because a problem can be solved technically doesn’t mean that it should be.
Make Architecture Everyone’s Responsibility – I’m a strong believer in the role of the architect, but not necessarily the title. While good architectures may benefit from accumulated knowledge and experience of senior technologists, all technologists can think through the pros and cons of certain decisions. Encourage the delivery team to think architecturally by understanding the overall vision, considering multiple design options and assessing the benefits/challenges of each, choosing the best fit, hopefully simplest, option.
Be Continuously Obsessive – While it is difficult to design a simple solution, it becomes increasingly more difficult to maintain that simple solution over time. Keep the simple-first mindset engaged throughout the life of the architecture. Final architectures and designs, after many iterations, proofs of concepts, requirements changes/tweaks/additions, and implementation hurdles, can often stray considerably from initial intentions. This occurs naturally through the process of solving challenging problems, which rarely follows a straight line from problem to solution. As a solution is developed it often needs to be adjusted along the way to solve newly identified gaps and/or roadblocks. This may lead to a final solution that is misaligned to the architectural vision and possibly misaligned to the problem statement. Continually assessing and validating decisions, along with adjusting the overall architecture as needed, can help maintain alignment with the overall vision and result in a simpler solution.
Conclusion
Architectural simplicity is not a panacea and designing a simple architecture does not guarantee success. Nor does designing a complex architecture guarantee failure. I have seen complex architectures be successful (depends on your definition of success) and I’ve seen one-time simple architectures grow into complex and unmaintainable messes. However, driving architectural simplicity will greatly increase your likelihood of success. Simple architectures are easier to communicate, build, deploy, operate, and evolve. Unfortunately, designing simple architectures is hard. There are no silver bullets. Architecture can be as much art as science and there are rarely black and white decisions. Many factors such as requirements (both functional and non-functional), environment, developers’ knowledge, pre-existing capabilities, enterprise skills, cost, and solution context color our decisions. Not to mention personal biases, corporate politics, and past experience (or lack thereof). It is difficult to know if a decision is optimal at the time of making it, or even months after. To counter this unpredictability, we must embrace agile practices and evolutionary architecture. Relentlessly challenge assumptions. Build for current needs and plan for change. Refactor regularly. Pay avid attention to the state of the architecture. Consciously and continuously driving architectural simplicity is key. When you are making significant changes to the architecture a year from now, your future self will thank you.
About the Author
Brandon Bryson is a Senior Consultant at Mastercard, a technology company and payments industry leader, focusing on application architecture, development, and agile delivery. He is a 18 year veteran of the software industry with a passion for technical leadership, architecture and design, agile principles and the confluence of the three. He has worked at Fortune 500 and start-up companies across a number of sectors including defense, travel, supply chain, and financial. He has a Computer Science degree from the University of Missouri.