The Art and Science of Architectural Decision-Making with ADRs
A practical guide to Architecture Decision Records (ADRs)
Have you ever joined a project and wondered, "Why on earth did they build it this way?" Many experienced engineers have felt the frustration of not understanding design choices and have no clue why.
Important decisions can get lost in backlogs, buried in emails, or locked in someone's memory. The result? New team members don’t understand past decisions; costly mistakes can happen easily.
Architecture Decision Records (ADRs) are a lightweight documentation practice that prevents this. ADRs create a knowledge base that preserves architectural knowledge by recording the context and reasoning behind essential design decisions.
This post'll demystify ADRs and show how they help software teams retain knowledge and make better decisions. You'll see how a few well-written records of crucial choices can save you time and energy in a project's life.
Today, we focus on this area, covering:
What is an Architecture Decision Record? We will discuss ADRs, their context, and their consequences.
Why document architectural decisions? Here we will cover the benefits of ADRs: preserving knowledge, improving collaboration, supporting architectural evolution, and avoiding costly misunderstandings.
How to start using ADRs. We will describe simple ways to adopt ADRs using Git, wikis, or documentation platforms, and how to structure them in your workflow.
Tools for managing ADRs. Lists command-line tools and platform integrations that help teams create, manage, and visualize ADRs.
How to write a good ADR. Offers clear guidelines and a practical template for writing concise, useful, and future-proof ADRs.
When (and when not) to write an ADR. It helps you decide which decisions deserve ADRs by focusing on architectural significance, high impact, and key trade-offs.
Organizing and storing ADRs. Suggests best practices for naming, structuring, and versioning ADRs so they remain useful over time.
So, let’s dive in.
Securing AI agents and non-human identities (Sponsored)
NHIs surged with the rise of AI agents, microservices, and distributed cloud systems. This ebook gives you a practical roadmap to secure NHIs in your architecture, with Zero Trust principles at the core:
Real-world examples of AI and NHI-specific security threats, illustrated by incidents from Okta, GitHub, and Microsoft + OWASP research
12 security principles with 35 practical steps for risk-informed NHI governance
A vendor landscape and evaluation checklist to help you build out your NHI security infrastructure
1. What is an Architecture Decision Record?
An Architecture Decision Record (ADR) is a short document that captures a single important decision about a software system’s architecture, context, and consequences. In practice, an ADR is often a simple markdown or text file (if stored in code) or an entry in the docs.
The record focuses on one significant architectural decision that typically affects the system’s structure, core behavior, or key qualities. Michael Nygard, who first popularized ADRs in 2011, defines architecturally significant decisions as those that impact "the structure, non-functional characteristics, dependencies, interfaces, or construction techniques" of the system. In other words, ADRs log the big decisions that shape your product, not any information.
Each ADR answers a few basic questions about a decision:
What was decided?
Why was it needed?
What alternatives were considered (up to 3)?
What are the implications?
By writing down these details, we create a historical record (sometimes called a decision log) that anyone can check later to understand our architecture decisions.
What is important is that we don't record decisions that aren't significant (ADRs are not Any Decision Records). ADRs should focus primarily on architectural choices that affect system structure, non-functional characteristics, dependencies, interfaces, or construction techniques. They document the major decisions that shape our system's evolution and are expensive to change later.
An example of an Architecture Decision Record structure is given in the image below:
💡 An architecture decision record (ADR) is a document that captures an essential architectural decision made along with its context and consequences.
2. Why document architectural decisions?
Someone can ask why we write down decisions when code and diagrams exist. It turns out that documenting architectural choices has a lot of benefits for a software team’s knowledge and productivity:
🧠 Knowledge retention. Teams change, people leave, but projects stay. ADRs preserve the original reasoning behind key decisions. Months or years later, anyone can read the ADR and understand our thoughts.
🤝 Better collaboration. Writing ADRs encourages teams to discuss and agree on important decisions. Instead of ad-hoc choices getting lost, an ADR is often proposed, reviewed, and accepted by the team (much like a mini design RFC). The result is a shared understanding.
🔄 Architectural evolution. Over time, requirements change, and some decisions may be revisited. ADRs make architecture evolution more manageable. By reading why a decision was made and what trade-offs were accepted, you can judge if the original context still holds or if it’s time to change course.
🗺️ Having the complete map. We need ADRs as some questions remain unanswered in the code, such as the system's goals, non-functional requirements, architectural decisions, and their arguments.
✍️ My personal story: Let me share a personal story that shows the real value of ADRs.
Our CEO came into my office a few years ago and said: "The client's CTO says we're wasting money using Cosmos DB when Azure SQL would be 60% cheaper. They're threatening to pull their contract. Explain this to me."
What should have been an easy answer turned into a week-long investigation. The decision had been made two years earlier, but the lead architect had left, and documentation was scattered throughout. I interviewed five engineers and searched through docs to find more.
The truth was this: Azure SQL couldn't handle the client's required sub-100ms response times across three continents without complex sharding. Cosmos DB's multi-region write capabilities were important for their global distribution centers. We had calculated that the "cheaper" SQL option would cost an additional $130,000 in development for custom sharding and $80,000 annually in operational overhead.
If we had documented this in an ADR, I could have answered this fast (and kept my reputation in check). Instead, the client questioned our competence, the CEO lost confidence in our processes, and I wasted an entire week trying to understand previous desicion.
This taught me that architectural decisions have real business implications. A missing ADR almost cost us a big client contract.
In essence, ADRs add a lightweight layer of architectural memory to your project. They ensure the lessons and reasoning behind design choices aren’t lost to time. A few hours spent writing a good ADR can save a lot of hours in future discussions and prevent costly mistakes from a lack of context.
“Archtectural drivers are considerations that need to be made for software systems that are architecturally significant” - Joseph Ingeno
3. How to start using ADRs
One great thing about ADRs is that you need nothing special to use them. ADRs are often as simple as text files, so you can introduce them using only Git and a text editor.
Use version control (Git)
A common approach is to keep ADR files in the same repository as your code (or a docs repo). This way, decisions live near the code they describe. For example, you might create a directory called docs/adr/
(or simply architecture/decisions/
) in your project. Whenever you make a significant decision, add a new markdown file there.
Many teams prefer storing ADRs in Git because it automatically tracks when changes were made and by whom. You can even use pull requests to discuss and approve new ADRs, treating them like code changes. A draft PR can represent a "Proposed" ADR in a GitHub workflow until the team accepts it.
To start in Git:
Create an ADR directory, e.g.
docs/adr
in your repo.Draft an ADR file, e.g.
001-use-graphql.md
(we'll talk naming soon).Write the ADR content (see templates below).
Commit it and share with the team for review.
Upon agreement, merge it so it’s part of the repo’s history.
Use a Wiki or Docs Platform
If your team uses an internal wiki, Notion, Confluence, or Google Docs for documentation, ADRs can live there too. The advantage is that these platforms are accessible and easy to edit for non-developers.
You might create one main page for “Architecture Decisions” and a page for each project. This is fine, especially for organizations that rely on such tools. Ensure there's some versioning or clear dating of changes (most wikis have page history; Google Docs and Confluence have version history).
The downside is that linking ADRs to code changes is less direct than if they were in Git, but the benefit is that anyone can read them. Choose this if your stakeholders (e.g., managers, QAs) prefer not to dig into the code repository.
An example of Architecture Decision Records (ADR) in a Confluence page is shown below:
Dedicated ADR Tools
There are also tools specifically for managing ADRs, ranging from simple command-line utilities to more complex solutions.
Command-line and Open-source tools
ADR Tools by Nat Pryce: One of the most widely used CLI tools,
adr-tools
helps you manage ADRs in Markdown format. It provides commands to initialize an ADR log, create new records, and manage their status (including superseding previous decisions). ADRs are stored as numbered Markdown files in a project directory, which makes them easy to track with version control systems.
For example:Create an ADR directory in the root of your project:
adr init doc/architecture/decisions
Create Architecture Decision Record:
adr new Use Messaging Library
adr-cli / adr (It exists for Go, Node.js, Python, C#, Java, PHP, Rust, Powershell): There are multiple rewrites and ports of the original
adr-tools
in different programming languages, such asadr-cli
(C#),adr
(Go, Node.js),adr-tools-python
,ADR-py
(Python),phpadr
(PHP),adrs
(Rust), and PowerShell modules.adr-viewer: A Python application that generates a browsable website from a set of ADRs, making it easier to visualize via a web interface.
MADR-tools. An extended template that clearly shows decision drivers, options considered, and pros/cons in a structured way. The MADR project provides complete and minimal templates and tooling support.
Log4brains is a tool for logging Architecture Decision Records (ADR) from your IDE and publishing them automatically as a static website.
.NET Ecosystem
dotnet-adr: A .NET global tool for creating and managing ADRs, supporting multiple templates (like MADR, Alexandrian, Business Case) and customizable workflows. It allows teams to use or share their templates via NuGet packages, supporting public and private feeds.
Tools embedded in code and architecture management
Embedded ADRs in code: Some tools and approaches allow ADRs to be embedded directly in the codebase via annotations or comments, which ensures decisions are close to the relevant implementation.
docToolchain implements a docs-as-code approach to software architecture, automating documentation processes and integrating ADRs with other docs.
Structurizr: While primarily focused on C4 model visualization, Structurizr can be used alongside ADRs to connect architectural diagrams with decision records.
So, how to choose the right tool:
For most software teams, starting with a CLI tool like
adr-tools
ordotnet-adr
is simple and effective, especially when paired with Git for version control.If you want to connect ADRs with code or other documentation, consider tools like docToolchain, Structurizr, or embedded approaches.
Templates
There are several great resources and variations of templates to explore:
Decision record template by Michael Nygard (original one)
➡️ Check the complete list of ADR templates here.
Whatever tool you choose, success with ADRs comes from team buy-in and consistent usage. Talk with your teammates about adopting ADRs, and maybe try them as a pilot on one upcoming decision.
Integrate with your workflow
The key to ADR adoption is making them a natural part of your architectural decision-making process. So, consider these integration points:
During architecture reviews, ask, "Should we document this decision in an ADR?"
Include ADR creation and review in your Definition Of Done (DOD) for architectural work.
To create a connected knowledge network, reference ADRs in design documents, commit messages, and comments.
One practice that makes successful ADR implementations is to connect ADRs to the actual code implementation.
Consider adding references to relevant ADRs in:
📦 Package-level documentation
🧩 Module interfaces or key components
📄 Design documents and engineering specifications
🔍 Pull request templates and code review checklists
These connections ensure ADRs guide implementation and help developers understand the reasons behind chosen decisions.
➡️ Read more about how to include ADRs in your architecture decision-making process:
4. How to write a good ADR
Not all docs are created equal. So, what does a good and functional ADR look like?
Here are some guidelines to make your ADRs useful:
🎯 Focus on ONE decision. Each ADR should cover exactly one architectural decision. Don’t combine multiple choices in one record. If two decisions are related, you can write two ADRs and link them.
✂️ Keep it concise. A good ADR should be readable in a few minutes and contain all essential information. It should be a maximum of 1-2 pages. Remember that an ADR doesn't need to capture every detail of the discussion; we only need the key factors influencing the decision and its outcomes. One helpful technique is to write your ADR and then edit it, asking: "Would a future team member need this information to understand why we made this choice?" If the answer is no, consider removing that content.
🖼️ Provide context: Explain why the decision was needed and what factors were considered. A good ADR reads like the backstory of the decision. In the Context section, describe the situation or problem that led to a decision point, e.g., requirements, constraints, technical or business drivers, and quality attributes.
📜 State the decision clearly: In the Decision section, clearly state what was decided. For example: “We will adopt GraphQL as the API query language for the new service.” Try to avoid anything that sounds unclear to the reader. A tip from Nygard’s original ADR guidelines is to write decisions in the present tense as if telling a story to a future developer (“We will do X”).
➖ Add consequences: Every architecture decision has positive, negative, or neutral consequences. A good ADR doesn’t just show the chosen path; it also indicates the consequences. In the Consequences section, list the outcomes and implications of the decision. Include both the expected benefits (e.g., “simpler scaling of services”) and the drawbacks (e.g., “higher complexity in deployment”).
🔒 Make it immutable (but updatable): Treat it as an immutable historical record once an ADR is written and accepted. You generally shouldn’t change its original content later. If you realize new facts or a better solution after the decision, write a new ADR to supersede the old one rather than altering the past.
In short: give enough context, be concise, be clear about trade-offs, and capture the decision in a way that will still make sense years from now.
5. A simple ADR template
While many templates exist, this simple version, based on Michael Nygard's popular format, provides a good foundation to start:
# Title: [Short, descriptive title]
## Status
[Proposed | Accepted | Deprecated | Superseded by ADR-X]
## Date
YYYY-MM-DD
## Context
[Describe the problem and context. What forces are at play? What constraints exist?]
## Decision
[State the decision clearly and concisely]
## Consequences
### Positive
- [Positive outcome 1]
- [Positive outcome 2]
### Negative
- [Negative consequence 1]
- [Negative consequence 2]
## Alternatives (up to 3)
[Describe other options and why they weren't chosen]
## References
[Optional: Link to relevant documents, patterns, or resources]
The Title and Status are usually just a couple of lines. The Context, Decision, and Consequences can each be a few short paragraphs or bullet lists, as needed.
The goal is for an ADR written with this template to be read in about 5 minutes and give a clear message to the reader: “We faced X, we decided Y, and that means Z.”
How to use this template: When a critical decision happens, open a new ADR document and fill in each section. Don’t worry about all the details, write down the key points under each heading. For example, in Context, you might list the 2-3 factors pushing for a change; in Decision, write the choice and a brief justification; in Consequences, list pros/cons and any follow-up tasks.
If you can’t easily fill in these sections, it might be a sign that the decision hasn’t been thoroughly considered yet, which is a good prompt for more discussion.
Feel free to adjust the template for your team, but be cautious about adding too much complexity (ADR should be lightweight).
Real-world example
To make this concrete, use the template above to walk through an example ADR.
Let’s imagine our team is deciding on an API design approach for a new product feature. We’re considering whether to use traditional REST endpoints or adopt GraphQL.
After the discussion, we reached a decision and recorded it in an ADR:
# Title: ADR 7 - Adopt GraphQL for Product Search API
## Status
Accepted
## Date
2025-03-15
## Context
Our e-commerce platform is adding a new Product Search service that will be consumed by multiple clients (web, mobile, third-party partners). We need an API that can serve different client needs in a flexible way.
In the past, we used REST, but that often meant adding many endpoints or over-fetching data. We have a performance goal of under 2s response time for search queries.
The team is also maintaining multiple API versions for different clients, which is becoming hard to manage. We considered sticking with REST (continuing versioned endpoints) versus trying a more flexible query approach. We also have to factor in team familiarity, where most of us have not used GraphQL in production before.
## Decision
We will implement the new Product Search API using GraphQL as the query language and endpoint. Specifically, we'll create a GraphQL schema for product data (products, categories, filters) and expose a /graphql endpoint for client queries.
We choose GraphQL because it allows clients to request exactly the data they need in one call, which should reduce over-fetching of custom endpoints. This decision means adopting a GraphQL server library (we selected Apollo Server) and writing resolvers that aggregate data from our product catalog.
We will provide a GraphQL query guide to client developers instead of REST API docs. The team decided that the benefits of flexibility outweigh the learning curve in this case.
## Consequences
### Positive: Clients (web, mobile, partners) can retrieve product search results and related data in a single request. The API is future-proofed for new requirements (we can add fields/types without breaking existing queries). Also, this serves as a pilot for GraphQL in our company, potentially making the way for more unified APIs.
### Negative (Trade-offs): The team faces a learning curve with GraphQL and will invest extra time upfront in schema design and creating resolvers. Debugging might be more complex initially. We’ll need to implement caching and rate-limiting carefully, as our current REST-oriented caching strategy (CDN caching on endpoints) won’t directly apply. Monitoring needs to be adjusted to track GraphQL queries.
### Follow-ups: We will schedule a knowledge-sharing session on GraphQL for the team. We’ll also monitor query performance closely after launch. We may need to revisit this decision if response times regress or complexity grows.
If GraphQL proves too costly, an ADR will be written to consider alternatives.
As you can see, the ADR tells a story: what led to the decision, what we decided and why, and what that means going forward. A new team member reading this in six months should quickly understand the reasoning behind using GraphQL (or understand the context if they consider changing it).
The ADR also sets context for future decisions (e.g., how we handle caching might become another ADR).
6. When (and when not) to write an ADR
ADRs are helpful, but you shouldn’t write one for every little thing. So, how do you know when a decision warrants an ADR?
It comes down to significance.
Here are some guidelines for when to write an ADR:
🏗️ Architecturally significant decisions: This is the golden rule - if a decision will impact the architecture’s structure, multiple components, or system qualities, capture it in an ADR. For example, decisions about component interactions, system integration patterns (REST vs. messaging), deployment architecture (cloud vs. on-prem, container orchestration), persistent data storage choice (SQL vs NoSQL), major frameworks or languages, or critical cross-cutting concerns (auth strategy, logging approach) usually need an ADR.
💥 High-impact decisions: If reversing or altering the decision later would be difficult, expensive, or risky, it’s a good candidate for an ADR. For example, choosing a relational database vs. a document store could be hard to undo once data and code depend on it, so it's better to document the rationale.
⚖️ Decisions involving important trade-offs: When a choice isn’t apparent and the team has to weigh pros and cons, that’s precisely the scenario to document it. For instance, using an internal build vs. buying a third-party tool involves trade-offs; writing an ADR ensures you record why one path was favored.
📜 Enduring policy decisions: Some decisions set a policy or pattern for the team’s work (for example, “All new microservices will follow an event-driven architecture using Kafka”). These are worth an ADR because they affect many future design choices and team practices. The ADR acts as a reference for the agreed direction.
We don’t want to include trivial decisions in ADRs, such as naming conventions for variables or decisions with no real alternatives (e.g., you must upgrade some library due to security patches or similar).
ADR is also not a low-level design doc. For example, deciding how to implement a specific algorithm or class structure is usually too granular for an ADR. Those can be captured in code comments, design discussion pages, or omitted entirely.
The diagram below is a helper for deciding when to write an ADR:
A frequent objection to ADRs is that "documentation slows us down." The time investment is modest: typically, it takes 30-60 minutes to write a good ADR.
Consider ADRs as investments in your future velocity, not overhead that slows you down today. The most effective teams recognize that some thoughtful documentation prevents significant pain later.
7. Organizing and storing ADRs
How you store and organize ADRs can make a difference in their usefulness. Here are some best practices for managing ADRs as a long-term resource:
Location
Store ADRs where your team will find and read them. If using source control, a dedicated adr/
or decisions/
folder in the repository is a great choice. By living in the repo, ADRs are always a git grep away for developers.
If using a wiki or docs site, ensure a separate section or index for Architecture Decisions. The key is that nobody should have to hunt through a jungle of folders to discover if an ADR exists for a topic.
Naming
Adopt a consistent naming scheme for ADR files or pages. Many projects use a sequential numbering prefix (e.g., 0001-
, 0002-
in file names) followed by a short slug describing the decision, like 0001-database-choice.md
. Others, like the ADR GitHub project, prefer using a present-tense verb phrase as the file name (e.g., use-postgresql-database.md
, enable-caching.md
) for clarity. Both approaches work.
Numbers can convey chronology and are easy for reference (“See ADR-3”), while descriptive names are more readable. You can even use both. For example, combine them: 001-use-postgresql-database.md
. Also, consider using a clear title inside the ADR document (as we did in the example) if the file name is abbreviated.
Folder structure
If your project is small, all ADRs in one folder are fine. In larger systems or monorepos with many ADRs, you might categorize them by subsystem or domain (e.g., subfolders for “UI,” “DevOps,” and “Backend”). However, resist deep nesting, as it might hide decisions.
A flat list or light categorization usually suffices. Some teams maintain an index file (README.md
in the ADR folder or a wiki index) that lists all ADRs with their titles and status. This can act as a table of contents for quickly scanning what decisions exist.
An example of a README.md file:
# Architecture Decision Log
This log tracks all significant design decisions for **your-project-name**.
<!-- adrlog -- Regenerate with `adr-log -i` (npm install -g adr-log) -->
- [ADR-0001](0001-record-architecture-decisions-with-markdown.md) - Record architecture decisions in Markdown ADRs
- [ADR-0002](0002-adopt-event-driven-architecture.md) - Adopt an event-driven architecture for service communication
- [ADR-0003](0003-select-kafka-as-message-bus.md) - Select Apache Kafka as the message bus
- [ADR-0004](0004-choose-postgresql-as-primary-database.md) - Choose PostgreSQL as the primary transactional database
Versioning
Since ADRs capture decisions at a point in time, treat the collection as an append-only journal. When a decision is changed or reversed, add a new ADR rather than editing or deleting the old one. Mark the old one as “Superseded by ADR X” (and mention it supersedes ADR Y in the new ADR). This way, you maintain a timeline of decisions.
Some teams also use statuses like “Proposed”, “Accepted”, and “Deprecated” at the top of each ADR to indicate its current validity. For example, once an ADR is implemented, mark it Accepted; if later replaced, mark it as Superseded and point to the new one.
This is a helpful practice so readers know which decisions are current.
Team worfklow
It is crucial to introduce ADR writing as a regular part of your development workflow. For instance, when kicking off a new epic or feature that involves an architecture choice, allocate time to discuss and record an ADR.
Some teams hold short “ADR review” meetings, especially in early project stages when many decisions are being made.
It’s also valuable to peer-review ADRs. A quick review by colleagues can catch unclear reasoning or missing consequences, improving the document.
Collaboration is key: ADRs shouldn’t feel like something we must do.
8. Conclusion
Architecture Decision Records bring a practical, lightweight structure to documenting critical architectural decisions. They fill the gap between architecture diagrams and code by capturing the why behind your design choices.
The investment in maintaining ADRs pays dividends when you need to understand why your system works the way it does in the future. The practice creates a valuable architectural legacy that persists even as team members come and go, tools and frameworks rise and fall, and business priorities shift.
Start small with a simple template and a few key decisions. As your team experiences the benefits of clear architectural documentation, you can refine your process and expand your collection of ADRs. The most crucial step is to begin.
Remember: the best time to start documenting your architecture decisions was when you began your project. The second-best time is today.
For further reading, check out the ADR GitHub organization, which aggregates knowledge on ADR practices and how Spotify, GitHub, and AWS use ADRs. Also, check out Jacquie Read's book Communication Patterns.
📦 Bonus: Get my Architecture Decision Record Template in Notion
Need a jump-start? I built the ADR workflow described above as a plug-and-play Notion template. It includes buttons, a checklist, a dashboard, and sample ADRs.
What you get:
Home dashboard with quick-action panel
Pre-filled ADR page (Context → Decision → Consequences)
Multiple ADR templates inside
Quick actions: New, Approve, and Archive buttons
Step-by-step “How to use” guide on 20 pages
➡️ Use this discount code for my readers until the end of the week (20% off): 6921C.
Duplicate, import, and start logging decisions in under five minutes, so the next time someone asks, “Why did we choose X?” you have the answer on one page.
More ways I can help you:
📚 The Ultimate .NET Bundle 2025 – 500+ pages distilled from 30 real projects show you how to own modern C#, ASP.NET Core, patterns, and the whole .NET ecosystem. You also get 200+ interview Q&As, a C# cheat sheet, and bonus guides on middleware and best practices to improve your career and land new .NET roles.
📢 LinkedIn Content Creator Masterclass – I share the system that grew my tech following to 100k+ in 6 months (now 255k+), covering audience targeting, algorithm triggers, and a repeatable writing framework. Leave with a 90-day content plan that turns expertise into daily growth.
📄 Resume Reality Check – Get a CTO-level teardown of your CV and LinkedIn profile. I flag what pops, fix what drags, and show how hiring managers judge you in 30 seconds.
✨ Join My Patreon Community – Unlock every book, template, and future drop (worth $100+), plus early access, behind-the-scenes notes, and priority asks. Your support lets me keep writing deep dives for free.
🤝 1:1 Coaching – Book a focused session to crush your biggest engineering or leadership roadblock. I’ll map next steps, share battle-tested playbooks, and hold you accountable.