Giving architectural decisions context

    By James Taylor, Technical Architect

    James Taylor

    In a world of Agile, decisions that dictate how software architecture evolves are being made all the time. Furthermore, in an age of micro or even nano services, these decisions may be distributed across autonomous teams looking after one or maybe a handful of services with perhaps some oversight from an architect taking care of the overall 'vision'.

    With engineers onboarding/leaving projects all the time it's easy to lose the context of why these decisions were made and whether those decisions still hold water in the current landscape. Of course, there will likely be architecture diagrams (both physical and logical) with supplementary documentation, perhaps some key design decision records (KDDs) that have been put in front of stakeholders over time, and these hopefully have been diligently maintained. However, even if these assets are kept up to date, they largely provide a point in time snapshot, and generally reside in a place like Confluence or similar, which can soon feel disconnected from the software itself, particularly if the volume of this documentation grows. Inevitably this can lead to large unmaintained documentation that never gets looked at, ultimately adding no value. 

    As a developer onboarding onto a project, understanding the makeup of a codebase is naturally a pre-requisite before I feel confident to affect change in a meaningful way. Naturally, a comprehensive README is something I always look for and this can go a long way in answering all of the obvious questions around getting an application up and running, dependencies, and general codebase orientation. Again, a README is generally aimed at capturing the here and now, rather than how we got here. Understanding what frameworks, databases, and design patterns are in use, or perhaps how a particular part of the domain has been modeled is crucial, but the key question a developer usually has is WHY!? (And who do I quiz for the answer?)

    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

    Date: 2022-03-21

    ## Status


    ## Context

    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.

    ## Decision

    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.

    ## Consequences

    - The code and application will continue to feel modern when JavaScript is enabled in the browser.

    - 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.

    Final Thoughts

    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!    


    Here's a handful of resources to get you started:

    Michael Nygard's original ADR template

    Various templates and further reading