Your application has an architecture even if it was accidential.

A typical scenario

Imagine an everyday situation. A team is working on a green field project solving a non trivial problem. The team is experienced, conducting proper testing and documents all public APIs. Time passes and the team structure changes. New developers are coming in, others leaving. A new developer might wonder why API calls were implemented synchronously and extends the software using asynchronous one; since asynchronous communication is the new best practice she is convinced…. Maintaining the software becomes more difficult; different styles require varying testing strategies. A new architect comes in, her job is to get everything back on track, increase performance and make it elastic for cloud workloads. She might shrug and recommend to drop the existing implementation simply because she has no clue about the impact of all those little decisions made by different developers. She recommends implementing a new solution. She needs a new team builds a “better” solution and we start from the beginning.

It’s like building a house and using different doorknobs due to personal preference of the technician in charge.

What is missing, is it all that bad?

The missing piece is a reliable process for introducing decisions affecting the architecture of the application and documenting it in an easy comprehensible form every team member (also future ones) are able to understand.

In other areas like house or car construction such process is existing in one way or another. There could be strict rules how to execute a particular task i.e. to meet safety guidelines. After having so many houses burned down there are now certain rules about different aspects to minimize the negative impact.

For software development such rules do not exist, simply because change in technology is volatile and the challenge to solve is always a new one, otherwise copy and pasting code would be sufficient. A best practice might be overruled by another one in just a very short timeframe.

Since there is no one-way solution every developer can rely on, we need to document along the way. This means writting down how we solved a significant challenge, clearly describing the context and any known drawbacks; tracing the important decisions made.

This allows new architects on the project (but also yourself a year later) to understand why the architecture is as it is and did not just “happen”. It also helps new developers to obtain a better understanding of the code they suppose to maintain.

This process is always a multi factorial evaluation considering

  • temporal - time of the decision
  • resources - what resources are available
  • knowledge about certain type of technology - a team having much experience about a layered architecture might face a steep learning curve building an event sourced service

ADR for the rescue

Michael Nygard was probably the first who coined the term Architecture Decision Records (ADR) back in 2011 and provided a basic structure(documenting-architecture-decision). His blog post covers more details than I do.

Others have built on top and come up with other variants and tooling. However the concept remains the same. I’m going to refer to Michael Nygard’s concept since it is the most pragmatic.

An ADR describes significant decisions about the architure of an application.

Those are decisions that affect the structure, non-functional characteristics, dependencies, interfaces or construction techniques (Michael Nygard)

They are recorded as simple textfile or markdown and stored/versioned in a repository. Similar to tickets ADRs are sequentially numbered.

Every ADR has a set of mandatory sections.

There is a multitude of benefits when using ADRS:

  • Great onboarding experience
  • Help with troubleshooting
  • Alignment about application architecture cross different development teams. Especially powerful for repeating problems.
  • Ownership handover to new architects.
  • If there are multiple engineering solutions for a problem it helps analyzing the trade-offs and visualiz the decision trace(Those usually invollve long meetings nobody remember the details and reasoning a few weeks later)

ADR basic structure

Title

Short descriptive name with sequential number.

Context

Facts about environmental constraints. Those can be technological but also political. It answers the question “Why do I have to make this decision”. It is often helpful to list available options.

Decision

Elaborate on the actual decisions including a justification, draw diagrams,…

Status

Creating an ADR is a process that can involve different parties. A status provides clarification about this. It can be one of Proposed, Accepted, Superseded. You might also add another status for Draft.

Consequences

Analyzes the impact of the decision. Every decision has its trade-offs that need to be mentionied in this section.

Example: Using asynchronous communication within an API might be good in terms of responsiveness, however it also brings in the drawback of a more complex error handling.

This obviously touches the business strategy, not only technical concerns.

Compliance

(non-standard section)

Describe how to ensure this ADR is followed in practice and not only existing as nice writing.

As more ADRs are created it becomes virtually impossible to verify them manually or trust the developers to blindly follow. Hence, this section describes the process howto ensure that the decision is followed in the code. Is it possible to setup a fitness function for this ADR? A fitness function could be implemented through Arch Unit.

Adjust the structure to your needs

ADRs are a pragmatic concept hence you can and should adapt it to your needs. I added already the Compliance section which is not original. The same you can specify sections for Date, Notes or clarify the content by splitting up existing sections. Have an additional section for Alternatives if you deem it useful. Of course, create an ADR addressing those ADR related decisions and prepare a template. The process creating an ADR should be simple and easy to understand also for the purpose of onboarding.

It is useful to link to a wiki knowledgebase from the ADR to elaborate on details for specific topics like terms belonging to ubiquitous language or architecture description. If describing configuration, migration, public interface changes those should be described in the knowledge base. Obviously linking from knowledbe base back to related ADRs is also useful.

Lifecycle

The Lifecycle depends on project and team size. Proposing an ADR could be limited to the Solution Architect or lead position, however such limitation might create a bottleneck. A pragmatic approach is to allow every team member to propose an ADR, which is then confirmed by involved parties. Following up with the draft status should be timeboxed with a date until when it is either accepted or rejected. A simple lifecycle could look like this:

no

yes

1. Proposed
2. Accepted
3. Superseded
4. Rejected

mermaid creating a weird artifact on the left bottom, please ignore.

  1. The initial version with sequential number is drafted by any team member and sent out to affected parties to verify and accept.
  2. ADR becomes effective, all parties have signed off
  3. A new ADR has been created overruling/replacing the previous ADR
  4. The ADR was not accepted. If this status is used it makes sense to also record the reason why the ADR has been rejected and by whom.

It is highly recommended to store the ADRs in a git repository to benefit from versioning. There is tooling available to export a repository to make it accessible through a website, visualizing dependencies(for superseded ADRs) and timeline. ADRs can either be stored in a dedicated repository or within a project doc/architectur-decisions dir. Former makes sense if one project consists of multiple applications that share some global ADRs.

For a dedicated ADR repository I found the following structure to be very useful (taken from the book M. Richards & N. Ford (2020-01-27), Fundamentals of Software Architecture, O’Reilly )

  • application: particular decisions grouped by application
  • enterprise: global design decisions affecting all applications i.e. deployment on openshift cloud
  • integration: communication between services

However most of the tooling available doesn’t support such a structure. Keep in mind the sequential number for an ADR should be unique also cross different folders.

.
├── architecture-decisions
│   ├── application
│   │   ├── common
│   │   ├── inventory
            └──0001-use-orchestration-for-order-workflow
│   │   └── pmc
│   ├── enterprise
│   └── integration

Tools

Don’t let a human do a machines job. There are various tools to manage, store and export ADRs. While there is absolutely nothing wrong with creating ADRs manually, the tools take away the need to consider the sequential counting and ensure a consistent template.

Most common are the bash style ADR Tools. Its a simple tool providing commands to add a new or supersede and existing ADR. One drawback is, that it expect all ADRs to reside in the same folder.

Another tool, based on nodejs is log4brains. It has similar functionalities managing ADRS and expects all ADRs to reside in the same folder. Additionally it allows to generate an interactive html export showing ADRs visualized in a timeline. It can also run as docker.

.log4brains.yml:

project:
  name: Foo Bar # The name that should be displayed in the UI
  tz: Europe/Paris # The timezone that you use for the dates in your ADR files
  adrFolder: ./doc/architecture/decisions # The location of your ADR files
docker run --rm -ti -v $(pwd):/workdir -p 4004:4004 thomvaill/log4brains preview

log4brains-example

Example

This is how an ADR would look like.

 # 2. Use orchestration for order workflow

Date: 2022-07-13

## Status

Accepted

## Context

The order workflow involves various business logic including payment, inventory
and reporting, all running as independent services.
Multiple constraints (available stock, credit card verification, ...) result in a rather complex error handling mechanism.

Possible communication patterns include orchestration and choreography.


## Decision

Order workflow shall be designed as **orchestrated workflow**.

A mediating service is controlling the workflow which also allows to centralise the error handling for different scenarios.

The development team is also more comfortable with orchestration, since none of the developer has any practical experience with choreography.


## Consequences

**Scalability becomes more difficult** due to multiple coordination points, limiting parallelism.
At this moment this seems of no concern.
However it should be re-visited at a later stage.

Orchestrating workflow service **becomes potential single point of failure**.
Additional effort is required to compensate i.e. increase redundancy.



Summary

ADRs are necesary to describe significant decisions about the architecture in the context of time.

ADRs help to understand the application(also for onboarding) and troubleshoot when there are problems.

ADRs are numbered sequentially.

It is a flexible process that can be customized easily to the own project requirements.

There is tooling to manage ADR creation and visualization.

Recommended storage for ADRs is a git repository. Especially for non-developers it is necessary to have an exported easy accessible form of ADRs. Even better a visualization(ADRs are build along a timeline).

Links