Diagramma dinamico C4: guida con esempi
Un diagramma dei container ti dice che l'API parla con l'order service, che l'order service parla con Kafka e che il notification service legge da Kafka. Non ti dice cosa succede, e in quale ordine, quando un cliente clicca Effettua ordine. Il pagamento viene incassato prima o dopo che la riga dell'ordine è stata scritta? L'email di conferma aspetta il magazzino? Sono le domande che si fanno in una post-mortem di un incidente, e i diagrammi statici non sanno rispondere.
È il compito del diagramma dinamico C4. Prende elementi che hai già disegnato e numera le interazioni tra di essi per uno scenario specifico. Questa guida spiega cos'è un diagramma dinamico, in cosa differisce da un diagramma di sequenza UML, quando vale la pena disegnarne uno (meno spesso di quanto pensi), un esempio completo svolto, gli errori più comuni e come evitare che diventi obsoleto quando il modello statico cambia.
Se il C4 è nuovo per te, parti da cos'è il modello C4. L'esempio svolto più sotto si basa sul tipo di diagramma trattato nella guida al diagramma dei container.
Cos'è un diagramma dinamico
Il diagramma dinamico è uno dei diagrammi supplementari del modello C4, insieme al system landscape e al diagramma di deployment. Non è uno dei quattro livelli principali. Sta accanto a loro e ne prende in prestito gli elementi.
La definizione di c4model.com è breve:
- Ambito: "Una particolare funzionalità, storia, caso d'uso, ecc."
- Elementi: "A tua scelta: puoi mostrare software system, container o componenti che interagiscono a runtime."
- Pubblico: "Persone tecniche e non tecniche, dentro e fuori dal team di sviluppo software."
- Consigliato? "No, i diagrammi dinamici vanno usati con parsimonia per mostrare pattern interessanti/ricorrenti o funzionalità che richiedono un insieme complicato di interazioni."
Da questa definizione derivano due cose.
Primo, un diagramma dinamico mostra istanze di relazioni che hai già. Se il diagramma dei container ha una freccia dall'order service a Kafka, il diagramma dinamico dice "e nel passo 4 del checkout, quella freccia viene usata per pubblicare OrderPlaced". Il DSL di Structurizr lo rende esplicito: la sua documentazione dice che con una vista dinamica "stai mostrando istanze di relazioni definite nel modello statico", e la relazione deve prima esistere lì (riferimento del DSL di Structurizr). È un vincolo utile. Impedisce al diagramma dinamico di inventare una chiamata che il modello statico non conosce.
Secondo, c'è uno scenario per diagramma. Non "come funziona l'order service", ma "il cliente effettua un ordine, pagamento con carta, articolo disponibile". Il percorso di errore ha il suo diagramma, se vale la pena disegnarlo.
L'ordine è indicato con numeri sulle frecce. Tutta la notazione è qui: gli stessi box, le stesse frecce, più un numero di sequenza e una descrizione di cosa succede in quel passo.
Diagramma dinamico vs diagramma di sequenza
"Diagramma di sequenza C4" è una ricerca comune, e la confusione è comprensibile: i due diagrammi rispondono alla stessa domanda. Il sito del C4 dice che il diagramma dinamico può essere disegnato in due stili che portano la stessa informazione:
- Stile collaborazione. Box disposti liberamente (di solito dove si trovano nel diagramma dei container) con frecce numerate tra di loro. Il C4 osserva che questo stile si basa sul diagramma di comunicazione UML, prima chiamato diagramma di collaborazione.
- Stile sequenza. Elementi disposti in colonne in alto, il tempo che scorre verso il basso, frecce tra le lifeline. Somiglia a un diagramma di sequenza UML, ma i partecipanti sono elementi C4.
Quindi un diagramma dinamico in stile sequenza è una sorta di diagramma di sequenza. Le differenze vere sono rispetto a un classico diagramma di sequenza UML ricavato dal codice:
| Diagramma dinamico C4 | Diagramma di sequenza UML (uso tipico) | |
|---|---|---|
| Partecipanti | Sistemi, container o componenti del tuo modello C4 | Oggetti, classi, spesso a livello di metodo |
| Cosa significa una freccia | Un utilizzo di una relazione del modello statico, con il suo protocollo | Un messaggio o una chiamata di metodo |
| Livello di dettaglio | Architetturale: "pubblica OrderPlaced (Kafka)" |
Spesso implementativo: validate(), save(), valori di ritorno |
| Notazione | Box e frecce numerate, una legenda spiega ciò che è insolito | Lifeline, barre di attivazione, frammenti combinati (alt, loop, par) |
| Collegamento con altri diagrammi | Riusa elementi del diagramma dei container o dei componenti | Di solito a sé stante |
Usa lo stile collaborazione quando la disposizione spaziale ha un significato, per esempio quando i lettori conoscono già il diagramma dei container e vuoi che il flusso appaia sopra di esso. Usa lo stile sequenza quando l'ordine è tutto ciò che conta, quando ci sono più di otto passi circa, o quando c'è molto botta e risposta tra due elementi (richiesta, risposta, callback). Nessuno dei due è più corretto; il C4 lascia la scelta a te.
Se ti servono frammenti alt e loop per spiegare uno scenario, spesso è il segno che stai descrivendo un algoritmo più che un'architettura. Disegna la versione architetturale come diagramma dinamico e lascia la versione dettagliata a un diagramma di sequenza UML accanto al codice, se qualcuno ne ha bisogno. Il nostro confronto tra C4 e UML spiega dove si colloca ciascuna notazione.
Quando vale la pena disegnarne uno (e quando no)
La risposta del C4 stesso a "consigliato?" è no, e vale la pena prenderla sul serio. Ogni diagramma dinamico è un altro artefatto che deve cambiare quando cambia l'architettura. Disegnane uno quando lo scenario soddisfa almeno uno di questi criteri:
- L'ordine non è ovvio dal diagramma statico. Checkout, incasso del pagamento, una saga che compensa in caso di errore. Se un ingegnere senior del team sbaglierebbe l'ordine, disegnalo.
- Lo scenario attraversa più container o sistemi. Qualsiasi cosa tocchi quattro o più container, o esca dal tuo sistema e ci rientri (webhook, callback, redirect verso terze parti come 3-D Secure).
- È asincrono. Appena entra in gioco una coda, il diagramma statico mostra che A e B toccano entrambi Kafka, ma non che B viene eseguito dopo A, o che A non lo aspetta.
- Si ripete. Un pattern usato in molti punti (come ogni servizio autentica una richiesta, come ogni scrittura emette un evento) merita un diagramma a cui il resto della documentazione possa rimandare.
- Qualcuno lo chiede in una review o durante un incidente. È il segnale migliore che ci sia. Se una post-mortem ha passato venti minuti a ricostruire una sequenza su una lavagna, quella sequenza merita un diagramma.
Lascia perdere quando:
- Il flusso è una linea retta. Browser, API, database, ritorno. Il diagramma dei container lo dice già.
- È CRUD. Cinque diagrammi dinamici per create, read, update, delete e list non aggiungono nulla.
- Nessuno lo leggerà. Un diagramma dinamico per ogni user story è un backlog di documentazione, non documentazione.
Un obiettivo ragionevole per un prodotto tipico è una manciata: i due o tre percorsi che fanno guadagnare o che svegliano la gente di notte, più uno o due pattern ricorrenti.
Esempio svolto: "il cliente effettua un ordine"
Prendi il sistema e-commerce della nostra guida completa. Il suo diagramma dei container ha una single-page app React, un API gateway Kong, servizi Go per ordini, prodotti e utenti (ognuno con il proprio database PostgreSQL), Kafka e un notification service. Al livello 1, il sistema dialoga anche con Stripe come gateway di pagamento e con SendGrid per le email.
Ecco le relazioni del modello statico che questo scenario usa. Ogni passo qui sotto deve corrispondere a una di esse.
[Customer] --> [Single-Page Application (React)] : Uses (HTTPS)
[Single-Page Application] --> [API Gateway (Kong)] : Makes API calls (HTTPS/JSON)
[API Gateway] --> [Order Service (Go)] : Routes requests
[Order Service] --> [Product Service (Go)] : Checks stock (gRPC)
[Order Service] --> [Payment Gateway (Stripe)] : Authorizes payments (HTTPS/REST)
[Order Service] --> [Order Database (PostgreSQL)] : Reads/writes orders (SQL)
[Order Service] --> [Message Queue (Kafka)] : Publishes order events
[Notification Service (Go)] --> [Message Queue] : Consumes order events
[Notification Service] --> [Email Service (SendGrid)] : Sends email (HTTPS)
Il diagramma dinamico, stile collaborazione
Le interazioni numerate, disegnate sugli stessi box:
1. [Customer] -> [Single-Page Application] : Clicks "Place order"
2. [Single-Page Application] -> [API Gateway] : POST /orders (HTTPS/JSON)
3. [API Gateway] -> [Order Service] : Routes the authenticated request
4. [Order Service] -> [Product Service] : Reserves stock for each line item (gRPC)
5. [Order Service] -> [Payment Gateway (Stripe)] : Authorizes the card for the order total (HTTPS)
6. [Order Service] -> [Order Database] : Writes the order with status "placed" (SQL)
7. [Order Service] -> [Message Queue] : Publishes OrderPlaced (Kafka)
8. [Order Service] -> [Single-Page Application] : Returns 201 with the order number (via the gateway)
9. [Notification Service] -> [Message Queue] : Consumes OrderPlaced (Kafka)
10. [Notification Service] -> [Email Service (SendGrid)] : Sends the confirmation email (HTTPS)
Disposti sul diagramma dei container, i numeri raccontano la storia: i passi da 1 a 8 sono sincroni e avvengono mentre il cliente aspetta, i passi 9 e 10 avvengono dopo e il cliente non li aspetta mai.
Lo stesso scenario, stile sequenza
| # | Da | A | Cosa succede | Sincrono? |
|---|---|---|---|---|
| 1 | Customer | Single-Page Application | Clicca "Effettua ordine" | sì |
| 2 | Single-Page Application | API Gateway | POST /orders |
sì |
| 3 | API Gateway | Order Service | Instrada la richiesta | sì |
| 4 | Order Service | Product Service | Riserva le scorte | sì |
| 5 | Order Service | Payment Gateway (Stripe) | Autorizza la carta | sì |
| 6 | Order Service | Order Database | Scrive l'ordine | sì |
| 7 | Order Service | Message Queue | Pubblica OrderPlaced |
no (fire and forget) |
| 8 | Order Service | Single-Page Application | Restituisce 201 con il numero d'ordine | sì |
| 9 | Notification Service | Message Queue | Consuma OrderPlaced |
asincrono |
| 10 | Notification Service | Email Service (SendGrid) | Invia la conferma | asincrono |
Una tabella come questa è un modo perfettamente valido di mettere per iscritto un diagramma dinamico. Disegnata come lifeline, diventa lo stile sequenza.
Cosa ti dice il diagramma
Leggendo i dieci passi, puoi rispondere a domande a cui il diagramma dei container non sapeva rispondere:
- Cosa succede se Stripe è giù? Le scorte sono già riservate al passo 4 quando l'autorizzazione fallisce al passo 5. Qualcuno deve rilasciarle. Il diagramma rende evidente che l'order service ha bisogno di un percorso di compensazione, oppure che i passi 4 e 5 andrebbero invertiti.
- Il cliente può ricevere una conferma per un ordine che non esiste? No. L'evento viene pubblicato al passo 7, dopo la scrittura al passo 6. Se fossero invertiti, una scrittura fallita potrebbe comunque far partire un'email. (Se ti serve che scrittura e pubblicazione siano atomiche, è lì che entra in gioco una tabella outbox, e merita un ADR.)
- Cosa c'è sul percorso critico del cliente? I passi da 2 a 8. L'email no, ed è per questo che passa da Kafka.
Ecco lo stesso scenario in Structurizr DSL, per i team che tengono il proprio modello come codice. Compila solo se ogni relazione esiste nel modello statico, che è il vincolo descritto sopra:
dynamic webshop "PlaceOrder" "Customer places an order" {
customer -> spa "Clicks Place order"
spa -> gateway "POST /orders"
gateway -> orderService "Routes the request"
orderService -> productService "Reserves stock"
orderService -> stripe "Authorizes the card"
orderService -> orderDb "Writes the order"
orderService -> kafka "Publishes OrderPlaced"
notificationService -> kafka "Consumes OrderPlaced"
notificationService -> sendgrid "Sends confirmation"
autoLayout lr
}
Il passo 8, la risposta, non è una relazione a sé nel modello statico, quindi resta fuori dalla versione DSL. Le risposte di solito sono implicite nella richiesta; disegnale solo quando la risposta in sé è importante.
Errori comuni
Troppi passi
Un diagramma dinamico con trenta frecce numerate è una sequenza che nessuno riesce a tenere a mente. Se uno scenario supera i quindici passi circa, dividilo: "checkout, fino al pagamento" e "checkout, dopo il pagamento", oppure un diagramma per ogni sistema che il flusso attraversa. La nostra documentazione sui flussi suggerisce da 5 a 15 passi per flusso per lo stesso motivo.
Mescolare i livelli
Il C4 ti lascia scegliere il livello (sistemi, container o componenti), ma scegline uno per diagramma. Un diagramma in cui il passo 3 va al container "Order Service" e il passo 4 va al componente PaymentClient al suo interno costringe il lettore a cambiare zoom a metà racconto. Se un passo ha bisogno del dettaglio dei componenti, disegna un secondo diagramma dinamico limitato a quel container.
Frecce che non esistono nel modello statico
Se il diagramma dinamico mostra il notification service che chiama direttamente l'order service, e il diagramma dei container non ha quella relazione, uno dei due è sbagliato. Di solito è il diagramma dinamico, disegnato a memoria. Tratta il modello statico come fonte di verità e fai in modo che ogni passo faccia riferimento a una delle sue relazioni.
Disegnare ogni chiamata
Health check, refresh dei token, invio dei log e scraping delle metriche sono reali, ma non sono lo scenario. Lascia fuori tutto ciò che comparirebbe in ogni diagramma dinamico che disegni. Se conta, riceve una volta sola il suo diagramma di pattern ricorrente.
Nascondere l'asincrono dietro frecce che sembrano sincrone
I passi 9 e 10 qui sopra avvengono quando il cliente ha già ricevuto una risposta. Se sono disegnati con le stesse frecce dei passi da 1 a 8, i lettori pensano che l'email venga inviata prima che la pagina si carichi. Segnala i passi asincroni (una linea tratteggiata, un'etichetta "async" o una numerazione separata come 9a) e spiega la convenzione nella legenda.
Tralasciare l'errore che conta
Un diagramma del percorso felice è l'impostazione predefinita giusta. Ma se il motivo per cui disegni il flusso è "cosa succede quando il pagamento fallisce", disegna quel percorso, non quello felice.
Mantenerlo veritiero quando il modello statico cambia
Un diagramma dinamico dipende due volte dal modello statico: dai suoi elementi e dalle sue relazioni. Questo lo rende una delle prime cose a diventare obsolete. Qualcuno rinomina l'order service in "checkout service", sostituisce Kafka con SQS o sposta la prenotazione delle scorte in un nuovo inventory service, e ogni diagramma dinamico che toccava quei box ora è sbagliato. Niente te lo segnala.
Tre abitudini aiutano:
- Disegna dal modello, non accanto al modello. Un diagramma dinamico in uno strumento di disegno è una copia del diagramma dei container, e le copie divergono. Una vista dinamica che fa riferimento agli elementi del modello tramite identificatore (come fa Structurizr DSL) almeno recepisce le rinomine, e fallisce in modo evidente quando una relazione scompare.
- Tieni la lista corta. Cinque diagrammi dinamici che controlli ogni trimestre valgono più di trenta che non apri mai.
- Rivedili quando cambiano i container che toccano. Quando una pull request modifica un container o una relazione, i diagrammi dinamici che li usano fanno parte della review.
Come funzionano i flussi in archyl
In archyl, un diagramma dinamico è un Flow (flusso): una lista ordinata di passi, ciascuno con un elemento di origine, un elemento di destinazione, una relazione e una descrizione, riprodotta passo dopo passo sul diagramma (documentazione sui flussi). Puoi costruirne uno a mano scegliendo le relazioni dal tuo modello, oppure descrivere lo scenario e lasciare che l'AI flow generator abbozzi i passi a partire dal tuo modello C4. Il generatore valida ogni passo rispetto al modello prima di salvarlo: origine e destinazione di ogni passo devono esistere, e la relazione citata deve collegare quei due elementi. Un passo che non corrisponde viene scartato anziché disegnato.
Due limiti, detti chiaramente perché sono esattamente il problema di cui parla questa sezione:
- Un flow conserva uno snapshot degli elementi e delle relazioni che usa, preso quando si aggiunge un passo. Questo mantiene leggibile un flow anche se un elemento viene poi eliminato, ma significa anche che rinominare un container nel modello non lo rinomina nei flow esistenti. Quando il modello cambia, apri i flow che lo toccano e controllali.
- Il drift score non verifica il comportamento. Il drift score di archyl ti dice se gli elementi documentati esistono ancora nel codice. Se una chiamata sincrona tra due servizi diventa un messaggio in coda e nulla viene rinominato o spostato, il punteggio non cambia, e nemmeno il flow.
Per approfondire il lato pratico, incluso come scriviamo i flussi come documenti con precondizioni e gestione degli errori, vedi documentare i flussi utente.
FAQ
Il diagramma dinamico fa parte del modello C4?
Sì, come diagramma supplementare. I quattro livelli principali sono System Context, Container, Component e Code. Il modello C4 aggiunge tre diagrammi supplementari: system landscape, dinamico e di deployment. Il diagramma dinamico riusa elementi dei livelli principali e mostra come interagiscono in uno scenario.
Qual è la differenza tra un diagramma dinamico C4 e un diagramma di sequenza?
Un diagramma dinamico C4 può essere disegnato in stile collaborazione (disposizione libera, frecce numerate) o in stile sequenza (lifeline, tempo che scorre verso il basso). Lo stile sequenza somiglia a un diagramma di sequenza UML, ma i suoi partecipanti sono sistemi, container o componenti C4, e ogni freccia è un utilizzo di una relazione del modello statico, non una chiamata di metodo.
Quale livello dovrebbe usare un diagramma dinamico?
Il livello che risponde alla domanda, e uno solo per diagramma. Il livello container è il più comune, perché la maggior parte degli scenari che vale la pena disegnare attraversa diverse unità deployabili. Usa il livello di sistema per i flussi tra sistemi e il livello dei componenti per spiegare l'interno di un container.
Quanti passi dovrebbe avere un diagramma dinamico?
Non c'è un limite ufficiale. Oltre i quindici passi circa, la maggior parte dei lettori perde il filo, quindi dividi lo scenario in parti o disegna un diagramma per ogni sistema che attraversa.
Un diagramma dinamico C4 può mostrare la messaggistica asincrona?
Sì. Mostra la pubblicazione e il consumo come passi numerati separati, e rendi visibile quali passi il chiamante aspetta e quali no: una linea tratteggiata, un'etichetta "async" o uno schema di numerazione separato, spiegato nella legenda del diagramma.
archyl supporta i diagrammi dinamici C4?
Sì, come Flow. Ogni passo fa riferimento a un elemento di origine, un elemento di destinazione e una relazione del tuo modello, e il flow viene riprodotto passo dopo passo sul diagramma. Puoi scrivere i flow a mano o generarne una bozza da una descrizione testuale. I flow conservano uno snapshot degli elementi che usano, quindi rivedili quando cambiano i container che toccano.
Vuoi disegnare il tuo primo flow su un modello che esiste già? Prova archyl gratis e genera prima il modello C4 dal tuo codice. Continua a leggere: Cos'è il modello C4? Una guida completa | Guida al diagramma dei container C4 | Documentare i flussi utente | Documentazione sui flussi.