Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Writing an Architecture Decision Record (ADR)

Welcome back. Last lesson traced a reservation confirmation from a React button through Laravel to persistent storage. The trace showed where each responsibility sits. It did not preserve why the team chose those boundaries—or what would make a different design preferable.

An architecture decision record (ADR) captures that reasoning while it is still available. In this lesson, you’ll write one for the reservation feature: state the context, compare viable options, make a clear proposal, and record its trade-offs and consequences. Allow about 40 minutes for the selected resources and worked example.


Record the circumstances, not just the choice

Suppose a developer finds a reservation module inside a Laravel application and asks, “Why wasn’t this built as a separate service?” The code can show how the module works, but it cannot reliably answer what constraints the team faced when it made that choice. Perhaps a single team owned the workflow, or perhaps independent deployment was essential at the time. Either fact could change later.

Watch these passages from Architecture Decision Records (ADR) as a LOG that answers “WHY?” by CodeOpinion. Focus on the difference between recording a decision in its original context and treating it as a rule that must hold forever.

Architecture Decision Records (ADR) as a LOG that answers "WHY?"

CodeOpinion explains why a decision log helps future developers assess choices without guessing at their original rationale.

Watch the motivation for preserving context, then the basic record covering decision, status, and consequences. Finish with the historical perspective: an ADR records a choice made under particular conditions, rather than serving as permanently current system documentation.

An ADR is usually short and addresses one significant choice. Its value is not that it proves the choice was perfect. Its value is that another developer can reconstruct the reasoning, check whether the assumptions still hold, and make a better-informed change.

For this lesson, keep four questions in mind:

  1. Context: What problem requires a decision, and what is true about the system now?
  2. Options: Which realistic approaches could solve that same problem?
  3. Trade-offs and decision: What does each approach gain and give up, and why choose one?
  4. Consequences: What work, benefits, limitations, and risks follow if the choice is adopted?

A statement such as “We chose services because they scale” is too thin to answer those questions. Which part must scale independently? Is it a current requirement or a prediction? What new operational work would services introduce?


Give the decision a useful shape

Read the “Template Walkthrough” in The Markdown ADR (MADR) Template Explained and Distilled. MADR provides more sections than every decision needs, but its distinctions are helpful: a decision driver is a criterion for choosing; an option is a possible solution; a consequence is something expected after adopting one.

The Markdown ADR (MADR) Template Explained and Distilled

This MADR template primer explains what belongs in each part of a decision record and how to compare alternatives fairly.

In “Template Walkthrough,” read the section guide, beginning with the paragraph introducing the template’s structure and ending after “More Information,” just before “Minimal Template.” Pay particular attention to “Context and Problem Statement,” “Decision Drivers,” “Considered Options,” “Decision Outcome,” and “Consequences.” Notice its warning that options should address the same problem at the same level of abstraction.

That last point prevents misleading comparisons. “A modular monolith” versus “a message queue” is not a sound pair of options: one describes system organization, while the other describes a communication mechanism. For our decision, the comparable options will be a reservation module in the existing application and a separately deployed reservation service. Both must still deliver the same confirmation behavior.

It also helps to separate trade-offs from consequences. During evaluation, you might say that a separate service could allow independent deployment but requires another operational boundary. If you choose the in-application module, its consequences include a shared deployment and the need to maintain the module boundary as the code grows. The first comparison explains the choice; the second tells future maintainers what they are taking on.


Write an ADR for the reservation feature

The following is a proposed record for a hypothetical application, not a claim that a team has approved or implemented the design. Its context builds on the feature you traced last lesson: React calls a Laravel API, and confirmation must respect a workshop’s approved booking limit.

# ADR-0001: Keep reservation confirmation in the Laravel application

Status: Proposed

## Context and problem statement

The Laravel application currently manages workshop booking limits and
reservation state. A React client requests confirmation through its JSON API.
Confirmation must never approve more reservations than the workshop's limit,
including when requests arrive concurrently.

One team currently maintains this workflow. There is no demonstrated need
to deploy or scale reservation confirmation independently. Should we keep
confirmation in a module of the existing application, or move it to a
separately deployed service?

## Decision drivers

- Preserve the booking-limit invariant under concurrent requests.
- Keep changes to the workshop and reservation workflow understandable.
- Avoid deployment and operational work without a demonstrated benefit.
- Leave room to change ownership or deployment boundaries if needs change.

## Considered options and trade-offs

1. Keep confirmation in a reservation module of the Laravel application.
   This retains one deployment and can keep the related data within one
   database transaction boundary. It also shares deployment and runtime
   resources with the rest of the application; module boundaries will need
   continued attention.

2. Build a separately deployed reservation service that owns both booking
   limits and reservation state. It could be deployed and scaled
   independently, and it could enforce the invariant locally if it owns
   all data required for confirmation. Moving that ownership would require
   migration and a new integration boundary for workshop changes, as well
   as operating another service and datastore.

## Proposed decision

Choose option 1. Keep confirmation in a Laravel reservation module, with
the application use case behind the existing HTTP boundary. The current
ownership and deployment needs do not justify extracting a service.

This decision does not, by itself, prevent overbooking. Implementation
must protect the limit check and status change with an appropriate
transaction and concurrency control.

## Expected consequences

- Good: The feature remains within one deployment and can coordinate
  related database changes without a cross-service workflow.
- Bad: Changes to confirmation share a release and runtime resources with
  the larger application.
- Risk: Direct access to reservation data from unrelated modules could
  erode the intended boundary.
- Follow-up: Review that concurrent confirmation tests protect the
  invariant and that other modules use the reservation module's interface.

Revisit this decision if independent team ownership, deployment frequency,
or measured scaling needs make the shared boundary costly.

Notice what the record does not claim. A modular monolith does not automatically provide concurrency safety; the transaction and control mechanism still have to be designed and tested. Nor does a separate service automatically make the invariant impossible to enforce: option 2 explicitly gives that service ownership of both facts needed for the decision. The trade-off is the cost of moving that ownership and integrating subsequent workshop changes.

The decision is direct, yet its status is honest. “Proposed” means it is available for review, not that agreement has already been reached. The follow-up makes the proposal testable: someone reviewing the implementation can look for the protected write and for unwanted access across module boundaries. Detailed transaction mechanisms come later in the course; an ADR can identify an obligation without pretending to have completed that design.


Put the record where it can be reviewed and found

MADR suggests keeping numbered Markdown records with the project. Read its short “Applying MADR to your project” guidance for a concrete directory and filename convention.

About MADR | MADR

MADR’s project guidance shows how a decision record can live alongside the code whose design it explains.

Under “Applying MADR to your project,” read “Initialization” and the “Manual approach” in “Create a new ADR.” Continue through the setup and naming guidance, ending with the paragraph explaining why decisions sit in their own documentation subfolder. Focus on the docs/decisions location and the numbered, descriptive Markdown filename; the tooling suggestions are optional.

For the example, a discoverable path would be docs/decisions/0001-keep-reservation-confirmation-in-laravel.md. The number identifies the record; the title helps someone find it during a design review or code change. Keeping it in Git lets a reviewer discuss the proposed reasoning in the same change as the affected code, though a decision can also be reviewed before implementation begins.

The provided ADR workflow image shows another important point: writing the file is not the entire decision process. A proposed record can be accepted, rejected with a reason, or revised and reviewed again.

A proposed ADR goes to team review; the team may accept it, reject it with a recorded reason, or request improvements before reviewing it again.

When feedback changes a proposal, revise it while it is still under consideration. Once a decision is accepted, preserve its original context rather than rewriting history to make an old choice look inevitable. If circumstances later warrant a different architecture, write a new ADR and mark the earlier one as superseded. A rejected record can also remain useful: it shows which alternative was examined and why it was not adopted.


What to carry forward

A useful ADR lets a future developer answer, “Why was this reasonable at the time, and does that reasoning still apply?” Write down the specific context, compare options that solve the same problem, make the choice unmistakable, and include both favorable and unfavorable consequences. Be especially careful not to present hoped-for properties—such as concurrency safety or independent scalability—as benefits already achieved.

In the next lesson, you’ll inspect application structure for coupling and cohesion problems. The ADR’s intended reservation boundary will give you something concrete to compare against the dependencies you actually find.

Can't find a good explanation? Sign up and we'll make it for you

Sign up