Learnixo
Back to blog
AI Systemsintermediate

Architecture Decision Records — Documenting the Why

Use Architecture Decision Records (ADRs) to document key technical decisions, the context behind them, the options considered, and the consequences — so future engineers understand why the system is built the way it is.

Asma Hafeez KhanMay 16, 20266 min read
Solution ArchitectureADRDocumentationArchitecture.NET
Share:𝕏

Why ADRs Exist

Code tells you what the system does.
Comments (sometimes) tell you how.
Nothing tells you why — unless you write it down.

Without ADRs:
  → New engineer asks: "Why are we using a modular monolith instead of microservices?"
  → Answer: "I don't know, Dave left two years ago."
  → Engineer refactors toward microservices without understanding the constraint that ruled them out.
  → Six months later: same constraints resurface and the work is rolled back.

With ADRs:
  → ADR-004: "We chose a modular monolith because the team has no Kubernetes operational experience
     and the hospital's SLA does not require per-service independent scaling."
  → Engineer reads the ADR, understands the constraint is still valid, proposes a targeted change
     instead of a full rewrite.

ADRs are insurance against institutional amnesia.

ADR Format

MARKDOWN
# ADR-007: Use Schema-per-Module for Data Isolation

**Status:** Accepted

**Date:** 2026-03-14

**Context:**
The Clinical Platform is built as a modular monolith with five bounded contexts:
Patients, Prescriptions, LabResults, Billing, and Notifications.
All modules currently share a single SQL Server database with no schema boundaries.
Developers have begun accidentally writing LINQ queries that JOIN across module tables,
and a recent incident caused prescription data to be corrupted when the Patients
team ran a migration that also modified prescription tables (shared DbContext).

**Decision:**
Each module will own a dedicated SQL schema (patients, prescriptions, lab_results, billing).
Each module will have its own EF Core DbContext mapping only its schema.
Cross-schema JOIN queries in application code are prohibited.
Migrations are managed per module with separate output directories.

**Options Considered:**
1. Separate databases per module  stronger isolation, but adds operational complexity
   (connection strings, backup coordination) the team cannot manage with current tooling.
2. Schema-per-module in one database  enforces logical isolation with shared operations.
   Chosen option.
3. No schema separation  status quo, known to cause accidents. Rejected.

**Consequences:**
+ Accidental cross-module queries fail at compile time (wrong DbContext).
+ Migrations are isolated  Patients team can release without affecting Prescriptions.
+ Cross-module queries require explicit API calls or event subscriptions.
- Cross-schema reporting queries must be done via a dedicated reporting database
  or a read model, not via application-layer joins.
- All existing tables must be migrated to their new schemas (one-time migration cost).

**Review Date:** 2026-09-14 (after 6 months in production)

ADR File Organization

docs/
  architecture/
    decisions/
      ADR-001-modular-monolith-over-microservices.md
      ADR-002-ef-core-as-orm.md
      ADR-003-mediatR-for-cqrs.md
      ADR-004-fluentvalidation-for-input-validation.md
      ADR-005-serilog-with-seq.md
      ADR-006-azure-app-service-deployment.md
      ADR-007-schema-per-module-data-isolation.md
      ADR-008-outbox-pattern-for-module-events.md
    README.md  → index of all ADRs with status
MARKDOWN
<!-- docs/architecture/decisions/README.md -->
# Architecture Decision Records

| ADR  | Title                                  | Status     | Date       |
|------|----------------------------------------|------------|------------|
| 001  | Modular monolith over microservices    | Accepted   | 2026-01-10 |
| 002  | EF Core as primary ORM                 | Accepted   | 2026-01-10 |
| 003  | MediatR for CQRS                       | Accepted   | 2026-01-15 |
| 004  | FluentValidation for input validation  | Accepted   | 2026-01-15 |
| 005  | Serilog with Seq for structured logging| Accepted   | 2026-01-20 |
| 006  | Azure App Service deployment           | Accepted   | 2026-02-01 |
| 007  | Schema-per-module data isolation       | Accepted   | 2026-03-14 |
| 008  | Outbox pattern for module events       | Proposed   | 2026-04-02 |

ADR Statuses

Proposed   → Written, not yet reviewed/accepted by the team
Accepted   → Team decision, being implemented
Deprecated → No longer applies (replaced by a newer ADR)
Superseded → Replaced by ADR-NNN (link to the new ADR)

Use "Superseded by ADR-012" rather than deleting ADR-007.
Deleting history is how institutional amnesia starts.

Bad practice: marking old ADRs as "Rejected" retroactively when the decision was actually implemented.
An ADR documents what WAS decided, not what you wish had been decided.

When to Write an ADR

Write an ADR when:
  → You are choosing between two or more non-trivial options
  → The decision will be hard or expensive to reverse
  → The decision will surprise or confuse a future team member
  → You are deliberately deviating from a team standard or industry default
  → The decision has regulatory or compliance implications

Do NOT write an ADR for:
  → Obvious choices ("we'll use HTTPS")
  → Coding style or formatting (put in .editorconfig or a style guide)
  → Decisions that affect one file and can be reversed in 10 minutes

Examples for a clinical .NET system:
  Write ADR: choosing modular monolith vs microservices
  Write ADR: using soft deletes for all clinical data
  Write ADR: FHIR R4 vs proprietary patient data format
  Write ADR: outbox pattern vs synchronous event publishing
  Skip ADR:  choosing FluentAssertions over built-in assertions in tests
  Skip ADR:  naming convention for local variables

Tooling Options

Option 1: Markdown files in the repo (recommended)
  → ADRs live alongside code in version control
  → PRs can include ADRs alongside feature changes
  → GitHub/GitLab render Markdown natively
  → Use adr-tools CLI: `adr new "Schema per module data isolation"`

Option 2: Confluence or Notion
  → Easier for non-developers to read
  → Harder to link to specific code versions
  → Risk of diverging from the codebase (nobody keeps docs in sync)

Option 3: Comments in code
  → Only appropriate for narrow, local decisions
  → Not suitable for system-level architectural decisions

Recommendation: Markdown files in /docs/architecture/decisions/
  committed to the same repo as the code they describe.

Production issue I've seen: A .NET system I joined had been running for 4 years with no ADRs. The codebase had three different HTTP client patterns: raw HttpClient in some services, IHttpClientFactory in others, and Refit in a third set. Nobody knew which was the "right" one or why. Each pattern had been chosen by a different developer in a different month. When we tried to standardize, we discovered one pattern had been used because of a specific TLS certificate issue with the hospital proxy — removing it broke prod. An ADR written in week 1 of that decision ("we use raw HttpClient here because IHttpClientFactory doesn't support client certificates with this proxy configuration") would have saved 3 days of investigation.


Key Takeaway

An ADR documents Context (why the decision was needed), Decision (what was chosen), Options Considered (what was rejected and why), and Consequences (what improves and what gets harder). Store ADRs as Markdown files in version control alongside the code. Write an ADR for any decision that is hard to reverse, will surprise future engineers, or has compliance implications. Never delete old ADRs — mark them Superseded and link to the replacement.

Developer Documentation Knowledge Check

5 questions · Test what you just learned · Instant explanations

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.