Architettura di dati e sistemi, dalle fondamenta Lezione 4 / 80

Architectural Decision Records (ADR)

Catturare le decisioni con le loro alternative e conseguenze. Il formato di Michael Nygard, su cui la maggior parte dei team ha convergito.

Tra sei mesi, qualcuno nel tuo team punterà il dito su un pezzo del sistema e chiederà: “perché il servizio di auth usa JWT invece delle sessioni?” Oppure: “perché siamo su Postgres e non MongoDB?” Oppure: “perché la coda è Redis e non RabbitMQ, visto che avevamo già RabbitMQ in un altro servizio?”

La risposta onesta, nella maggior parte dei team, è: nessuno se lo ricorda. La persona che ha preso la decisione se n’è andata nel 2024. C’era un thread Slack lunghissimo, ma è scivolato fuori dal tier gratuito. C’era una riunione, ma gli appunti sono nel OneNote privato di qualcuno. C’era una pagina wiki, ma è stata modificata l’ultima volta due anni fa e fa riferimento a un’azienda che da allora ha fatto rebranding due volte. Il ragionamento è sparito. Quello che resta è la decisione, seduta in produzione, che fa il suo lavoro, senza che nessuno sappia spiegare perché era la scelta giusta.

Questo è il problema che gli ADR risolvono. Gli Architectural Decision Records sono documenti brevi, in plain text, scritti al momento della decisione, che catturano cosa è stato deciso, quali erano le alternative, e quali saranno le conseguenze. Vivono nel repository, accanto al codice che descrivono. Non vengono cancellati quando diventano obsoleti; vengono marcati come superseded e il nuovo ADR rimanda a quello vecchio. Fatti bene, gli ADR diventano il record archeologico del tuo sistema: un ingegnere futuro può leggerli in ordine e capire come l’architettura è arrivata dov’è.

Questa lezione parla del formato che ha vinto (quello di Michael Nygard, dal 2011), di cosa mettere in un ADR, di quando scriverne uno, e di che aspetto hanno gli ADR pubblici reali. È breve sulla teoria e lunga sul “ecco cosa fai davvero martedì mattina quando bisogna prendere una decisione.”

Il formato Nygard

Nel novembre 2011, Michael Nygard, autore di Release It! e da molto tempo consulente in aziende che erano in giro da abbastanza per pentirsi delle proprie architetture, scrisse un post di blog intitolato Documenting Architecture Decisions. Il post è meno di 1.000 parole. Proponeva un formato con cinque sezioni:

  1. Title. Una breve frase sostantivale. “Use Postgres for the order service.”
  2. Status. Proposed, accepted, deprecated, oppure superseded by ADR-0017.
  3. Context. Qual è la situazione, quali sono le forze in gioco, perché serve una decisione.
  4. Decision. Cosa faremo, in voce attiva. “We will use Postgres.”
  5. Consequences. Cosa diventa più facile, cosa diventa più difficile, a cosa ci siamo impegnati.

Tutto qui. L’intero formato sta in 30 righe di spiegazione nel post originale. Quasi ogni template ADR che vedi in giro oggi è questo formato con variazioni cosmetiche: qualcuno ha aggiunto una sezione “Alternatives Considered”, qualcun altro ha diviso Consequences in “Positive” e “Negative”, qualcuno ha aggiunto un footer “Related decisions”. Le ossa sono di Nygard.

Il formato funziona per quello che deliberatamente lascia fuori. Non c’è un executive summary. Non c’è una matrice dei rischi. Non c’è una RACI chart. Non c’è una roadmap. Un ADR non è un documento di progetto; è un documento di decisione. Registra una decisione, e una sola, in circa una o due pagine di plain Markdown. Qualunque cosa più grande è un altro tipo di artefatto.

Status

Lo status è una singola parola o una frase breve. Il ciclo di vita è:

  • Proposed: qualcuno ha scritto l’ADR, è in review, il team non si è ancora impegnato.
  • Accepted: il team ha concordato; la decisione è ora in vigore.
  • Deprecated: la decisione non viene più seguita, ma non esiste ancora un sostituto.
  • Superseded by ADR-NNNN: un ADR più nuovo sostituisce questo. Il vecchio ADR resta nel repo; aggiorni semplicemente la riga di status e aggiungi un riferimento a ritroso.

Il punto cruciale è che gli ADR sono append-only. Non cancelli gli ADR superseded. Non li modifichi sul posto per riflettere la nuova decisione. Scrivi un nuovo ADR, colleghi i due, e lasci il vecchio lì come record. Tra sei mesi, quando qualcuno chiederà “aspetta, non usavamo Postgres?”, potrà leggere ADR-0007 (“Use Postgres for the order service”, Superseded by ADR-0034), poi leggere ADR-0034 (“Move order service to DynamoDB”), e vedere l’intero arco.

Context

La sezione context è dove sta la maggior parte del valore. Descrive il mondo al momento in cui la decisione è stata presa: cosa stavamo costruendo, quali erano i vincoli, a cosa ci eravamo già impegnati altrove, di cosa avevamo paura. Il te futuro non si ricorderà nulla di tutto questo. Il te futuro vedrà il sistema in produzione così com’è e non avrà idea di quale trade-off si stesse navigando.

Le sezioni context buone nominano le forze esplicitamente. “Ci aspettiamo grosso modo 50 scritture al secondo e 500 letture al secondo nel primo anno, in scala fino a forse 10x quel valore entro il terzo anno. Abbiamo un ingegnere con esperienza profonda di Postgres e nessuno che abbia gestito MongoDB in produzione. Il team compliance richiede che siamo in grado di puntare a audit log a livello di riga.”

Decision

Voce attiva, presente, un paragrafo. “We will use Postgres 16, hosted on RDS, with a single primary and one read replica in another availability zone.”

Non “Postgres sembra una buona scelta.” Non “il team propende per Postgres.” La sezione decision si impegna.

Consequences

Le consequences sono la sezione onesta. Ogni decisione chiude alcune porte e ne apre altre. Esplicitare le conseguenze negative non è pessimismo; è l’unico modo in cui la prossima persona possa valutare se il trade-off regge ancora.

“Ci impegniamo a far girare Postgres in produzione, il che significa che i nostri ingegneri on-call devono imparare le preoccupazioni operative di Postgres: vacuum, replication lag, connection pooling. Rinunciamo alla flessibilità per-document di un document store; se dovremo aggiungere campi spesso pagheremo migrazioni. Beneficiamo di forti garanzie transazionali e di un linguaggio di query che il team già conosce.”

Dove vivono gli ADR

Gli ADR vanno nel repository, accanto al codice che descrivono. Il path convenzionale è docs/adr/ o docs/architecture/decisions/. I filename sono numerati: 0001-record-architecture-decisions.md, 0002-use-postgres-for-orders.md, 0003-jwt-for-auth.md. Il primo ADR è quasi sempre quello che dice “useremo gli ADR”, cosa che sembra ricorsiva ma è genuinamente utile: impegna il team alla pratica e dice ai contributori futuri dove guardare.

Plain Markdown. Nessun tooling speciale richiesto. Esiste un piccolo tool chiamato adr-tools, uno shell script che crea nuovi ADR da un template e gestisce la numerazione, e ci sono equivalenti specifici per linguaggio in Node, Python, e Go. Usane uno se ti piace, oppure copia semplicemente il file ADR precedente e incrementa il numero. Il formato è economicissimo di proposito.

Le PR che introducono cambiamenti architetturali significativi dovrebbero includere l’ADR nello stesso commit. Fare la review dell’ADR è fare la review del design. Se il team non è d’accordo con la decisione, l’ADR viene rivisto prima che il codice atterri. Questa è una delle poche pratiche di documentazione che mantiene davvero la documentazione in sync con il codice, perché la documentazione è parte della code review.

Quando scrivere un ADR

La regola del pollice che funziona in pratica: scrivi un ADR per qualunque decisione che richieda un’argomentazione su Slack per essere risolta. Se due ingegneri ragionevoli potrebbero non essere d’accordo, e il team ha dovuto discuterne per più di 15 minuti, quella decisione vale la pena di essere registrata. Se la risposta è ovvia per tutti, salta.

Cose che di solito meritano un ADR:

  • Scelta del database, della coda, della cache, o di qualunque infrastruttura con un ciclo di sostituzione lungo.
  • Modello di authentication e authorization (sessioni vs JWT vs scelta del flow OAuth).
  • Decisioni sui confini dei servizi: “questo è un servizio” vs “questi sono due servizi”.
  • Contratti di API pubbliche: REST vs gRPC vs GraphQL.
  • Cross-cutting concerns: formato di logging, strategia di tracing, convenzioni di error handling.
  • Decisioni di build e deploy: monorepo vs polyrepo, provider CI, container registry.

Cose che non lo meritano:

  • Nomi di variabili, layout dei file dentro un servizio, scelte di formatting.
  • Piccole scelte di libreria facili da scambiare (quale parser JSON, quale libreria per le date).
  • Qualunque cosa catturata da un linter o da una code review.

Il rischio in un team giovane è scriverne troppi; il rischio in un team maturo è scriverne troppo pochi. Entrambi falliscono allo stesso modo: nessuno li legge più. Punta a qualcosa tra cinque e venti ADR nel primo anno di un progetto, in crescita lenta dopo.

ADR pubblici reali

Diversi progetti grandi pubblicano i loro ADR pubblicamente. Vale la pena leggerli sia come esempi del formato sia come finestre su come team seri ragionano sui trade-off.

Backstage, la piattaforma open-source per developer-portal di Spotify, ha oltre 30 ADR pubblici che coprono decisioni come “use Lerna for monorepo management” (poi superseded), “expose the React component for breadcrumbs”, e “version-control plugin metadata in YAML.” Il formato è esattamente quello di Nygard, con leggere aggiunte. Leggi ADR-001 (“Architecture Decisions”) e ADR-005 (“Catalog Core Entities”) per vedere lo stile della casa.

Arc42 mantiene un sito community con un grande catalogo di esempi e template ADR, incluse alternative al formato Nygard (il formato MADR aggiunge una sezione “Alternatives Considered” come blocco di prima classe, che raccomanderei). È la cosa più vicina a un riferimento canonico che la community ADR abbia.

Il ThoughtWorks Technology Radar non è un repository di ADR, ma è il documento pubblico di larga scala più vicino al concetto di “cose che i nostri practitioner pensano dovresti adottare, sperimentare, valutare, o tenere a bada.” È in sostanza il feed ADR collettivo dell’industria per le decisioni di tooling, ed è aggiornato due volte all’anno.

Se devi leggere solo un ADR esterno prima di scrivere il tuo primo, leggi ADR-001 di Backstage. È breve, è il progetto che si impegna a tenere gli ADR, e mostra il formato usato sul serio.

Un esempio lavorato

Ecco che aspetto ha un ADR per un team fittizio che ha appena deciso di usare Postgres per il proprio servizio di ordini. Questo è l’intero file; non c’è altra documentazione della decisione.

# ADR-0007: Use Postgres for the order service

## Status

Accepted, 2025-10-28.

## Context

The order service stores customer orders, line items, payments, and
fulfilment status. Expected load in year one is ~50 writes/sec and
~500 reads/sec, growing to roughly 10x that by year three. The data
is highly relational: orders have many line items, line items
reference products and pricing, payments reference orders and
refund chains.

The team has three engineers with deep Postgres experience and
nobody with MongoDB in production. Our finance partners require
row-level audit logs and ACID guarantees on payment state
transitions. The platform already runs Postgres for the user
service, so we have a managed RDS pattern, backup tooling, and
on-call runbooks for it.

We considered Postgres, MongoDB, and DynamoDB.

## Decision

We will use Postgres 16 on AWS RDS for the order service. One
primary in eu-central-1a, one read replica in eu-central-1b for
analytics queries and failover. PgBouncer in front for connection
pooling. Schema migrations via Flyway, run on deploy.

## Consequences

Positive:
- Strong transactional guarantees on payment state changes.
- Reuse of existing operational tooling, backups, alerts.
- Team can debug query plans on day one.
- SQL is a known quantity for analytics and ad-hoc queries.

Negative:
- We commit to schema migrations for every new field. We will
  pay this cost on every release.
- Single-region primary; cross-region writes are not supported.
  If we ever need EU + US write locality, we will need to revisit.
- We are now responsible for index design. A bad query at 10x
  load will hurt.

Alternatives considered:
- MongoDB: rejected. Team has no operational experience, and the
  schema flexibility benefit is not worth the audit-log cost.
- DynamoDB: rejected. Single-table design would constrain the
  analytics team, and we would still need a separate relational
  store for reporting.

Questo è l’intero artefatto. Un singolo file Markdown, in docs/adr/0007-use-postgres-for-orders.md, committato accanto alla prima migrazione che crea le tabelle degli ordini. Tra due anni, quando qualcuno chiederà perché gli ordini sono su Postgres, questa è la risposta.

Il ciclo di vita

Ecco il ciclo di vita di un ADR sotto forma di flowchart:

flowchart LR
    A[Proposed] --> B[Accepted]
    A --> X[Rejected]
    B --> C[Deprecated]
    B --> D[Superseded by ADR-NNNN]
    C --> D

La maggior parte degli ADR passa da Proposed ad Accepted in una singola review di PR e poi resta in Accepted per sempre, facendo il proprio lavoro. Un piccolo numero viene superseded man mano che il sistema cresce. Una manciata viene deprecata senza sostituto, di solito perché la cosa su cui decidevano non esiste più.

Il cimitero degli ADR superseded è la parte che la maggior parte dei team trova poco intuitiva. L’istinto è cancellare il vecchio perché è “ora sbagliato.” Resisti all’istinto. Il vecchio ADR è il record storico di perché il team la pensava diversamente nel 2024, e la catena delle supersession è una mappa di come l’architettura si è evoluta. Quando un nuovo ingegnere chiede “perché siamo così?”, quella catena è la risposta.

Cosa gli ADR non sono

Gli ADR non sono specifiche. Non descrivono in dettaglio come è costruito il servizio degli ordini; descrivono una decisione su di esso. Il design completo vive nel codice, nelle API doc, nei runbook. L’ADR cattura solo la scelta che contava al momento.

Gli ADR non sono contratti. Il team è autorizzato a cambiare idea, e supersedere un ADR precedente con uno nuovo. La leggerezza del formato è ciò che rende questo pratico: riscrivere un design doc da 50 pagine quando il database cambia è impensabile, ma scrivere ADR-0034 che superseda ADR-0007 è il lavoro di un martedì pomeriggio.

Gli ADR non sono piani di progetto. Non hanno scadenze, owner, o budget. Se i tuoi ADR stanno facendo crescere quelle sezioni, li stai lentamente trasformando in qualcos’altro, e il formato smetterà di funzionare.

Iniziando domani

Ecco lo starter kit per un team che non ha mai scritto un ADR:

  1. Crea docs/adr/ nel tuo repo principale.
  2. Copia il template Nygard in 0001-record-architecture-decisions.md e adattalo: “registreremo le decisioni architetturali usando ADR nel formato Nygard.”
  3. La prossima volta che scoppia un’argomentazione architetturale vera su Slack, scrivi 0002-<qualunque-cosa-fosse>.md e linkalo nel thread.
  4. Aggiungi una checkbox al template della tua PR: “Se questa PR prende una decisione architetturale, c’è un ADR?”

Questo è l’intero onboarding. Il formato è abbastanza piccolo da far sì che un team lo adotti in un pomeriggio, e dopo un anno d’uso l’archivio delle decisioni diventa uno dei documenti più letti del repository.

Nella prossima lezione passiamo da “come registrare le decisioni” a “perché ogni decisione è un trade-off, e quali sono quelle che si presentano più spesso in questo mestiere.”

Cerca