Advanced System Design Tutorial 0/70 lessons ~6 min read Lesson 10

    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

    Course progress0%
    Focus
    15 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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:

    1. Choose the C4 view — context, container, component, or deployment — that answers a specific reader’s question, and refuse a diagram with no audience.
    2. Write an architecture decision record with status, context, drivers, options, decision, and consequences, including the cost of the option you accepted.
    3. Record “remain a modular monolith” as a real decision with a revisit trigger, not as the absence of a decision.
    4. Link an ADR to the modules, schemas, and tests that make it true, including ApplicationModules.verify() and the outbox table.
    5. Supersede a record without rewriting history, and point readers at the replacement.
    6. Detect documentation drift: a diagram, a README, and a record that disagree with the repository, and say which one you will fix.
    7. Run a staff review that rejects records whose only consequence is “improved scalability,” with no driver, no alternative, and no owner.
    8. 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:

    ArtifactQuestion it answersUpdate 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 viewModules inside the Boot process, and their allowed edgesA new module or a new allowedDependencies
    Deployment viewProcesses, broker, database, trust zonesA topology or PCI boundary change
    GlossaryWhat a word means in one contextThe language changes (lesson 8)
    ADR logWhy a significant decision standsA decision is proposed, accepted, or superseded
    Generated module canvasDoes 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:

    1. 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.
    2. 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.
    3. 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.
    4. 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:

    SectionWhat a reviewer looks for
    StatusProposed, Accepted, Superseded by ADR-00N, or Rejected. Not “final.”
    Date and ownerA squad or a named role that still exists. “Architecture team” is not an owner if nobody carries a pager.
    DriversQuality attributes and constraints, tied to a requirement. PCI scope, four squads, 40 orders per second, one on-call rotation.
    OptionsAt least two, including the one you did not pick. Real options, not a cartoon.
    DecisionOne sentence a stranger can quote.
    ConsequencesBenefits, costs, and new risks. The costs are mandatory.
    Revisit triggerThe observable condition that reopens the decision.
    ConfirmationWhich 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:

    1. Can I quote the decision in one sentence?
    2. Are the drivers falsifiable? “Scale” is not. “Peak 40 placements per second on one write database, with catalog read-heavy” is.
    3. Was a serious alternative considered, with a real downside of the chosen option?
    4. Does the revisit trigger name a signal, not a mood?
    5. What in the repository would be wrong if we violated this next week, and is that check automated?
    6. 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

    mermaid
    C4Context
    title System context for TechLearningPro retail commerce
    Person(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")
    mermaid
    flowchart TB
    subgraph zone [Commerce trust zone one deployable]
    APP[Spring Boot commerce]
    PUB[Outbox publisher thread]
    APP --- PUB
    end
    PG[(PostgreSQL schemas catalog orders payment fulfillment returns)]
    K[(Kafka topic orders.order-authorized)]
    ACQ[Card acquirer]
    ERP[Legacy ERP]
    APP --> PG
    PUB --> PG
    PUB --> K
    APP --> ACQ
    APP --> ERP
    K --> 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.

    mermaid
    flowchart LR
    ADR[ADR Markdown in git] --> PR[Pull request]
    CODE[Module packages and SQL] --> PR
    PR --> 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

    text
    docs/
    c4/
    01-system-context.md
    02-containers.md
    03-commerce-components.md
    04-deployment.md
    adr/
    README.md
    ADR-0007-modular-monolith.md
    ADR-0008-outbox-for-order-authorized.md
    glossary/
    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:

    • ModularityTest calling ApplicationModules.of(CommerceApplication.class).verify().
    • package-info.java files and their allowedDependencies.
    • Schemas catalog, orders, payment, fulfillment, returns.
    • The absence of a second deploy pipeline for catalog.

    ADR-0008’s confirmation points at:

    • orders.outbox created in the orders migration directory.
    • A test that rolls back and expects no outbox row.
    • KafkaTemplate referenced 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 OrderAuthorized through 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 edges
    Drivers
    - Four squads commit to commerce. Independent modules are required so they
    do not edit one shared service package.
    - Peak checkout is about 40 placements per second. The write path fits one
    PostgreSQL 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 leave
    the commerce jar.
    - Shoppers wait for price, stock hold, and authorization. They do not wait
    for shipment, finance, or loyalty (ADR-0008).
    Options considered
    1. One modular monolith: modules catalog, orders, payment, fulfillment,
    Spring Modulith verification, schema per module, synchronous calls where
    the shopper waits, Kafka outbox for OrderAuthorized.
    2. Split into deployables now: one service per module, synchronous HTTP for
    price, reserve, and pay, Kafka between them, one database per service.
    3. Leave the historic package-by-layer layout and postpone boundaries.
    Decision
    Accept option 1. Commerce ships as one Spring Boot deployable. Module
    boundaries are enforced with ApplicationModules.verify() and schema
    ownership. Payment is extracted into its own deployable only when a PCI
    finding requires the smaller artifact, and only along the existing
    PaymentGateway 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 stay
    in-process. Squads can own packages and migrations.
    - Negative: a defect that exhausts the shared heap or the shared pool
    affects every module. PCI scope includes catalog’s admin routes until
    extraction. We do not get independent scaling of fulfillment.
    - Risks: a shared kernel that grows into a hidden fifth module; a
    cross-schema join that bypasses verify() because it is SQL. Both have
    confirmations below.
    - Option 2 is not the default next step. Option 2 is a new ADR when a
    trigger fires.
    Revisit triggers
    - A written PCI finding that the commerce artifact is in scope and that a
    payment-only artifact would be accepted out of scope.
    - Sustained write-path saturation on the primary that a module-local change
    cannot fix, demonstrated with capacity numbers.
    - A second on-call organization that must ship fulfillment or payment on
    its own release train.
    Confirmation
    - ModularityTest verifies ApplicationModules.
    - allowedDependencies: orders to catalog, payment, fulfillment; payment
    empty; 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 same
    change 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

    SymptomLikely causeWhat to checkCorrection
    Two current picturesSlide and docs/c4 both existWhich one onboarding linksOne canonical path; mark the other historical
    ADR accepted, modules do not matchRecord written after a workshop, code never followedallowedDependencies versus the decision sentenceChange the code or supersede the ADR
    Incident action based on a missing boxDiagram showed a consumer that is not deployedContainer view versus consumer groupsDraw only what runs; list consumers in the ADR
    Nobody knows if an ADR is in forceStatus not updated, no indexdocs/adr/README.mdIndex with status; superseded links both ways
    Review argues about wording for an hourDrivers are slogansDriver sectionRewrite drivers as constraints and numbers, then resume
    Generated canvas differs from the component docHand diagram not updated after a package moveCI diff or side-by-sideUpdate the hand view in the same PR as the package
    New service appears without a recordADR process is optional under deadlineRecent deployables versus ADR titlesRequire the record for a new pipeline
    Regulator bundle is a stale PDFExport was a one-time copyCommit hash on the coverGenerate 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.

    1. State drivers with constraints (teams, on-call, approximate peak write rate, PCI status). Label assumptions if you lack a measurement.
    2. Draw a context diagram and a container diagram for the system you would actually deploy this quarter.
    3. Write ADR-0007 with at least three options, including package-by-layer and a full microservice split.
    4. List positive and negative consequences.
    5. Define revisit triggers that are observable.
    6. Name confirmations that exist as tests or repository structure.
    7. Show the index line and the status.
    8. 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.

    AreaWeight
    Drivers and constraints15%
    Diagrams match the decision and name an audience20%
    Options, decision, and costs25%
    Revisit trigger and confirmation20%
    Status, index, and supersession hygiene10%
    Drift handling and what was left out on purpose10%

    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

    Ready to mark this lesson complete?Track your journey across the entire course.