Skip to content

Recording architecture decisions

Assignment

The new volunteer asks why the app uses Pinia, why the course API goes through a proxy and why the form no longer asks before leaving. The volunteer no longer remembers all the reasons, and Martina Hostettler never knew any of them.

As a board member, I want every important decision about the app to be recorded with its reasons, so that new volunteers understand it and we can change it on purpose later.

The acceptance criteria are:

  1. Every important decision is in the repository as a short text with context, decision and consequences.
  2. A decision that no longer holds stays and points to the new one.
  3. An index lists all decisions with their status.

Learning goals

After this chapter you can

  • explain what architecture decision records are for,
  • record a decision with status, context, decision and consequences,
  • supersede a decision with a new one without deleting it,
  • weigh options and record them,
  • recognize which decisions deserve a record of their own.

Code shows what an app does, but not why it does it that way. For this, Michael Nygard proposed architecture decision records in 2011, ADRs for short. An ADR records a single decision that shapes the structure of a piece of software, qualities like performance or security, its dependencies, its interfaces or how it's built. It runs one or two pages and is written for someone who works on the software later and doesn't know the reasons.

Nygard proposes a few parts. The title is a short noun phrase. The context describes the forces at play in neutral terms. The decision is written in full sentences and in the active voice. The status says whether the decision holds, and the consequences list everything that follows, good, bad and neutral. The running case app records its decisions in this structure.

0003-shared-state-with-pinia.md
# 3. Shared state with Pinia
## Status
Accepted in part 7, written down in part 8.
## Context
The header, the catalog, the guard and the form need the login, the favorites and the requests. `App` provided them with `provide`, and every new page needed some of them again. A module with `ref` would have done, but it shows its changes in no developer tools and is harder to prepare in tests.
## Decision
The app keeps shared state in setup stores of Pinia, one each for the catalog, the login, the favorites and the requests. State that only one component needs stays in that component, and the filter stays in the address.
## Consequences
The Vue DevTools show the stores, and tests start with a fresh Pinia. The app depends on one more package. Whoever takes a store apart needs `storeToRefs`, otherwise the connections get lost.

The context also names what spoke against Pinia, and the consequences name the extra dependency. Those are exactly the sentences that help later, when someone reconsiders the decision.

Every ADR gets the next number, and a number is never used twice. A new decision starts out as proposed and is accepted or rejected. An accepted decision can later become deprecated or be superseded by a new one.

The status of an architecture decision A state diagram. A decision starts as proposed and is accepted or rejected. An accepted decision can later become deprecated or be superseded by a new decision that points to it. No decision is deleted, it keeps its number and stays in the index. Proposed Accepted Rejected Deprecated Superseded by ADR n No decision is deleted, the number stays.

In part 6, the app asked before someone left a half-filled form. In part 7, a draft in sessionStorage replaced that question. The old decision stays and points to the new one.

0002-warning-before-leaving.md
# 2. A warning before leaving a form
## Status
Superseded by [ADR 5](0005-draft-in-session-storage.md).

The new decision names the old one in its status.

0005-draft-in-session-storage.md
# 5. The draft of a request in sessionStorage
## Status
Accepted in part 7, written down in part 8. Supersedes [ADR 2](0002-warning-before-leaving.md).

Whoever asks later why the app stopped asking finds both reasons, the one for the question and the one for replacing it.

If the choice wasn't obvious, a section with the options that were on the table pays off. The template MADR, in version 4.0.0 from 2024, provides "Considered Options" for this and, if you like, pros and cons of each option. The decision then says which option was chosen and why the others weren't.

ADRs belong in the repository, next to the code they explain. Nygard suggested doc/arch/adr-NNN.md, MADR keeps them in docs/decisions, and the running case app in docs/adr with a file README.md that lists every decision with its status. Public administrations work with them as well, the UK government published a framework for ADRs for the public sector in 2025.

An ADR is worth it for anything that's hard to undo or that would surprise a newcomer, such as the framework, how the files are organized, where state lives, the data source, the kind of history in the router or where the app is published. Variable names or the color of a button don't deserve one.

According to the project assignment, your README contains the important decisions as short architecture decisions. The minimum scope asks for global state for a sensible area or a justified decision against it, recorded as an architecture decision. Record a decision as soon as you make it, while you still know the reasons. The README template contains an example.

Work with the decisions of the running case app in the folder case/08-architecture/en/docs/adr of the course repo. Read first, before you write anything.

  1. Read ADR 3. Which sentence of the context would you miss most if it weren't there?
  2. Compare ADR 2 and ADR 5. What's left of ADR 2, and why is it worth keeping?
  3. The login with an HttpOnly cookie from chapter 7.4 has no ADR yet. Write its context in two sentences.
  4. Which of these decisions deserve an ADR? The color of the header, Pinia for state, sorting by name as the default, hash mode for the demo.

The exercises record decisions about the favorites and the search. You'll find the project in the folder exercises/08-architecture/adr/en of the course repo. You write the decisions in docs/adr, template.md shows the structure, and ADR 1 serves as an example. The tests check the structure, the status and the links, not the content.

Terminal window
npm install
npm test

Exercise Getting started

Members lose their favorites as soon as they reload the page, and the association has no server that could store them. So the app keeps them in localStorage. Record this decision as ADR 2 "Favorites in localStorage" with the sections Status, Context, Decision and Consequences, in full sentences, and add it to the index docs/adr/README.md.

Solution
0002-favorites-in-local-storage.md
# 2. Favorites in localStorage
## Status
Superseded by [ADR 3](0003-favorites-on-the-server.md)
## Context
Members mark things as favorites and lose them as soon as they reload the page. The association has no server that could store the favorites.
## Decision
The app stores the numbers of the favorites as JSON in localStorage under the key `lorraine-commons-favorites` and reads them again on start.
## Consequences
The favorites survive a reload and hold in every tab. They are missing on another device, and whoever clears the data of the browser loses them.

In your version the status reads "Accepted". The solution already shows the status after going further.

Exercise Going further

Members want to see their favorites at the counter and on their phone too. The association's API should store them for members who are signed in. Record this as ADR 3 "Favorites on the server", which supersedes ADR 2. ADR 2 stays, its status reads "Superseded by" with a link to ADR 3, and ADR 3 names ADR 2 with a link in its status. Update the index.

Solution
0003-favorites-on-the-server.md
# 3. Favorites on the server
## Status
Accepted, supersedes [ADR 2](0002-favorites-in-local-storage.md)
## Context
Members want to see their favorites at the counter and on their phone, not only in the browser where they marked them. The API of the association knows signed-in members.
## Decision
The API stores the favorites of signed-in members. Without a login the favorites stay in localStorage, and after the login the app sends them to the API once.
## Consequences
Favorites follow a member to every device. The app needs two ways for favorites and a rule for when both hold different lists, and every mark becomes a request to the server.
README.md
# Architecture decisions
| Number | Decision | Status |
|---|---|---|
| 1 | [Record decisions](0001-record-decisions.md) | Accepted |
| 2 | [Favorites in localStorage](0002-favorites-in-local-storage.md) | Superseded by ADR 3 |
| 3 | [Favorites on the server](0003-favorites-on-the-server.md) | Accepted |
| 4 | [The search text without a store](0004-search-text-without-a-store.md) | Proposed |

Exercise Challenge

A new volunteer suggests putting the search text into a store. Record this as ADR 4 with the status "Proposed". Between context and decision there's a section "Options" with at least two options as a list, and the decision says which option you propose and why.

Solution
0004-search-text-without-a-store.md
# 4. The search text without a store
## Status
Proposed
## Context
A new volunteer suggests putting the search text into a store, like the login and the favorites. Only the catalog needs the search text, but members send each other links to searches.
## Options
- A store with Pinia, as for the favorites
- A `ref` in the page of the catalog
- The query in the address, such as `?q=saw`
## Decision
The search text lives in the query of the address. A store gains nothing, because only one page needs the text, and a `ref` would get lost when someone shares a link.
## Consequences
A link shows the same search, and the back button restores it. The page has to read the text from the address and write changes into it after a pause.

A proposal is accepted or rejected only after a discussion. Until then, the ADR records what the team is deciding about.

MistakeWhat helps
An ADR describes what was built, but not why.Name the forces that led to the decision in the context.
An outdated decision gets deleted or overwritten.Keep it, set its status to "Superseded by" with a link, and point to the old one in the new ADR.
The consequences only list advantages.Also name drawbacks and open questions, they help with later decisions.
Every detail gets an ADR.Only record decisions that are hard to undo or would surprise newcomers.
An AI assistant writes an ADR with reasons that never played a role in the project.The context and the reasons come from you. Check every sentence against what actually happened in the project.

Self-check

  1. Which parts does an ADR have according to Nygard?
  2. Why does a superseded decision stay?
  3. When is a section with options worth it?
  4. Name two decisions in your project that deserve an ADR.
Answers
  1. Title, context, decision, status and consequences.
  2. It explains why the app used to be different, and the new decision builds on its reasons. The number stays unique.
  3. When several solutions were possible and the choice wasn't obvious.
  4. For example the framework, the data source, where state lives or where the app is published.

Assignment

The decisions of the app are in docs/adr now, and the new volunteer looks things up there. Shortly afterwards, Martina Hostettler reports an old bug. After a change to the rules, the app accepts requests whose first day has already passed again, although that was fixed once. The next chapter makes sure with tests that a bug like this doesn't come back a third time.

Text and figures are licensed under CC BY-SA 4.0, the code under the MIT License and the course data under CC0 1.0. The figures use the font Kalam under the SIL Open Font License 1.1. The sources are on GitHub.