When collaborating on software projects with a group of people for a long time, a lot of knowledge is accumulated by all of the team members. This becomes apparent when new members are onboarded. Once the IDE is installed and the source code is checked out, they need to ingest a lot of knowledge to be able to perform their job. Often, this knowledge is spread across several repositories, like Confluence, wikis, CASE tools or the source code itself. More often, it is not written down at all and only shared on demand by the more experienced team members if you happen to ask the right question.
As a consequence, knowledge deteriorates over time: documentation without proper maintenance becomes stale, while the more subtle details on (design) decisions that are not written down tend to be forgotten. A good example of this is knowledge that is not used on a day-to-day basis or reiterated often enough, such as architectural or design decisions. We probably all have had discussions on why certain things were done the way they are done currently, and not being able to remember the exact details or even any details at all. Writing down such decisions certainly would help a lot. There are a lot of aspects in a software project that need to be documented, but for the remainder of this document we will focus on architectural documentation.
Documenting a software architecture is a laborious task: trying to accumulate all the architectural knowledge in a single document yields a hefty document that is updated only occasionally. As a result, the document has either too many details as it tries to record all the complete evolution of the architecture or not enough detail as it only records the latest insights on the architecture. Note that neither form is incorrect: if you want to get familiar with the current architecture it is sufficient to read only about the current state. However, if you want to know the reasons as to why certain decisions were made, it is the reasoning behind those decisions you are after. Ultimately, you want both: one document that describes the current architecture as-is and one (or more) documents that capture the rationale behind the architectural decisions.
Decision - ADR
One way to document architectural design decisions is to write them in the form of Architectural Decision Records (or ADR). An ADR has the following characteristics:
it is a text document potentially written in a markup language such as Markdown or AsciiDoc;
it captures an architectural decision with its context and consequences;
it is stored in source control, alongside with the rest of the source code.
An ADR describes the rationale behind an architectural decision: why was that decision made and against which trade-offs. It captures the reasoning of that moment in time and, as such, should not be modified once it is written. As work progresses, it is quite natural that architectural decisions may need to be revisited and re-evaluated; any change or new decision should then be captured in a new ADR. This new ADR can supersede one or more existing ones. In that case, the remaining ADRs remain as-is while the new ADR captures the latest decision(s). Over time this will yield a nice log that records all the (important) architectural decisions of your project.
The structure of an ADR is always the same:
you start out by documenting the context of the decision: what was the rationale for making a particular decision? For example, suppose that you want to provide an API that users can use to query one or more of your data sources. The context might be that you cannot predict what queries are performed through your API making it difficult to hard-code them by means of a REST endpoint;
once the context is defined, the actual decision should be documented. To continue our example, due to the required flexibility you decide upon using GraphQL to expose as API to your users;
potentially, a decision has certain consequences. For example, the consequence of using GraphQL in your API might be that you acknowledge the fact that your existing API endpoints need to be refactored to GraphQL (or that you leave your existing API as-is).
Additionally, you can add some metadata to your ADR: like the date on which the decision was made and the current status of the ADR. The latter is useful to communicate whether or not an ADR is superseded.
In our experience, keeping your ADRs close to your sources, for example, in the same repository as your source code, makes it easier for team members to actually write them when architectural decisions are made. Using a simple textual format for ADRs lowers the bar to actually write them even more: the only tool you need is your favourite editor. Many source control systems like, Bitbucket, Gitlab or GitHub, allow you to preview Markdown or AsciiDoc documents directly for easier reading. The effect is that ADRs can become part of the developer’s workflow, just like writing tests, as they can be included in pull-requests to be reviewed as a "complete package" (consequently, the omission of an ADR in a pull-request is also easier to spot).
To make life easier on you, there is a nice CLI tool that you can use to maintain your ADRs. It gives you the ability to create new ADRs or amend existing ADRs with a single command.
If you want to use ADRs for existing projects, you can start with writing down the current state of your architecture and amend them whenever you come across a situation where decisions are not recorded. It requires some discipline to start using ADRs to record all of your decisions and keep recording them, but we found it worth the effort to do so.
There is no excuse anymore to not document those architectural decisions!