Have you ever looked at the architecture of your application and wondered, why did I/we do it this way? If you have, then you’ve probably also cursed under your breath for not having documented the reasoning somewhere. I know I have!
As developers we can be pathologically averse to writing documentation because we mistakenly (and selfishly) think, that its duplication of effort.
“I’ve already written code once, now why I do need to repeat the same thing in text?”
Sometimes the comeback has a slight air of arrogance about it:
“My code is self-documenting, I don’t need to write documentation separately!”
All of this is just sheer laziness masquerading as some misplaced sense of superiority or downright selfishness.
You are not the only one on the team and your memory will eventually fail you at the worst possible times. Architectural Decision Records alleviate some of those problems by providing a (semi) formal document of key architectural decisions that you & your team make in the early phases of a project or piece of work.
It also serves as a reference compendium not just for you to refresh your own memory but also for the new members on the team who need to learn about the system architecture in order to contribute effectively and keep that discipline alive.
Writing ADRs/decision logs gives the reader a sense that the team had been deliberate and intentional about doing something in a certain way with good reason, it wasn’t just accidental and haphazard.
One of the other advantages for ADRs is that in order to write it coherently you really have to think through the options properly and cut out what doesn’t make sense! In other words, you learn how to articulate your ideas in a concise manner for written consumption.
The decision doesn’t even have to be perfect or 100% future proof (you will never know today what you will know tomorrow), it just has to be good enough for today’s known problems with everyone aware of the consequences of that decision. It also helps I have found, to have some idea of how we are going to evolve the design should the decision made today were to constrain us tomorrow. Key is to have this written down instead of just being in people’s heads!
Format of ADRs
I personally quite like the simple (yet effective) Michael Nygard format as prescribed on his blog and have been using this in my team for about a year now and it has definitely helped.
Essentially, you want to keep the document brief and to the point. Followed to its prescribed style, you would create a document with architectural option(s) in a “Proposed” state which the team will use as a base to discuss further options. At the end of the discussion, the ADR will either be “Accepted” or it will be “Superseded”. At each stage, you push this document to your code repository (right next to the code that this document pertains to).
This of course could be a bit laborious and could also isolate you as the developer/architect into a corner using only text files to communicate. I’ve found more value in the following approach (of our own invention):
- For architecturally significant* work, we put an investigation/spike story (timeboxed to 2 days max) on our backlog with goals and outcomes defined.
- Any two people will then pick up this investigation work and pair up on it because two heads are better than one.
- During the investigation these “architects” will explore various architectural/design options to solve the problem, write PoCs (Proofs of Concept) where needed, to make ideas a bit more concrete and then share the findings with the team.
- Team will then deep dive into these findings and pick the option that fits the bill best based on domain requirements and any technical constraints (for e.g. our security posture doesn’t support containers as a deployment option so we can’t Dockerise our app so a solution calling for it won’t work, or the domain requirement is to have an audit trail of changes made to the state of the system so the architecture needs to cater for that). This collective brainstorming gives everyone a chance to contribute actively and to challenge assumptions and shape ideas. It is simply indispensable!
Your team could even propose alternatives that the investigators didn’t think of. Its this collaborative evolution of a design that leads to more effective knowledge sharing all the while destroying the ivory towers by encouraging shared ownership of the architecture.
- Once the decision has been made, the “architects” will then log the final decision in the ADR highlighting all the “also ran” options so the reader has perspective. This ADR will then be pushed to the code repository in an “Accepted” state. It should not always be the same person who does this, others need to learn it as well so in my team we make it a part of the story itself, that way whoever picks it up will also write the ADR.
- You can log auxiliary decisions that are not necessarily architecturally significant but its good to log them anyway, in a separate decision log. For e.g.
10-Dec-2019 Don’t use DI for the Calculation Engine in the Shipment Tracking Domain Model
We’ve chosen to forego dependency injecting the ShipmentTrackingApproximator into the ShipmentTracker use case because the dependency is internal to it and only contains stateless computational algorithms of which there is only ever going to be one implementation. This also simplifies the dependency graph of the use case.
You could come up with a variation of your own that works for you and your team.
There is a whole host of architectural document types that you can get into but the point of this is not to get bogged down by the ceremony of it but get started with the simplest possible tool and cultivate a discipline around this.
We don’t even use any command line tools for this, simply copy and paste a template Markdown file and fill it up! Couldn’t be simpler!
*Architecturally significant work/decisions are those that have a high cost of fixing if done the wrong way. For e.g. splitting a simple CRUD application into microservices could be a costly mistake to undo. Or even worse, choosing an outdated/not fit for purpose language for development work. Or coupling ever more to the shared database if you organisation is transitioning to microservices.
Modern architectural styles like Ports and Adapters (which I very much like and have written about) could allow you to reduce this significance to some level by decoupling the volatile components of your architecture from the domain model.
This way, if you realise that the datastore you picked initially is not going to be able to meet the future needs, then you can write a new “adapter” for a different data store and migrate to it incrementally. Regardless, there is going to be time, effort and risk involved so the goal is to minimise that kind of back-tracking too late into the project by investing some time doing just enough up-front design & investigation work, having a fallback in-case things don’t pan out & documenting this analysis in an ADR.