Enter Architecture decision records (ADRs)
Inevitably, due to engineers rotating off projects or leaving a company context these decisions go with the decision-makers unless there is diligent handover but expecting a developer to dump the contents of their mind without missing anything is unrealistic. Really what we need is a means of recording decisions in a concise format that captures a decision and the context that surrounds it. ADRs to the rescue!
The idea is simple, but I've found it to be a really effective way of ensuring this sort of information isn't lost. Essentially, each time a decision of architectural significance is made, a supporting ADR is created. This can be at any scope really, it may be as broad as “We've elected to implement a loosely coupled monolith because x, y, z" to something more granular like “we have decided to use this ORM for DB access because...". An ADR is a short immutable document that forms an append-only log that resides in the codebase comprising of the following:
Title - titles should be numbered sequentially and use short noun phrases to describe the decision e.g., 0001 - Cosmos DB for persistence
Status - A status that describes the stage in the ADR's lifecycle. This is the only element of the ADR that should be updated once created (besides the odd typo or whatever). Status' can be whatever works for your project e.g., Pending | Accepted | Rejected | Deprecated | Superseded
Context - The context around the decision, what are the current contributing factors? are there constraints that exist? This doesn't have to be purely technical, if there is business/political context then this adds a lot of value. Essentially this is the opportunity to briefly capture the landscape that has led you to a decision
Decision - What action is being taken? We're using framework X, we are modeling Y in this particular way, along with a brief justification. Perhaps alternatives that have been discounted and why would also be useful here; essentially whatever holistically justifies the decision.
Consequences - Inevitably, there will be tradeoffs with any decision. Here we capture the consequences of the decision, both good and bad.
These files are simple markdown documents and are generally short easily digestible pieces of documentation. By numbering each markdown file with a short declarative file name, it gives a nice structured list of the decisions made to date, which serves as a great summary of how the architecture has evolved over time.
To make this more concrete, below is a great example put together by Calum James on our current project where we had a decision to make around what framework we were going to use to manage client-side state in our React application:
# 9. Recoil for state management
One facet of modern web application development is the use of a central storage mechanism to manage application state; this allows components to be more testable, ensures more maintainable code, and reduces the scope for bugs and programming errors.
In recent years, Redux has been the standard for this feature; however, after React released their Hooks feature and improved how asynchronous functions can be used within components, a team at Meta (the creators of React) created Recoil, which is fast becoming the standard. Not only is this library more modern than React, it results in much simpler code.
Having discussed the pros, cons, and consequences with our Technical Architect, we decided using Recoil was our best option. The alternatives would have been to either use a different state management library, such as Redux or MobX; to use React Context as a state management solution; to create our own state management system; to have stored the data in the browser's local storage; or to have just relied on full-page postbacks, as our no-JS solution provides. None of these alternatives follow best practices for modern application development, apart from the usage of existing state management libraries; however, those existing state management libraries are old-fashioned, not future-proofed, and heavyweight in terms of library size.
The key pros of Recoil that cemented our decision are as follows:
- The library was built for modern React, taking full advantage of React hooks and making working with asynchronous functions a breeze.
- Recoil is still in active development and there are no signs of that stopping.
- It is utilized by Meta themselves in production code that's used by billions of people.
- Recoil was very quick and easy to add to our app, and it would be very easy to strip out if we do decide to replace it.
- For such a simple use case of state management, Redux would have been overkill, due to how much boilerplate and code is needed.
- Recoil is designed for app state management; whereas React Context is designed for sharing data throughout a component tree that could be considered global (e.g., a theme for component styling).
- The size of the library is much more lightweight than Redux and other alternatives, which is very important for fast page load times.
- Using Recoil ensures we future-proof our code, to ensure it's modern going forward and less time will need to be spent on maintainability.
- Recoil is very easy for new developers to understand and get to grips with, due to the APIs being based on built-in React hooks and the documentation being excellent.
- The code will be more testable.
- The code will be clear to read.
Effects on Culture
I've heard anecdotally from architects that perhaps are perceived to be in their ivory towers, that making architectural decisions is one thing, but getting developers to buy into those decisions can sometimes be challenging when historical or just simply the broader context isn't well understood or communicated.
As far as I'm concerned, architecture is just one facet of all software engineers' makeup, each of us with varying levels of experience at different 'zoom levels'. We all need to make architectural decisions on a day-to-day basis, some at the micro and others at a macro level. Of course, projects have their technical leaders, wearing the TA or tech lead hat, and it falls to them to ensure the quality attributes of the application are met, but I really believe that developers at any level of experience should feel that they are qualified to contribute to these sorts of decision.
In my view with the ADR mechanism in place, decisions are there for all to see and contribute to and of course, by being in source control it can be reviewed alongside code changes in PRs and through design review in perhaps a three amigos fashion.
Ultimately, it's my belief that ADRs affect culture positively in the following ways:
- Clear & concisely communicating the journey to the current architecture
- Improved understanding and an appreciation for WHY decisions have been made over time
- Consequently, improved buy-in and ownership of decisions and their implementation.
Hopefully, my ramblings have resonated with you on some level, and thanks for reading. I'm sure ADRs aren't a new concept for some (maybe all) of you, but if they are then I'd strongly encourage you to adopt them, your future teammates or inheritors of your codebase will thank you!