Architecture Principles
The principles I come back to when shaping a solution architecture, whatever the technology or domain. Almost every one of them is a trade-off rather than a rule, so the consultant’s answer of “it depends” is nearly always true.
Context Is King
Section titled “Context Is King”No single solution is always the right one, so I don’t pick a solution without first understanding the context. In practice this is about not skipping analysis and going straight to design. Analysis means asking questions: understanding the business context, the constraints, and the goals, and taking the time to see the big picture before committing to anything. (See Shaping the Requirement for how I push past a proposed solution to find the underlying need.)
Part of that is understanding the business itself when it isn’t one you know. When I first moved into a consultancy role at Esuasive, I heard a leading voice in consultancy on a podcast say that whenever he started work in an unfamiliar industry, he would find the canonical book on it, the sector’s “bible”, and read up. I don’t go that far, but I do think understanding the business model matters, even for a technical architect rather than an enterprise or business architect.
Context can also mean the basics: whether the project is greenfield or brownfield, what systems are already in place, what technology preferences exist, and which standards the solution has to comply with. Any of these can rule out a design that would be the right answer somewhere else.
Don’t Be Too Rigid
Section titled “Don’t Be Too Rigid”This applies mainly to design patterns and architecture styles. Picking the right tool for the job matters more than applying one approach everywhere. Vertical slices are a good example: there’s no need for every slice to follow the same internal architecture: a simple slice can stay simple, and a complex one can use whatever structure it needs.
The same goes for standards. Architecture standards have real value, but applied too rigidly they start to hurt. Over-standardising cuts against both agile principles and the idea behind microservices that teams should have autonomy over how they build. Given the choice, I favour a polyglot approach over a fixed enterprise standard.
There are forces that push back the other way, though. Security in particular argues for more standardisation, since every variation is another thing to secure, audit, and keep patched. Observability is another: tracing a problem across services is much harder when each team logs and measures things differently. Governance also earns its place. On integration projects, where several teams had to agree the interfaces between their systems, a reviewed up-front design was genuinely useful. The aim isn’t to avoid governance, but to keep it proportionate to the decision. Where the balance sits depends on the organisation and what it has to protect.
Avoid Paralysis by Analysis
Section titled “Avoid Paralysis by Analysis”Context matters, but analysis can’t go on for ever. Some up-front design is needed: my view is that you take the time to do the analysis, then get something down, a placeholder architecture that’s good enough to start from, and refine it as you learn. The risk is doing too much up front, which goes against agile principles and tends to lock in decisions before the information exists to make them well.
I’ve also worked at the other extreme, where no decision could be made without a formal decision document and a review with every stakeholder, and it took ages. Often the right response would have been to make a decision and move on. Writing decisions down is valuable, and I’d always record the reasoning behind a significant one, but when the document becomes a gate that every decision has to pass through, the process itself becomes the bottleneck.
A few ideas help keep that balance:
- Emergent architecture accepts that the design will evolve as the system is built, rather than being fixed on day one.
- Sprint 0 gives the team room to put the initial design and tooling in place, without pretending it’s feature work.
- A walking skeleton, a thin end-to-end implementation that touches each major component, tests the architecture early. The term comes from Alistair Cockburn; the version I reach for is in Steve Freeman and Nat Pryce’s Growing Object-Oriented Software, Guided by Tests (chapter 4, “Kick-Starting the Test-Driven Cycle”). It does carry the risk described in Early Solutions Become De Facto Solutions.
- The last responsible moment, from Mary and Tom Poppendieck’s Lean Software Development, is a useful test for when to commit: delay a decision until the point where not deciding would close off an important option.
My day-to-day defaults for this are in Ways of Working: time-box spikes, and default to reversible decisions. Where the line sits is still a value judgement, and one that can’t always be backed by metrics or empirical evidence. It’s one reason I keep coming back to the question of whether software development is better thought of as engineering or as craftsmanship: much of good architecture comes down to experienced judgement rather than hard and fast rules.
Review and Re-tack
Section titled “Review and Re-tack”Development rarely proceeds in a straight line. A train runs on a fixed track laid in advance; a sailboat sets a destination and then tacks, adjusting course as the wind changes. I think architecture should work more like the sailboat. Set the general direction, make an educated guess, start down a path, and adjust as you learn, rather than sticking to the same route regardless of what you find. This is the same idea as emergent architecture, and it follows the agile emphasis on short feedback loops.
The exception is a costly decision. If something will be hard to change later, it’s worth stopping to review and do some proper analysis before picking a direction. Even then, the aim is to evaluate quickly and get feedback early, so that if a re-tack is needed, it happens before the cost of changing course has grown.
The Code Is the Model
Section titled “The Code Is the Model”This comes straight from the tenets of Eric Evans’ Domain-Driven Design: the software should be modelled on the business domain and reflect it. A rich domain model, where behaviour lives with the domain objects rather than in separate service classes (the opposite of what Martin Fowler calls an anaemic domain model), and a ubiquitous language shared with the business mean the code itself becomes the most accurate model of the system. It’s the same principle as good abstraction: the code should be understandable as it stands, in the language of the problem domain.
It also shapes how I document an architecture. I have experience of UML, but I don’t use the formality that was proposed for it back in the day. One project I worked on took a heavy model-driven architecture (MDA) approach. My role was on the BizTalk and integration side, so I saw it from a little distance, and I didn’t feel it worked terribly well, although the colleague who did the most with the model loved it. I’d advocate something much lighter: the code is the model, and diagrams exist to support the design rather than to specify it. Simple whiteboard sketches and lightweight notations such as Simon Brown’s C4 model are usually enough.
Spec-driven development with AI might change where the model lives, although in a sense it moves the same idea up a level. If the spec is what you maintain and the code is generated from it, much like a compiler’s output, then the spec becomes the code, and the principle would still hold at a different layer. But it’s close to what MDA promised, and I’m sceptical. I haven’t used spec-driven development, and I wouldn’t reach for it yet. Things do change, though, and AI may make the approach more viable than MDA ever was.
Use the Compiler to Enforce Dependency Rules
Section titled “Use the Compiler to Enforce Dependency Rules”Dependency rules that live only in a diagram or a wiki get broken. I’d rather have the build enforce them. In practice that means separate projects, typically structured on the principles of Clean or Hexagonal Architecture, with clean contracts between them, internal visibility for anything that shouldn’t leak out, and architecture tests on top. The architecture tests are, in effect, fitness functions.
The reason is architecture erosion, a term that goes back to Dewayne Perry and Alexander Wolf’s 1992 paper on the foundations of software architecture. Over time, and over many changes, especially when the focus is on short-term functional requirements, an architecture drifts away from its intended design. This will always happen to some degree, and it’s closely tied to technical debt. Tools can slow it down, and we should use whatever is available, but they won’t stop it on their own. There also needs to be a budget for continuous improvement.
John Ousterhout’s A Philosophy of Software Design makes a similar argument through the difference between tactical and strategic programming. Tactical programming focuses on getting the next feature working; strategic programming treats good design as part of the job and invests a little in it continuously. His advice is to stay strategic, and the compiler and a standing improvement budget are two of the ways I try to do that.
References
Section titled “References”- Alistair Cockburn — originator of the term “walking skeleton”; also Hexagonal Architecture (ports and adapters, 2005).
- Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software (2003).
- Martin Fowler — “Anemic Domain Model” (2003).
- Steve Freeman and Nat Pryce — Growing Object-Oriented Software, Guided by Tests (2009), chapter 4.
- Robert C. Martin — “The Clean Architecture” (2012).
- John Ousterhout — A Philosophy of Software Design (2018).
- Dewayne E. Perry and Alexander L. Wolf — “Foundations for the Study of Software Architecture”, ACM SIGSOFT Software Engineering Notes (1992).
- Mary and Tom Poppendieck — Lean Software Development: An Agile Toolkit (2003).
- Simon Brown — The C4 model.
