Software architecture has traditionally been associated with big design up front and waterfall-style delivery, where a team would ensure that every last element of the software design was considered before any code was written. In 2001, the "Manifesto for Agile Software Development" suggested that we should value "responding to change over following a plan". Unfortunately, when taken at face value, many teams have misinterpreted this to mean that we shouldn't plan. The net result, and I've seen this first hand at numerous organisations around the world, is that some software development teams have flipped from doing big design up front to doing no design up front.
Big design up front is dumb. Doing no design up front is even dumber.
Both extremes are foolish, and there's a sweet spot somewhere that is relatively easy to discover if you're willing to consider that up front design is not necessarily about creating a perfect end-state. Instead, think about up front design as being about creating a starting point, and setting a direction for the team. This often missed step can add a tremendous amount of value to a team, encouraging them to understand what they are going to build and whether it is going to work.
In order to arrive at a software design, you need to make some design decisions. In discussing the difference between architecture and design, Grady Booch tells us that "architecture represents the significant decisions, where significance is measured by cost of change". In other words, which decisions are expensive to change at a later date?
A good way to think about up front design is to ensure that you've made and understand the trade-offs associated with the "significant decisions". These significant decisions are typically related to technology choices (i.e. programming languages, frameworks, protocols, products, etc) and structure (i.e. decomposition strategies, modularity, functional boundaries, etc).
If you're building a monolithic software system, the choice of programming language is likely to be significant for a number of reasons. Adopting a microservices architecture potentially reduces the significance of which programming language(s) you choose, but introduces other trade-offs that need thinking through, including some that are sociotechnical in nature. Similarly, adopting a hexagonal architecture allows you to decouple your business logic from your technology choices, but again there are trade-offs.
The up front design process should therefore be about understanding the significant decisions that influence the shape of a software system rather than, for example, understanding the length of every column in a database table. In real terms, I'd like teams to understand what they are going to build, how they are going to build it (at a high-level), and whether what they've designed will have a good chance of actually working.
But, how much up front design should teams do? This is one of the questions I'm asked the most, with agile teams often confused about how much up front design they should do, or whether they should be doing up front design at all! My recommended approach can be summarised as follows:
- "Some" up front design before you start coding.
- Continuous evolutionary design as you progress, learn, and deliver features.
"some" is quite a vague amount of up front design, and it's one of those "it depends" type situations where context matters. Some teams can get away with doing very little up front design, others will need to do more. Perhaps a better approach is to think about up front design being an iterative and incremental process too, and asking, "when should we stop doing up front design?" instead. My view is that you should stop doing up front design when:
- You understand the significant architectural drivers (requirements, quality attributes, constraints).
- You understand the context and scope of what you're building.
- You understand the significant design decisions (i.e. technology, modularity, etc).
- You have a way to communicate your technical vision to other people.
- You are confident that your design satisfies the key architectural drivers.
- You have identified, and are comfortable with, the risks associated with building the software.
In terms of a concrete approach, this is what I tend to do with teams:
- Run analysis workshops to understand the scope of what needs to be built, at a high level. If I can confidently draw a System Context diagram, then we understand who's using the software system we're designing, and how it fits into the ecosystem around it.
- Run design workshops, with the goal of creating a Container diagram. Doing this allows us to understand how the software system will be built, and the significant decisions we're making related to technology and modularity. Remember, this is a starting point, and the design could (and will) change as we gain feedback.
- Perform a risk identification exercise (e.g. risk-storming), so that we understand if the approach is feasible, and where the highest priority risks are. With this information, we can tweak the design if needed, or write code (e.g. prototypes, proofs of concept, walking skeletons, etc) to de-risk the implementation.
From a timescale perspective this is "hours/days/weeks", rather than "weeks/months/years". This topic is too large for a blog post, so let me point you to a video of a talk from the YOW! conference last year called "The lost art of software design".
Software architecture isn't about big design up front. Do "some" up front design before you start coding, as a starting point, and be prepared to continuously evolve your design as you progress, learn, and deliver features.