What Are Architecture Decision Records (ADR) and What Should You Consider When Making Architectural Decisions? 🗒️ — EN

Architecture Decision Records (ADR) are a method for documenting significant architectural decisions made in software projects, including their context, alternatives, and consequences. This documentation enhances the traceability of decisions and ensures transparent communication among teams. ADRs support the long-term sustainability of projects and help new team members understand past decisions.

Murat Dinç
11 min readOct 1, 2024

Hello,

Many important decisions are made during the software development process. These decisions are related to how we will build the project, which technologies we will choose, and how the architecture will take shape. To ensure that such decisions are not forgotten over time and to serve as a reference for future team members or even ourselves, Architecture Decision Records (ADR) are used.

In my personal opinion, the number of companies that truly implement this approach correctly in today’s conditions can be counted on one hand. However, when applied properly, it’s an approach that can even provide answers to questions about past architectural decisions throughout the entire process.

Imagine you’ve just started working at a company, and you see many different approaches in the project you’re working on. Maybe some of them seem illogical to you, and you start thinking things like, “It must be the previous developer’s work” or “The project started off wrong and just continued this way.” Let’s admit it, we’ve all been there. 😊

But if these decisions had been documented using ADR, you could have had the chance to question their reasoning.

I can almost hear you asking, “Do you use this approach yourself, though?” I’ll answer that question at the end of this article. 😊

Let’s dive in👇🏻

What Are Architecture Decision Records (ADR)?

Architecture Decision Records (ADR) are a systematic method for documenting important architectural decisions made in software projects. As we develop a project, we constantly make various architectural choices and decisions. However, it’s not just about making these decisions — it’s also crucial to record why they were made and which alternatives were considered. This provides long-term benefits to the project.

ADR answers these key questions 👇🏻

  1. What problem are we trying to solve?
  2. What alternatives did we consider?
  3. What solution did we choose and why?
  4. What will be the consequences of this decision?

💡 Why Should We Use ADR?

As a software project progresses, questions like “Why was this architectural decision made?” and “Why were alternative solutions rejected?” become increasingly important. ADR helps reduce uncertainty and complexity within the project. The main benefits of ADR can be summarized as follows:

  • Traceability: Over time, it’s easy to forget why certain decisions were made in software projects. A year later, it might be hard to remember answers to questions like “Why did we choose this microservice architecture?” or “What were the advantages of using this library?” ADR provides clear answers to these questions.
  • Consistency: New developers joining the team might struggle to understand the reasoning behind previous architectural decisions. ADR makes it easier for them to understand the project’s current structure and why it was built that way. This helps maintain consistency as the team grows or the project expands.
  • Future Development: When making changes to the project in the future, ADR allows you to evaluate the impact of previous decisions and determine which ones may need to be updated. When making a new architectural decision, the consequences of past decisions can be considered.
  • Communication: In projects with multiple developers and teams, lack of communication can lead to major issues. ADR organizes communication between teams and ensures that everyone stays on the same page.

🗓️ When Should We Use ADR?

It’s not necessary to create an ADR for every architectural decision. For example, writing an ADR for selecting a UI framework is not always essential. However, it’s highly beneficial to use ADR in the following situations:

  • Critical architectural decisions: If a decision significantly affects the overall structure or performance of the application, it’s good practice to write an ADR for that decision. For example, choosing a database technology or deciding to move to a microservice architecture.
  • Decisions involving technical debt or risk: If a decision provides short-term benefits but risks creating technical debt in the long term, ADR should be used to document the decision and evaluate its consequences in the future.
  • Team decisions: Decisions made within the team that affect all developers should also be documented with ADR. For example, choosing a development methodology (Scrum, Kanban) or defining coding standards.

🔴 Why Is ADR Important?

Projects grow over time, and the team members involved may change. Architectural decisions made early on can become complex or lose their meaning over time. This is where ADR comes into play, systematically documenting the background, alternatives, and final decision of each architectural choice.

Additionally, ADR provides:

  • Traceability: By documenting why decisions were made and which alternatives were considered, it becomes easier to revisit these decisions in the future.
  • Communication: Ensures clarity among team members regarding the decisions made.
  • Consistency: When new team members join the project, it’s easier for them to understand and continue with the significant decisions made throughout the project.
  • Better Decision-Making: Discussing and documenting alternatives leads to better decision-making.

🗒️ Core Content of ADR

Each ADR document should clearly and comprehensively describe all aspects of an architectural decision. A standard ADR document consists of the following sections:

1. Title: A brief summary of the decision made. For example, “Choosing an ORM: Entity Framework vs Dapper.”

2. Context: This explains the situations and context that led to the decision. Here, project requirements, constraints, and existing problems are specified. It is essential to clearly express the need that led to the decision. For example, if database operations are performing poorly and we are looking for an ORM solution, we should explain this here.

3. Decision: This section details which solution was chosen and why. It provides the specifics of the decision. For example, “We chose Dapper ORM because it better meets our performance needs.”

4. Alternatives: This outlines the other options considered during the decision-making process and why they were not chosen. There are always multiple solutions, and their pros and cons should be evaluated. For example, “We didn’t choose Entity Framework because we found it insufficient in terms of performance.”

5. Consequences: This explains the impact of the decision on the project, including both positive and negative outcomes. For example, “Using Dapper may require writing more manual SQL, which could increase maintenance costs in the long run.”

Real-Life ADR Examples

When writing an architectural decision record, we need to document a concrete decision made within the project and the factors that influenced this decision. Let’s look at how this process works in a project where we are working with C# through an example.

Example 1: Choosing an ORM for Database Access

In our project, we need to interact with a database, and at this point, we need to choose an ORM (Object-Relational Mapping). Will we use Entity Framework, or a lighter solution like Dapper?

1. Title:

Choosing an ORM — Entity Framework vs Dapper

2. Context:

In our project, we will frequently interact with the database. At this point, instead of writing raw SQL, we want to use an ORM. An ORM will simplify the interaction between object-oriented programming and the database, reducing the chance of errors. However, the ORM we use must meet the performance requirements of the project.

3. Decision:

We chose Dapper ORM because it offers higher performance and flexibility. Since we do not need the change tracker or the higher level of abstraction provided by Entity Framework, the lighter Dapper is more suitable for us.

4. Consequences:

Pros

  • Being a lighter and faster ORM, it offers performance advantages.
  • It provides more control and flexibility.

Cons

  • It doesn’t offer as strong an abstraction as Entity Framework, so we may need to write manual SQL.
  • Advanced features like change tracking are not available in Dapper.

As seen in this example, documenting our decision-making process will explain to both current and future team members why this decision was made.

Now, let’s approach this with a code example…

Let’s assume we chose Dapper ORM and show how it can be used with a simple example. Consider a simple query to retrieve data from a product table.

C# Dapper Example

using System.Data.SqlClient;
using Dapper;
using System.Collections.Generic;

public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}

public class ProductRepository
{
private string _connectionString = "connstr";

public IEnumerable<Product> GetAllProducts()
{
using (var connection = new SqlConnection(_connectionString))
{
string query = "SELECT Id, Name, Price FROM Products";
return connection.Query<Product>(query);
}
}
}

n the code above, we performed a simple query using Dapper to retrieve all products from the Products table. This is an example of how Dapper allows us to perform database operations quickly and easily.

[
{ "Id": 1, "Name": "Product 1", "Price": 100.00 },
{ "Id": 2, "Name": "Product 2", "Price": 200.00 },
{ "Id": 3, "Name": "Product 3", "Price": 300.00 }
]

Example 2: API Design

When designing an API in a project, you may need to decide whether to use a RESTful API or GraphQL. You can document this decision as an ADR.

Example 3: Logging Strategy

When deciding which framework to use for logging in your project (e.g., Serilog or NLog), you can record this decision with an ADR.

Methods for Documenting ADRs

1. Recording with Version Control (like Git)

ADRs can be documented using version control systems (VCS) that are part of the software development process. In version control systems like Git, each decision can be stored as a text file.

Advantages

  • They are maintained within the project, under version control.
  • Every change is trackable and its history is preserved.
  • It is easy to access and manage, especially for technical teams.

Disadvantages

  • It may be difficult for non-technical stakeholders to access and understand.
  • Team members unfamiliar with Git may require additional learning.

Example: You can create an “adr” folder in the root directory of your project and store each decision as a separate markdown file:

/adr
001-database-choice.md
002-logging-strategy.md

2. Documentation and Wiki-Based Systems

If non-technical stakeholders also need access to these decisions, it can be useful to use systems like Confluence, Google Docs, or a Wiki platform. These platforms allow team members to easily read and update decisions.

Advantages

  • Easier access and use for non-technical team members.
  • Decisions can be searchable, editable, and viewable by everyone.

Disadvantages

  • It does not provide as strong version tracking as version control systems.
  • If documents are not regularly updated, the decisions may become outdated.

3. Recording with Project Management Tools

ADRs can be recorded as cards or tasks using project management tools like Jira or Trello. This allows decisions to be visible as part of the project planning and associated with tasks.

Advantages

  • Decisions are integrated with project tasks.
  • All team members, including both technical and non-technical individuals, can view and track decisions.

Disadvantages

  • It does not provide as strong version tracking as version control systems.
  • The lack of structured document format and regular tracking may lead to confusion.

4. Specialized Tools (e.g., ADR Tools)

Specialized software like ADR Tools can be used to automate this process and make it easier to manage. Additionally, these tools can integrate with version control systems like Git to track changes.

Advantages

  • Automates the creation and tracking of ADRs.
  • Can be integrated with version control systems for change tracking.

Disadvantages

  • Requires learning and managing additional software.
  • The use of these tools may be limited in large-scale projects.

5. Recording Using Comments and Annotations in the Codebase

In some cases, architectural decisions can be documented directly within the code using annotations or comments. This method provides transparency in situations where a decision directly affects how the code functions.

Advantages

  • It’s possible to see how the decision relates to the code directly.
  • Any developer reviewing the code can easily see the rationale and effects of the decision.

Disadvantages

  • Not having a centralized record of decisions can make traceability difficult.
  • Managing all decisions within the code can lead to complexity in large projects.

Let’s make examples of ADR within the code.

Example: Decision on ORM Selection (Comment)

In a project, you can document the decision to use Dapper as an ORM by writing an explanatory comment within the code. You can add a comment that explains why this decision was made and which alternatives were considered.

using System.Data.SqlClient;
using Dapper;
using System.Collections.Generic;

public class ProductRepository
{
private string _connectionString = "connstr";

// Decision: We chose Dapper over Entity Framework for our ORM
// Reason: Dapper provides better performance for our specific use case where we need fast, lightweight data access
// Alternatives: Entity Framework was considered but was too heavy for our needs
// Consequences: Developers will need to manually write SQL queries, which may require more attention to detail

public IEnumerable<Product> GetAllProducts()
{
using (var connection = new SqlConnection(_connectionString))
{
string query = "SELECT Id, Name, Price FROM Products";
return connection.Query<Product>(query);
}
}
}

This comment block contains the details of the decision related to ORM selection. The context of the decision, evaluated alternatives, and consequences are placed directly alongside the code. This way, when a developer reviews the code, they can quickly understand why Dapper was chosen and what this decision means.

Example: Decision on Caching Strategy (Comment)

Another example can be seen when documenting an architectural decision related to a caching strategy. For instance, we can document the decision to use Redis Cache within the code through comments.

public class CacheService
{
private readonly IDistributedCache _cache;

// Decision: Using Redis as our distributed cache system.
// Reason: Redis offers low-latency caching, which is essential for our application's performance requirements.
// Alternatives: In-memory caching (not distributed), Memcached were considered.
// Consequences: Need to manage Redis infrastructure, but performance benefits outweigh this cost.

public CacheService(IDistributedCache cache)
{
_cache = cache;
}

public async Task<string> GetCachedValueAsync(string key)
{
return await _cache.GetStringAsync(key);
}
}

In this example, the decision to use Redis is detailed within the code using a comment. This allows future developers to not only understand the decision itself but also why the alternatives were not suitable and the advantages of using Redis.

Example: Decision on Dependency Injection Structure (Comment)

A comment can be added regarding the decision to manage services using Dependency Injection (DI).

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Decision: Implementing Dependency Injection to manage service lifetimes and dependencies.
// Reason: Promotes loose coupling and better testability in our services.
// Alternatives: Manually managing service instances was considered but would lead to tight coupling.
// Consequences: Developers must be familiar with DI patterns; potential overuse of DI could complicate the architecture.

services.AddTransient<IProductService, ProductService>();
services.AddSingleton<ICacheService, RedisCacheService>();
}
}

This comment explains why the DI structure was used, which alternatives were considered, and the potential consequences of this structure on the project.

As you can see, an architectural decision record is highly beneficial for making a project more sustainable and understandable in the long term. Additionally, the real-world examples we explored with C# demonstrate the practical use of ADRs. When you make an important decision in a project, documenting it in ADR format will provide great ease for both yourself and your team in the future.

As for the question I mentioned at the beginning of the article, “Do you use this yourself?” — I try to implement these standards as much as I can, but there have been very few instances where I’ve used them. The disadvantages I discussed in the article are challenges I also encounter from time to time, but I believe this shouldn’t be an excuse. In particular, I think architects or team leaders managing large teams should adopt this approach to prevent conflicts that may arise within the team.

Sources

If you found the article helpful, you can support by following 🙏

See you in the next article 😊

--

--