Architecture Documentation and ADRs
TechLearningPro’s commerce team had a slide deck called “Target Architecture.” It showed twelve microservices, a service mesh, and a bus. The running system was one Spring Boot dep
Architecture Documentation and ADRs
Advanced System Design & Software Architecture · Lesson 10
Lesson Overview
TechLearningPro’s commerce team had a slide deck called “Target Architecture.” It showed twelve microservices, a service mesh, and a bus. The running system was one Spring Boot deployable with four modules, an outbox table, and a single PostgreSQL instance. New hires followed the slides, opened pull requests that added a broker client inside catalog “because the picture has a bus,” and then discovered the decision to stay a modular monolith lived in a meeting that was not recorded. Six months later nobody could say whether the picture was a goal, a rejected option, or an unowned sketch. The code was ahead of the slides in some places and behind them in others. That gap is documentation drift. It is an operational problem, because people debug the system they believe exists.
This lesson specifies a documentation set a staff engineer can review: a small number of C4 views with an audience for each, and architecture decision records that capture one decision, the options, and the consequences. The worked decision is the one the previous lessons prepared: TechLearningPro retail stays a modular monolith, with payment extraction pre-authorized only when a PCI scope finding requires a separate deployable. The record is written so a reviewer can disagree with it without reverse-engineering the repository.
Diagrams that are not in version control will diverge. ADRs that list only the winning option will not survive a staff review. A forty-page architecture document that nobody updates will lose to the code, and the code will not tell you why a rejected microservice split should stay rejected. Requirements and Architecture Drivers and Architectural Thinking and Quality Attributes supply the drivers. Lessons 6 through 9 supply the design this lesson records. Staff Engineer Architecture Decision-Making picks up the social side of the same records. The enterprise track’s companion is Architecture Decision Records.
Learning Objectives
By the end of this lesson, you should be able to:
- Choose the C4 view — context, container, component, or deployment — that answers a specific reader’s question, and refuse a diagram with no audience.
- Write an architecture decision record with status, context, drivers, options, decision, and consequences, including the cost of the option you accepted.
- Record “remain a modular monolith” as a real decision with a revisit trigger, not as the absence of a decision.
- Link an ADR to the modules, schemas, and tests that make it true, including
ApplicationModules.verify()and the outbox table. - Supersede a record without rewriting history, and point readers at the replacement.
- Detect documentation drift: a diagram, a README, and a record that disagree with the repository, and say which one you will fix.
- Run a staff review that rejects records whose only consequence is “improved scalability,” with no driver, no alternative, and no owner.
- Place generated Spring Modulith canvases and Mermaid sources so a pull request diff shows an architecture change.
Prerequisites
- Lessons 6 through 9 in this module: ports, modular monolith, bounded contexts, and the outbox. This lesson documents those decisions; it does not re-teach them.
- Architectural Thinking and Quality Attributes: modifiability, availability, and security scope are the words an ADR should use when they are the actual drivers.
- Requirements and Architecture Drivers: a decision without a driver is a preference.
- Comfort reading a Markdown pull request. The records in this lesson are Markdown files in the repository, not slides.
- Simon Brown’s C4 model at the level of “four zoom levels.” You will draw two of them here with Mermaid.
Detailed Core Concepts
What architecture documentation is for
Architecture documentation answers questions a reader cannot answer quickly from a single class:
- What sits around this system, and who uses it?
- What are the deployable units, data stores, and external systems?
- Inside this deployable, what are the major parts and how do they depend on each other?
- Why is it this way, and what was rejected?
- What would cause us to change our minds?
It does not replace Javadoc, a runbook, or a product spec. A runbook says how to drain the outbox when the publisher is down. An ADR says why the outbox exists and what you refused to do instead. Mixing them produces a document that is too long for a design review and too stale for an incident.
The set this module uses for commerce:
| Artifact | Question it answers | Update when |
|---|---|---|
| System context (C4) | Who and what is outside the commerce system? | A new user or a new external system |
| Container view (C4) | What do we deploy and where does data live? | A new deployable or data store |
| Component view | Modules inside the Boot process, and their allowed edges | A new module or a new allowedDependencies |
| Deployment view | Processes, broker, database, trust zones | A topology or PCI boundary change |
| Glossary | What a word means in one context | The language changes (lesson 8) |
| ADR log | Why a significant decision stands | A decision is proposed, accepted, or superseded |
| Generated module canvas | Does the component view match the packages? | Every pull request, by CI |
If a fact fits in none of these, it is either code or a runbook. If the same fact is hand-copied into all of them, they will drift. Write it once, link it.
C4, in the order a new hire needs
Simon Brown’s C4 model is a zoom sequence, not a notation religion. The levels:
- System context. People and external systems around commerce. One box for the commerce system. No classes, no topics, no “PostgreSQL” unless the audience is already inside.
- Container. Deployable or runnable units and data stores: the Spring Boot process, PostgreSQL, Kafka, the acquirer, the legacy ERP. A container in C4 is not a Docker container, though it might be packaged as one. The word means a separately runnable unit or store.
- Component. The major structural parts inside one container. For the Boot process, these are the modules from lesson 7: catalog, orders, payment, fulfillment, plus the outbox publisher inside orders.
- Code. Class diagrams for an aggregate when a squad is changing invariants. Optional in the long-lived docs. Lesson 8’s aggregate diagram belongs next to the checkout module, not on the context poster.
Add a deployment view when topology matters: one region, one Kafka cluster, PostgreSQL primary, PCI zone empty until payment is extracted. Add a dynamic view (a sequence) for the place-order path and the refund orchestration, linking to lessons 6 and 9 rather than pasting every arrow into the context diagram.
Each diagram states its audience in the first line. Context is for a new engineer and a product manager. Container and deployment are for the on-call and the security reviewer. Component is for the squads who edit allowedDependencies. A diagram with every level at once has no audience and will be wrong in the corner nobody owns.
Mermaid in the repository is enough for this team’s scale. Structurizr is the tool Simon Brown built when the model outgrows a handful of files; adopt it when you have many systems and want a single model that renders several views. Do not adopt it to avoid writing the first context diagram. arc42 is a fuller template (constraints, building blocks, runtime, deployment, risks). Use a slice of it if a regulator asks for a document of record. The day-to-day log is still ADRs plus C4. A blank arc42 copy with every heading and no decisions is drift waiting to happen.
Architecture decision records
Michael Nygard’s 2011 note “Documenting Architecture Decisions” proposed a short record per decision: title, status, context, decision, consequences. The point was a log the team could append to, instead of a master document that had to stay coherent. MADR (Markdown Architectural Decision Records) adds considered options and explicit positive and negative consequences. This lesson uses that expanded shape, because a staff review asks “what else did you consider?” and Nygard’s original sections still fit inside it.
A record that survives review contains:
| Section | What a reviewer looks for |
|---|---|
| Status | Proposed, Accepted, Superseded by ADR-00N, or Rejected. Not “final.” |
| Date and owner | A squad or a named role that still exists. “Architecture team” is not an owner if nobody carries a pager. |
| Drivers | Quality attributes and constraints, tied to a requirement. PCI scope, four squads, 40 orders per second, one on-call rotation. |
| Options | At least two, including the one you did not pick. Real options, not a cartoon. |
| Decision | One sentence a stranger can quote. |
| Consequences | Benefits, costs, and new risks. The costs are mandatory. |
| Revisit trigger | The observable condition that reopens the decision. |
| Confirmation | Which test, diagram, or grant proves the decision is still true. |
Status is a lifecycle. Proposed means review is open and the code should not pretend otherwise. Accepted means the team will follow it until it is superseded. Superseded by ADR-014 stays in the file forever. Deleting an old ADR deletes the reason the current one exists. Rejected options stay inside the accepted record, or as their own short record when the rejection itself was expensive and people will propose it again.
What is significant enough to record:
- A choice that is expensive to reverse (data ownership, deployable boundaries, consistency model).
- A choice several squads must follow (module dependency rules, event naming).
- A choice people will relitigate because a conference talk disagreed (microservices, Kafka everywhere).
What is not an ADR: the sprint’s library bump, the name of a test class, a preference for tabs. Those belong in a commit message. A log full of trivial records trains readers to ignore the log.
Consequences include the bill
An accepted modular monolith has consequences a cheerful record hides if you let it:
- One deployable means one bad catalog leak can take down checkout. Fault isolation is weaker than in the rejected microservice option.
- Cross-module calls are in-process and can still couple latency through locks.
- Payment remains in the commerce artifact, so PCI scope is larger until the revisit trigger fires.
- You operate one pipeline, not twelve, and that is a benefit you also write down.
A record that lists only benefits will be used as a slogan after the next incident and then ignored. Costs make the record trustworthy.
Drift is a defect
Documentation drift is any place a reasonable reader and the running system disagree. Examples from this course’s commerce system:
- The context diagram still shows a separate pricing service. Catalog is a module.
- ADR-003 says events go to Kafka. Shipment creation still uses only
@ApplicationModuleListener, and nobody superseded the ADR when the team postponed the broker. - The README says “do not join across schemas.” A new query joins
catalog.price. - The generated Modulith canvas shows a dependency the hand-drawn component diagram does not.
The repair policy: code and tests are the confirmation; the ADR is the intent; the diagram is a view. If the code changed on purpose, update or supersede the ADR in the same pull request. If the code changed by accident, revert the code. If you cannot tell which, the record was missing a confirmation section. CI helps where the check is mechanical: ApplicationModules.verify(), a search that fails on cross-schema SQL under orders/, a generated canvas committed or published as a build artifact. CI cannot check that the prose still matches. A quarterly thirty-minute read of accepted ADRs, with the owner present, catches the rest. Put that review on a calendar. A documentation initiative without a date becomes the slide deck you started with.
Staff review is a reading protocol
A staff engineer reviewing an ADR is not spell-checking. They read in this order:
- Can I quote the decision in one sentence?
- Are the drivers falsifiable? “Scale” is not. “Peak 40 placements per second on one write database, with catalog read-heavy” is.
- Was a serious alternative considered, with a real downside of the chosen option?
- Does the revisit trigger name a signal, not a mood?
- What in the repository would be wrong if we violated this next week, and is that check automated?
- Who is the owner six months from now?
They reject records that announce a technology (“we will use Kafka”) without a problem, records that cite “industry best practice” as the driver, and records whose consequences section is a list of hopes. They ask the author to link the component diagram and the module test. They do not require a novel. Two pages that contain the table above beat twelve pages of restated lesson text.
Architecture and Design Diagrams
mermaidC4Contexttitle System context for TechLearningPro retail commercePerson(shopper, "Shopper", "Buys courses, books, and vouchers")Person(support, "Support agent", "Starts refunds")System(commerce, "Commerce system", "Modular monolith: catalog, checkout, payment, fulfillment")System_Ext(acquirer, "Card acquirer", "Authorizes payment-method tokens")System_Ext(erp, "Legacy warehouse ERP", "Fixed-width demand and ship status for one node")System_Ext(tax, "Tax vendor", "Country tax codes the finance path conforms to")Rel(shopper, commerce, "Browses and places orders")Rel(support, commerce, "Starts refunds")Rel(commerce, acquirer, "Authorizes with idempotency key")Rel(commerce, erp, "Translates via anti-corruption adapter")Rel(commerce, tax, "Requests tax for a total and country")
mermaidflowchart TBsubgraph zone [Commerce trust zone one deployable]APP[Spring Boot commerce]PUB[Outbox publisher thread]APP --- PUBendPG[(PostgreSQL schemas catalog orders payment fulfillment returns)]K[(Kafka topic orders.order-authorized)]ACQ[Card acquirer]ERP[Legacy ERP]APP --> PGPUB --> PGPUB --> KAPP --> ACQAPP --> ERPK --> FIN[Finance consumer]K --> LOY[Loyalty consumer]
The container view is honest about today: one Boot process, one database with several schemas, Kafka for fan-out after authorization, acquirer and ERP outside. It does not draw the twelve services from the old slide. Fulfillment’s shipment handler may still be in-process or a Kafka consumer; the ADR below says which one is accepted now, and the diagram must match the ADR after the pull request, not the quarter’s ambition.
mermaidflowchart LRADR[ADR Markdown in git] --> PR[Pull request]CODE[Module packages and SQL] --> PRPR --> V[ApplicationModules.verify]PR --> CANVAS[Generated module canvas]CANVAS --> REV[Staff review reads decision plus confirmation]V --> REV
Technical Explanation
File layout that reviews cleanly
textdocs/c4/01-system-context.md02-containers.md03-commerce-components.md04-deployment.mdadr/README.mdADR-0007-modular-monolith.mdADR-0008-outbox-for-order-authorized.mdglossary/checkout.md
docs/adr/README.md is a table: number, title, status, date, superseded-by. It is the only index. Do not maintain a second index in a wiki. The wiki, if you still have one, links here.
Number ADRs monotonically. Do not reuse a number when you supersede. ADR-0007 stays accepted until ADR-0014 says Supersedes ADR-0007 and ADR-0007’s status line changes to Superseded by ADR-0014. Both edits land in one pull request so a reader never sees a successor that points at a record still marked accepted, or the reverse.
Mermaid sources live in those Markdown files so the diff is the diagram. Exporting only a PNG from a whiteboard is how the PNG wins an argument the source cannot reconstruct. A PNG beside the source is fine for readers who will not render Mermaid; the PNG is a build output, regenerated when the source changes.
Confirmation hooks in the repo
ADR-0007’s confirmation section points at:
ModularityTestcallingApplicationModules.of(CommerceApplication.class).verify().package-info.javafiles and theirallowedDependencies.- Schemas
catalog,orders,payment,fulfillment,returns. - The absence of a second deploy pipeline for catalog.
ADR-0008’s confirmation points at:
orders.outboxcreated in the orders migration directory.- A test that rolls back and expects no outbox row.
KafkaTemplatereferenced only from the publisher adapter, enforced by ArchUnit if you want the check to be mechanical.
Spring Modulith’s Documenter (org.springframework.modulith.docs.Documenter) writes module canvases from the same structure verify() uses. Publish that output next to 03-commerce-components.md or compare it in CI. When they disagree, the generated canvas tracks the code. Fix the handwritten component view or fix the packages. Do not weaken verify() to match an outdated drawing.
How a view stays shallow enough
The context diagram names the acquirer and not the acquirer’s URL. The container diagram names Kafka and not the partition count. The partition count belongs in the event lesson’s runbook and in producer configuration, and it changes without an architecture decision unless you are changing the ordering story. ADRs link to configuration keys; they do not paste production passwords, broker hostnames, or a snapshot of application-prod.yml. Hostnames drift every migration. The decision “one cluster, order id as key” does not.
Writing style that reviewers trust
- Present tense for what is accepted now. “Checkout publishes
OrderAuthorizedthrough an outbox.” - The rejected option is described fairly enough that its advocate would recognize it.
- Numbers come from a requirement or a measurement. Invented precision (“99.999% once we split”) is a reason to send the record back.
- Trade-offs are explicit. “We accept a larger PCI scope until the trigger.”
- No roadmap disguised as a decision. A decision says what is true after merge. A trigger says what would make a new record.
Practical Implementation or Design Walkthrough
Problem. Write the decision record for TechLearningPro retail’s deployable boundary, and the two C4 views a staff reviewer will open beside it. The team has four squads, one on-call rotation, a PCI program that has not yet issued a finding, and the module seams from lesson 7.
Assumptions. Markdown in git is the system of record. Mermaid renders in review. The audience for the first review is the four squads plus one staff engineer. The record’s identifier is ADR-0007.
1. Context view, one screen
The context diagram in the previous section is the content of docs/c4/01-system-context.md, with two sentences underneath: shoppers and support use the commerce system; the acquirer, the tax vendor, and one legacy ERP sit outside; payment-method tokens never enter the ERP adapter. If a box does not change a decision or onboard a hire, delete the box.
2. Container view that matches today
docs/c4/02-containers.md uses the container flowchart above. The prose under it states: finance and loyalty consume orders.order-authorized; fulfillment’s shipment creation is named explicitly as in-process or on Kafka, and that sentence is load-bearing. Reservation remains a synchronous in-process call. Those sentences are the ones you change, in the same pull request, if shipment creation moves.
3. The decision record
text# ADR-0007: Keep commerce as a modular monolith- Status: Accepted- Date: 2026-11-06- Owner: Commerce platform squad (accountable), with catalog, orders, payment,and fulfillment as consultees for their edgesDrivers- Four squads commit to commerce. Independent modules are required so theydo not edit one shared service package.- Peak checkout is about 40 placements per second. The write path fits onePostgreSQL primary. Catalog reads are cacheable inside the catalog module.- One on-call rotation. A second deployable is an ongoing operational cost,not a one-time project.- PCI scope: no current finding requires payment to be a separate artifact.Security has said a finding would require payment’s admin surface to leavethe commerce jar.- Shoppers wait for price, stock hold, and authorization. They do not waitfor shipment, finance, or loyalty (ADR-0008).Options considered1. One modular monolith: modules catalog, orders, payment, fulfillment,Spring Modulith verification, schema per module, synchronous calls wherethe shopper waits, Kafka outbox for OrderAuthorized.2. Split into deployables now: one service per module, synchronous HTTP forprice, reserve, and pay, Kafka between them, one database per service.3. Leave the historic package-by-layer layout and postpone boundaries.DecisionAccept option 1. Commerce ships as one Spring Boot deployable. Moduleboundaries are enforced with ApplicationModules.verify() and schemaownership. Payment is extracted into its own deployable only when a PCIfinding requires the smaller artifact, and only along the existingPaymentGateway API and payment schema.Consequences- Positive: one pipeline and one primary data store for the write path.Cross-module calls that must be consistent with a shopper response stayin-process. Squads can own packages and migrations.- Negative: a defect that exhausts the shared heap or the shared poolaffects every module. PCI scope includes catalog’s admin routes untilextraction. We do not get independent scaling of fulfillment.- Risks: a shared kernel that grows into a hidden fifth module; across-schema join that bypasses verify() because it is SQL. Both haveconfirmations below.- Option 2 is not the default next step. Option 2 is a new ADR when atrigger fires.Revisit triggers- A written PCI finding that the commerce artifact is in scope and that apayment-only artifact would be accepted out of scope.- Sustained write-path saturation on the primary that a module-local changecannot fix, demonstrated with capacity numbers.- A second on-call organization that must ship fulfillment or payment onits own release train.Confirmation- ModularityTest verifies ApplicationModules.- allowedDependencies: orders to catalog, payment, fulfillment; paymentempty; fulfillment to orders for the event type only.- Separate migration directories per schema. No foreign keys across schemas.- Component diagram docs/c4/03-commerce-components.md updated in the samechange as any new module.- This record is superseded only by a later ADR that names ADR-0007.
That is the whole record. It is specific enough to review. It names option 3 so “do nothing” is visible. It does not promise a mesh.
4. The review meeting, thirty minutes
The staff engineer asks the payment squad whether an empty allowedDependencies matches reality, asks fulfillment whether the ERP adapter is on the context diagram (it is), and asks who edits the record if the PCI letter arrives on a Friday. The owner is the commerce platform squad, not a steering committee. The payment squad can propose ADR-0014; they do not silently deploy a second service. The outcome of the meeting is Status: Accepted in the same week as the modules, or a list of drivers that are still fiction. A record whose drivers are fiction does not become accepted because the diagram looks finished.
5. Superseding when PCI arrives
When the finding arrives, you do not edit ADR-0007’s decision into its opposite. You add ADR-0014, “Extract the payment deployable,” status Accepted, with Supersedes ADR-0007 for the payment deployable boundary. ADR-0007’s status becomes Superseded in part by ADR-0014; monolith remains for catalog, orders, and fulfillment. If your status vocabulary has no “in part,” write Superseded by ADR-0014 and let ADR-0014 state what remains of 0007. Partial memory is how two accepted records contradict. Pick one convention and use it in the index.
ADR-0014’s drivers quote the finding. Its confirmation is a second pipeline, a datasource that cannot read catalog, and an HttpPaymentGateway adapter. The component diagram loses in-process payment and the container diagram gains a box. Same pull request.
6. What you file beside it
ADR-0008, shorter, records the outbox for OrderAuthorized: dual write rejected, AFTER_COMMIT rejected for this fact, consumer dedup required, shopper copy does not mention tracking numbers. Link lesson 9 rather than pasting it. An ADR that duplicates a lesson will drift from the lesson. The record holds the decision; the lesson holds the teaching.
Expected result. A new engineer clones the repo, reads the context diagram and ADR-0007, and can predict that a pull request adding a pricing-service deployable will be asked for a superseding ADR. A reviewer can point at ModularityTest as the confirmation. The old twelve-service slide is marked historical in the ADR index or removed from the wiki so it cannot outrank docs/.
Enterprise Real-World Scenarios
Scenario 1: The staff review that sends an ADR back
A squad proposes ADR-0011, “Adopt microservices for agility.” Context: two paragraphs of industry trend. Options: microservices, or “the monolith,” described as a big ball of mud. Decision: split all modules this quarter. Consequences: “better scalability and ownership.” No numbers, no PCI driver, no statement about the shared database, no owner, no revisit trigger other than “continuous improvement.”
The staff review rejects it in one pass. The monolith in question is already modular; calling it mud ignores ADR-0007’s confirmations. Scalability is not a problem they measured. Splitting all modules maximizes cutover risk for a driver they did not name. The reviewer asks for a rewritten record that either identifies a real trigger from ADR-0007 or explicitly supersedes 0007 with costs: extra on-call, network timeouts on price and reserve, and a data migration plan. The squad returns with no new driver and withdraws 0011. The rejection is noted in the ADR index as Rejected with a pointer to the discussion, so the next conference talk does not restart the same document from zero. The review succeeded because the template demanded options and costs. A slide would have been applauded and then half-executed.
Scenario 2: Drift between the broker picture and the listener
ADR-0008 said every OrderAuthorized consumer uses Kafka. Fulfillment’s shipment path still used @ApplicationModuleListener because the warehouse squad had not taken the topic yet, and a later edit to the component diagram drew a Kafka arrow anyway. During an incident, on-call restarted the broker to fix a missing shipment. Shipments were never on the broker. The restart was harmless and useless, and it cost forty minutes.
The repair was a documentation fix and a confirmation fix together. ADR-0008 was amended, or superseded by ADR-0009 if the team prefers immutability, to list consumers: finance and loyalty on Kafka, fulfillment in-process until it has a separate deploy. The container diagram was redrawn from that sentence. A line in the incident review said the wrong diagram directed a useless action. The team added a quarterly ADR read and a rule: a diagram change without an ADR status check does not merge. An aspirational arrow is a false runbook. Draw the system that is deployed. Put the aspiration in the revisit trigger.
Scenario 3: A regulator asks for the architecture document
A partner’s security review asks for an architecture document, a data-flow, and a list of decisions affecting cardholder data. The team does not write a new forty-page PDF from memory. They export the context and container views, ADR-0007, ADR-0008, the trust-boundary paragraph from lesson 6 (tokens, no PAN in the commerce logs), and the deployment view showing that the acquirer is the only cardholder-data environment they depend on. arc42 headings are used as the cover sheet: building blocks point at the C4 files, design decisions point at docs/adr/README.md, risks point at the negative consequences already in ADR-0007. The reviewer receives a bundle generated from the repo at a commit hash. Next quarter’s review uses the next hash. A hand-maintained PDF would have repeated the twelve-service fiction. The bundle cannot, because those twelve services are not in docs/c4.
Best Practices
- Keep one system of record in git. Other copies link to it.
- One decision per ADR. Split “monolith boundary” from “outbox” so each can be superseded alone.
- Always record at least one rejected option and the cost of the accepted option.
- Write a revisit trigger that a later reader can test without asking the author.
- Name an owner who can change the record.
- Put the audience in the first line of every diagram.
- Draw the deployed system. Put hopes in triggers, not in arrows.
- Change the diagram and the ADR in the same pull request as the structural code change.
- Index status in
docs/adr/README.md. Never reuse numbers. - Confirm with tests where you can: Modulith
verify(), ArchUnit, migration layout. - Generate component canvases from code so the pretty picture has a rival that is hard to fake.
- Keep secrets, hostnames, and tuning constants out of ADRs.
- When a record is wrong, supersede it. Do not silently edit the decision and leave the date.
- Review accepted records on a calendar. Drift is the default if nobody reads them.
- Prefer a two-page record a squad will write over a template so long that they avoid the log.
Common Mistakes and Anti-Patterns
The target-architecture slide as the only document. It shows a desired end state, has no status, and outranks the code in argument. Replace it with an accepted ADR and a container view of what is deployed. If the end state matters, it is a trigger or a proposed ADR, labeled Proposed.
Decision with no alternative. “We chose Kafka.” Reviewers cannot tell whether the outbox, an in-process listener, or a different broker was considered. The record looks like a purchase order. Add the alternatives you actually had time to consider. If you only had one, say so, and say why the clock was short.
Consequences that are only benefits. Future readers will not trust the log, and incidents will be argued from memory. Write the availability coupling, the PCI scope, and the operational load.
Rewriting history. Editing ADR-0007’s decision text a year later without a new number erases the rationale of the code written in between. Supersede. Keep the original.
Diagrams that include every class. A component view of all forty @Service classes is unread and wrong tomorrow. Stop at modules and the ports between them. Class design lives next to the module.
C4 levels mixed on one poster. Context, topics, package names, and a sequence on a single figure. Nobody updates it. One level per file.
Wiki fork. The wiki page is edited during a war room and becomes the new truth. The repo still has the old ADR. Pick git as canonical and paste a link into the war room, then change git when the decision changes.
Confirmation by intention. “Developers will respect module boundaries.” Without verify(), the ADR is a wish. Name the test.
Boilerplate ADRs for every pull request. The log becomes noise. Use ADRs for expensive, cross-team, or hard-to-reverse choices. A useful test: if reversing it next month is cheap and local, a commit message is enough.
Troubleshooting and Design Review
| Symptom | Likely cause | What to check | Correction |
|---|---|---|---|
| Two current pictures | Slide and docs/c4 both exist | Which one onboarding links | One canonical path; mark the other historical |
| ADR accepted, modules do not match | Record written after a workshop, code never followed | allowedDependencies versus the decision sentence | Change the code or supersede the ADR |
| Incident action based on a missing box | Diagram showed a consumer that is not deployed | Container view versus consumer groups | Draw only what runs; list consumers in the ADR |
| Nobody knows if an ADR is in force | Status not updated, no index | docs/adr/README.md | Index with status; superseded links both ways |
| Review argues about wording for an hour | Drivers are slogans | Driver section | Rewrite drivers as constraints and numbers, then resume |
| Generated canvas differs from the component doc | Hand diagram not updated after a package move | CI diff or side-by-side | Update the hand view in the same PR as the package |
| New service appears without a record | ADR process is optional under deadline | Recent deployables versus ADR titles | Require the record for a new pipeline |
| Regulator bundle is a stale PDF | Export was a one-time copy | Commit hash on the cover | Generate the bundle from docs/ at the reviewed commit |
Staff review checklist, used as the meeting script:
- Decision is one quotable sentence.
- Drivers are specific and still true.
- A serious alternative is present, and the accepted option’s cost is present.
- Revisit trigger can be observed.
- Owner is a real squad.
- Confirmation is a test, a schema, or a diagram path that exists in this pull request.
- Context and container views show the same external systems as the ADR’s prose.
- No secrets, no aspirational boxes.
Interview Questions and Answers
Q1. Why record architecture decisions in ADRs instead of one architecture document?
A single document has to be rewritten whenever one decision changes, so people stop updating it, and the useful history of rejected options disappears into overwritten paragraphs. An ADR log appends one decision at a time. Each file has a status. Superseding points at the old file instead of deleting it, so a reader can see why the monolith was accepted in November and why payment left later. The master document, when you need one for a regulator, is a table of contents over those files and the C4 views. Michael Nygard’s argument was social as much as technical: short records get written. A living forty-page document does not. The failure mode of ADRs is a log nobody indexes; fix that with docs/adr/README.md, not by returning to a single mutable essay.
Q2. What sections does a reviewable ADR contain?
Status, date, owner, drivers, options considered, the decision in one sentence, consequences that include costs and risks, a revisit trigger, and a confirmation that ties the decision to the repo. Nygard’s original set is title, status, context, decision, consequences. MADR makes the options and the positive versus negative consequences explicit. This lesson keeps both: context and drivers answer “why now,” options stop a cartoon alternative, consequences include the bill. A record missing options or costs is not ready for staff review, even if the decision sentence is crisp.
Q3. What should make a staff engineer send an ADR back?
Drivers that are slogans (“agility,” “scale”) with no constraint. A single option, or a second option described in bad faith (“the monolith, which is mud”) when the monolith is already modular. Consequences that list only benefits. No owner. No revisit trigger. No link to a test or diagram that would fail if the decision were violated. A technology choice with no problem statement. Any of those is enough to return the record. The reviewer is not asking for more pages. They are asking for a decision a later on-call can trust. The first scenario in this lesson is the pattern: reject, and keep the rejection visible so the same draft does not return under a new number without new facts.
Q4. Which C4 view do you draw for which reader?
System context for anyone who needs the boundary: new hires, product, a partner security reviewer. One box for commerce, plus people and external systems. Container view for operators and for anyone asking what is deployed: the Boot process, PostgreSQL, Kafka, the acquirer. Component view for the squads: modules and allowed dependencies inside the Boot container. Code-level diagrams only around an aggregate that is hard to see from packages. Deployment view when zones matter, such as PCI. Mixing these on one poster guarantees drift. The vocabulary trap: a C4 container is a runnable unit or store, not necessarily a Docker container. Say “process” in prose if the room will confuse them.
Q5. How do you document the decision to stay a monolith so it does not look like indecision?
You write an accepted ADR whose decision sentence is “Commerce ships as one Spring Boot deployable with enforced modules.” You include the microservice split as an option you rejected, with the drivers you used: team size, on-call, measured load, absence of a PCI finding. You list the costs of staying, including shared failure domain and broader PCI scope. You add revisit triggers that would make a new ADR legitimate. You point confirmation at ApplicationModules.verify() and the schema layout. Indecision is an empty docs/adr folder and a slide titled future state. A conscious boundary is a record with a status. Lesson 7 is the design; ADR-0007 is the commitment.
Q6. How do diagrams stay true after the workshop?
Store the source in git, Mermaid or Structurizr, and review the diff. Generate what you can from code, such as Spring Modulith’s Documenter output, and treat a disagreement with the hand-drawn component view as a defect. Change pictures in the same pull request as allowedDependencies or a new deployable. Draw only what is deployed; put the next extraction in a trigger. Forbid a second canonical copy in a slide tool. A PNG exported once from a whiteboard has no diff and will silently win an incident. The quarterly read is for the prose CI cannot check. The pipeline is for the structure it can.
Q7. What is documentation drift, and how do you repair it?
Drift is a disagreement between a reasonable reader’s sources: ADR versus diagram versus code versus wiki. Repair starts by deciding which source is supposed to be right. If the team changed the code on purpose, supersede or amend the ADR and redraw the view in that pull request. If the code drifted from an accepted decision, fix the code or explicitly accept a new decision. If you cannot tell, the record had no confirmation section, and that is the first fix. Deleting the docs so they cannot be wrong leaves the next hire with only folklore. The incident in scenario 2 — restarting Kafka for an in-process consumer — is the cost of an aspirational arrow.
Q8. When do you supersede an ADR instead of editing it?
Supersede when the decision itself changes: payment leaves the monolith, the outbox is replaced by a different mechanism, a trigger fires. Edit typos and broken links freely. Do not edit the decision paragraph of an accepted record in place; anyone who built code against the old sentence needs to see it. The successor names the predecessor. The predecessor’s status changes to superseded in the same pull request. The index lists both. If only one sentence of a long record changes, you may still supersede, or you may keep an amendments section with dates. Pick one policy in the ADR README so half the log is not rewritten and the other half is immutable.
Q9. How do ADRs relate to C4 and to arc42?
C4 is the picture of structure at several zooms. ADRs are the log of choices that produced that structure. A container diagram does not explain why Kafka is present; ADR-0008 does. An ADR does not onboard a hire who needs to see the acquirer outside the system; the context diagram does. arc42 is a document outline that can point at both: building-block section to C4, design-decisions section to the ADR index, risks to the negative consequences. Filling arc42 by rewriting those sources into a third prose pile creates drift. A cover sheet of links, generated at a commit hash, satisfies a reviewer who asked for “the document” without inventing a new source of truth.
Q10. What does not belong in an ADR?
Secrets, production hostnames, connection strings, and tuning values that change without a new architecture. Step-by-step incident commands; those are runbooks linked from the confirmation section. A restatement of a whole lesson. A product roadmap of features that are not structural. A library upgrade that is cheap to reverse, unless it forces a boundary change. Personal style preferences. If the record is longer than a few pages, it is probably a design document wearing an ADR header, and reviewers will not re-read it when the decision is challenged. Split it or cut it back to drivers, options, decision, and consequences.
Q11. How would you confirm ADR-0007 in a pull request six months later?
Open the record and the container diagram side by side. Check that there is still one commerce deployable. Run or read ModularityTest. Compare allowedDependencies to the decision’s module list. Look for a new pipeline that is not mentioned; if payment has its own chart, ADR-0014 should exist and 0007’s status should already say so. Skim the shared kernel for JDBC. Search for cross-schema SQL on the write path. None of this requires the original author. That is the standard. A confirmation step that is “ask the author” means the record failed, even if the author is still on the team.
Q12. A stakeholder wants the twelve-service picture back on the landing page. What do you do?
You do not put an unaccepted target over the container view. You offer a Proposed ADR if they have a driver that meets the revisit triggers, with costs and a migration slice (payment first, not all twelve). Until that record is accepted, the landing page shows the context and container views of the deployed system and links the ADR index. A picture of a system you do not run trains new hires to write the wrong client. The stakeholder’s goal can be real; the place for it is a proposed decision with a trigger, not a decoration that outranks docs/c4.
Hands-On Exercises
Exercise 1: ADR, modular monolith versus microservices
Problem. Write the decision record for TechLearningPro commerce: modular monolith now, microservice split only under a named trigger. Include the views a reviewer needs.
Tasks.
- State drivers with constraints (teams, on-call, approximate peak write rate, PCI status). Label assumptions if you lack a measurement.
- Draw a context diagram and a container diagram for the system you would actually deploy this quarter.
- Write ADR-0007 with at least three options, including package-by-layer and a full microservice split.
- List positive and negative consequences.
- Define revisit triggers that are observable.
- Name confirmations that exist as tests or repository structure.
- Show the index line and the status.
- Describe the superseding record you would write if a PCI finding arrived, without pretending it has arrived.
Expected deliverables. Two diagrams, one ADR in Markdown, an index row, and a paragraph on how drift would be noticed.
Acceptance. A reviewer who disagrees can point at a driver or a missing cost. The container diagram contains no service the ADR does not accept. The confirmation section names ApplicationModules.verify() or an equivalent check.
Exercise 2: Repair a drifted picture
You have an accepted ADR that says finance and fulfillment consume Kafka, a component diagram with both arrows, and code where fulfillment uses an in-process listener. Write the doc-only pull request description: what you change, whether you amend or supersede, and what you would say in the incident review. Do not change runtime behavior in this exercise.
Rubric.
| Area | Weight |
|---|---|
| Drivers and constraints | 15% |
| Diagrams match the decision and name an audience | 20% |
| Options, decision, and costs | 25% |
| Revisit trigger and confirmation | 20% |
| Status, index, and supersession hygiene | 10% |
| Drift handling and what was left out on purpose | 10% |
Lesson Summary
Architecture documentation for this system is a small set of C4 views plus an ADR log in git. Context and container diagrams tell a new hire and an on-call what exists today. Component structure is enforced by Spring Modulith and explained by a diagram that must match it. An ADR that survives staff review names drivers, real options, a quotable decision, the costs of that decision, a revisit trigger, and a confirmation in the repository. “Stay a modular monolith” is one of those decisions, not a delay. Supersede records when the decision changes; do not overwrite history. Draw deployed boxes only. Aspirational arrows become bad incident commands. Generated canvases and verify() keep the structural part honest. A quarterly read keeps the prose honest. The twelve-service slide is not a rival source of truth. The next module of this course takes these boundaries into low-level design, and the microservices module will assume you can point at the ADR that justified a split.
Further Reading
- Michael Nygard, Documenting Architecture Decisions — the original short ADR form.
- MADR — Markdown template with options and consequences, the shape of ADR-0007 in this lesson.
- Simon Brown, The C4 model — context, container, component, code, and the supplementary views.
- Structurizr — a model-first tool when Mermaid files are no longer enough.
- arc42 template overview — the long-form outline this lesson uses as a cover sheet, not as a second source of truth.
- Spring Modulith, Documentation —
Documenterand module canvases generated from packages. - Microsoft, Architecture decision records — drivers, decision, and consequences in a well-architected role.
- AWS, Well-Architected Framework — quality-attribute language for drivers when a pillar is actually in play.
- Enterprise track: Architecture Decision Records.
- Sibling lessons: Requirements and Architecture Drivers, Architectural Thinking and Quality Attributes, Layered and Modular Monolith Architecture, Event-Driven Architecture Fundamentals, Staff Engineer Architecture Decision-Making.