Diagrama dinâmico C4: guia com exemplos
Um diagrama de containers diz que a API fala com o order service, que o order service fala com o Kafka e que o notification service lê do Kafka. Ele não diz o que acontece, e em que ordem, quando um cliente clica em Finalizar pedido. O pagamento é capturado antes ou depois de a linha do pedido ser gravada? O e-mail de confirmação espera o estoque? Essas são as perguntas feitas em uma revisão de incidente, e os diagramas estáticos não conseguem respondê-las.
Esse é o papel do diagrama dinâmico C4. Ele pega elementos que você já desenhou e numera as interações entre eles para um cenário específico. Este guia cobre o que é um diagrama dinâmico, como ele difere de um diagrama de sequência UML, quando vale a pena desenhar um (menos vezes do que você imagina), um exemplo completo, os erros mais comuns e como evitar que ele fique desatualizado quando o modelo estático muda.
Se o C4 é novidade para você, comece por o que é o modelo C4. O exemplo mais abaixo parte do tipo de diagrama tratado no guia do diagrama de containers.
O que é um diagrama dinâmico
O diagrama dinâmico é um dos diagramas suplementares do modelo C4, junto com o system landscape e o diagrama de deployment. Ele não é um dos quatro níveis principais. Fica ao lado deles e toma emprestados os seus elementos.
A definição do c4model.com é curta:
- Escopo: "Uma funcionalidade, história, caso de uso etc. em particular."
- Elementos: "Você escolhe: pode mostrar software systems, containers ou componentes interagindo em tempo de execução."
- Público: "Pessoas técnicas e não técnicas, dentro e fora da equipe de desenvolvimento de software."
- Recomendado? "Não, diagramas dinâmicos devem ser usados com moderação para mostrar padrões interessantes/recorrentes ou funcionalidades que exigem um conjunto complicado de interações."
Duas coisas decorrem dessa definição.
Primeiro, um diagrama dinâmico mostra instâncias de relacionamentos que você já tem. Se o diagrama de containers tem uma seta do order service para o Kafka, o diagrama dinâmico diz "e no passo 4 do checkout, essa seta é usada para publicar OrderPlaced". A DSL do Structurizr deixa isso explícito: a documentação diz que, com uma view dinâmica, "você está mostrando instâncias de relacionamentos definidos no modelo estático", e o relacionamento precisa existir lá antes (referência da DSL do Structurizr). É uma restrição útil. Ela impede que o diagrama dinâmico invente uma chamada que o modelo estático não conhece.
Segundo, é um cenário por diagrama. Não "como o order service funciona", mas "cliente faz um pedido, pagamento com cartão, item em estoque". O caminho de falha ganha seu próprio diagrama, se valer a pena desenhá-lo.
A ordem é indicada com números nas setas. A notação é só isso: as mesmas caixas, as mesmas setas, mais um número de sequência e uma descrição do que acontece naquele passo.
Diagrama dinâmico vs diagrama de sequência
"Diagrama de sequência C4" é uma busca comum, e a confusão é compreensível: os dois diagramas respondem à mesma pergunta. O site do C4 diz que o diagrama dinâmico pode ser desenhado em dois estilos que carregam a mesma informação:
- Estilo colaboração. Caixas dispostas livremente (em geral onde estão no diagrama de containers) com setas numeradas entre elas. O C4 observa que esse estilo se baseia no diagrama de comunicação UML, antes chamado de diagrama de colaboração.
- Estilo sequência. Elementos em colunas no topo, o tempo correndo para baixo, setas entre as linhas de vida. Parece um diagrama de sequência UML, mas os participantes são elementos C4.
Então um diagrama dinâmico no estilo sequência é um tipo de diagrama de sequência. As diferenças reais são em relação a um diagrama de sequência UML clássico, feito a partir do código:
| Diagrama dinâmico C4 | Diagrama de sequência UML (uso típico) | |
|---|---|---|
| Participantes | Sistemas, containers ou componentes do seu modelo C4 | Objetos, classes, muitas vezes no nível de método |
| O que uma seta significa | Um uso de um relacionamento do modelo estático, com seu protocolo | Uma mensagem ou chamada de método |
| Nível de detalhe | Arquitetural: "publica OrderPlaced (Kafka)" |
Muitas vezes de implementação: validate(), save(), valores de retorno |
| Notação | Caixas e setas numeradas, uma legenda explica o que for incomum | Linhas de vida, barras de ativação, fragmentos combinados (alt, loop, par) |
| Ligação com outros diagramas | Reutiliza elementos do diagrama de containers ou de componentes | Geralmente isolado |
Use o estilo colaboração quando a disposição espacial tem significado, por exemplo quando os leitores já conhecem o diagrama de containers e você quer que o fluxo apareça sobre ele. Use o estilo sequência quando a ordem é tudo o que importa, quando há mais de uns oito passos, ou quando há muito vai e vem entre dois elementos (requisição, resposta, callback). Nenhum é mais correto; o C4 deixa a escolha com você.
Se você precisa de fragmentos alt e loop para explicar um cenário, muitas vezes é sinal de que está descrevendo um algoritmo, não uma arquitetura. Desenhe a versão arquitetural como diagrama dinâmico e deixe a versão detalhada para um diagrama de sequência UML junto ao código, se alguém precisar dela. Nossa comparação entre C4 e UML mostra onde cada notação se encaixa.
Quando vale a pena desenhar um (e quando não)
A resposta do próprio C4 para "recomendado?" é não, e vale levar isso a sério. Cada diagrama dinâmico é mais um artefato que precisa mudar quando a arquitetura muda. Desenhe um quando o cenário atender a pelo menos um destes critérios:
- A ordem não é óbvia pelo diagrama estático. Checkout, captura de pagamento, uma saga que compensa em caso de falha. Se um engenheiro sênior da equipe erraria a ordem, desenhe.
- O cenário atravessa vários containers ou sistemas. Qualquer coisa que toque quatro ou mais containers, ou que saia do seu sistema e volte (webhooks, callbacks, redirecionamentos para terceiros como o 3-D Secure).
- É assíncrono. Quando uma fila entra em cena, o diagrama estático mostra que A e B tocam o Kafka, mas não que B roda depois de A, nem que A não espera por ele.
- Ele se repete. Um padrão usado em muitos lugares (como todo serviço autentica uma requisição, como toda escrita emite um evento) merece um diagrama para o qual o resto da documentação possa apontar.
- Alguém pede em uma revisão ou em um incidente. É o melhor gatilho que existe. Se uma revisão de incidente passou vinte minutos reconstruindo uma sequência em um quadro branco, essa sequência merece um diagrama.
Pule quando:
- O fluxo é uma linha reta. Navegador, API, banco de dados, volta. O diagrama de containers já diz isso.
- É CRUD. Cinco diagramas dinâmicos para create, read, update, delete e list não acrescentam nada.
- Ninguém vai ler. Um diagrama dinâmico para cada user story é um backlog de documentação, não documentação.
Uma meta razoável para um produto típico é um punhado: as duas ou três jornadas que geram receita ou que acordam as pessoas de madrugada, mais um ou dois padrões recorrentes.
Exemplo completo: "cliente faz um pedido"
Pegue o sistema de e-commerce do nosso guia completo. O diagrama de containers dele tem uma single-page app em React, um API gateway Kong, serviços em Go para pedidos, produtos e usuários (cada um com seu banco PostgreSQL), Kafka e um notification service. No nível 1, o sistema também conversa com a Stripe como gateway de pagamento e com o SendGrid para e-mail.
Estes são os relacionamentos do modelo estático que esse cenário usa. Cada passo abaixo precisa corresponder a um deles.
[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)
O diagrama dinâmico, estilo colaboração
As interações numeradas, desenhadas sobre as mesmas caixas:
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)
Dispostos sobre o diagrama de containers, os números contam a história: os passos 1 a 8 são síncronos e acontecem enquanto o cliente espera; os passos 9 e 10 acontecem depois, e o cliente nunca espera por eles.
O mesmo cenário, estilo sequência
| # | De | Para | O que acontece | Síncrono? |
|---|---|---|---|---|
| 1 | Customer | Single-Page Application | Clica em "Finalizar pedido" | sim |
| 2 | Single-Page Application | API Gateway | POST /orders |
sim |
| 3 | API Gateway | Order Service | Roteia a requisição | sim |
| 4 | Order Service | Product Service | Reserva o estoque | sim |
| 5 | Order Service | Payment Gateway (Stripe) | Autoriza o cartão | sim |
| 6 | Order Service | Order Database | Grava o pedido | sim |
| 7 | Order Service | Message Queue | Publica OrderPlaced |
não (fire and forget) |
| 8 | Order Service | Single-Page Application | Retorna 201 com o número do pedido | sim |
| 9 | Notification Service | Message Queue | Consome OrderPlaced |
assíncrono |
| 10 | Notification Service | Email Service (SendGrid) | Envia a confirmação | assíncrono |
Uma tabela como esta é uma forma perfeitamente válida de registrar um diagrama dinâmico. Desenhada com linhas de vida, ela vira o estilo sequência.
O que o diagrama revela
Lendo os dez passos, você consegue responder a perguntas que o diagrama de containers não respondia:
- O que acontece se a Stripe estiver fora do ar? O estoque já foi reservado no passo 4 quando a autorização falha no passo 5. Alguém precisa liberá-lo. O diagrama deixa óbvio que o order service precisa de um caminho de compensação, ou que os passos 4 e 5 deveriam trocar de lugar.
- O cliente pode receber uma confirmação de um pedido que não existe? Não. O evento é publicado no passo 7, depois da gravação no passo 6. Se estivessem invertidos, uma gravação com falha ainda poderia disparar um e-mail. (Se você precisa que a gravação e a publicação sejam atômicas, é aí que entra uma tabela outbox, e isso merece um ADR.)
- O que está no caminho crítico do cliente? Os passos 2 a 8. O e-mail não está, e é por isso que ele passa pelo Kafka.
Aqui está o mesmo cenário em Structurizr DSL, para equipes que mantêm o modelo como código. Ele só compila se cada relacionamento existir no modelo estático, que é a restrição descrita acima:
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
}
O passo 8, a resposta, não é um relacionamento separado no modelo estático, por isso fica de fora da versão em DSL. As respostas geralmente estão implícitas na requisição; desenhe-as só quando a própria resposta importa.
Erros comuns
Passos demais
Um diagrama dinâmico com trinta setas numeradas é uma sequência que ninguém consegue guardar na cabeça. Se um cenário passa de uns quinze passos, divida-o: "checkout, até o pagamento" e "checkout, depois do pagamento", ou um diagrama por sistema que o fluxo atravessa. Nossa própria documentação de fluxos sugere de 5 a 15 passos por fluxo pelo mesmo motivo.
Misturar níveis
O C4 deixa você escolher o nível (sistemas, containers ou componentes), mas escolha um por diagrama. Um diagrama em que o passo 3 vai para o container "Order Service" e o passo 4 vai para o componente PaymentClient dentro dele obriga o leitor a mudar de zoom no meio da história. Se um passo precisa de detalhe de componente, desenhe um segundo diagrama dinâmico restrito àquele container.
Setas que não existem no modelo estático
Se o diagrama dinâmico mostra o notification service chamando o order service diretamente, e o diagrama de containers não tem esse relacionamento, um dos dois está errado. Geralmente é o diagrama dinâmico, desenhado de memória. Trate o modelo estático como fonte da verdade e faça cada passo referenciar um dos seus relacionamentos.
Desenhar todas as chamadas
Health checks, renovação de tokens, envio de logs e coleta de métricas são reais, mas não são o cenário. Deixe de fora tudo o que apareceria em todos os diagramas dinâmicos que você desenhar. Se importa, ganha uma única vez seu próprio diagrama de padrão recorrente.
Esconder o assíncrono atrás de setas que parecem síncronas
Os passos 9 e 10 acima acontecem depois que o cliente já recebeu uma resposta. Se forem desenhados com as mesmas setas dos passos 1 a 8, os leitores vão supor que o e-mail é enviado antes de a página carregar. Marque os passos assíncronos (uma linha tracejada, um rótulo "async" ou uma numeração separada como 9a) e explique a convenção na legenda.
Deixar de fora a falha que importa
Um diagrama do caminho feliz é o padrão certo. Mas se o motivo de desenhar o fluxo é "o que acontece quando o pagamento falha", desenhe esse caminho, não o feliz.
Mantendo-o verdadeiro quando o modelo estático muda
Um diagrama dinâmico depende duas vezes do modelo estático: dos seus elementos e dos seus relacionamentos. Isso faz dele uma das primeiras coisas a ficar desatualizada. Alguém renomeia o order service para "checkout service", troca o Kafka pelo SQS ou move a reserva de estoque para um novo inventory service, e todo diagrama dinâmico que tocava essas caixas agora está errado. Nada avisa você.
Três hábitos ajudam:
- Desenhe a partir do modelo, não ao lado dele. Um diagrama dinâmico em uma ferramenta de desenho é uma cópia do diagrama de containers, e cópias divergem. Uma view dinâmica que referencia os elementos do modelo por identificador (como faz a Structurizr DSL) pelo menos acompanha as renomeações, e falha de forma visível quando um relacionamento desaparece.
- Mantenha a lista curta. Cinco diagramas dinâmicos que você confere a cada trimestre valem mais do que trinta que você nunca abre.
- Revise-os quando os containers que eles tocam mudarem. Quando um pull request altera um container ou um relacionamento, os diagramas dinâmicos que o usam fazem parte da revisão.
Como os fluxos funcionam no archyl
No archyl, um diagrama dinâmico é um Flow (fluxo): uma lista ordenada de passos, cada um com um elemento de origem, um elemento de destino, um relacionamento e uma descrição, reproduzida passo a passo sobre o diagrama (documentação de fluxos). Você pode montar um à mão escolhendo relacionamentos do seu modelo, ou descrever o cenário e deixar o AI flow generator rascunhar os passos a partir do seu modelo C4. O gerador valida cada passo contra o modelo antes de salvá-lo: a origem e o destino de cada passo precisam existir, e o relacionamento citado precisa ligar esses dois elementos. Um passo que não corresponde é descartado em vez de desenhado.
Dois limites, ditos com clareza porque são exatamente o problema de que esta seção trata:
- Um flow guarda um snapshot dos elementos e relacionamentos que usa, tirado quando um passo é adicionado. Isso mantém o flow legível mesmo que um elemento seja excluído depois, mas também significa que renomear um container no modelo não o renomeia nos flows existentes. Quando o modelo mudar, abra os flows que o tocam e confira.
- O drift score não verifica comportamento. O drift score do archyl diz se os elementos documentados ainda existem no código. Se uma chamada síncrona entre dois serviços vira uma mensagem em fila e nada é renomeado ou movido, a pontuação não muda, e o flow também não.
Para mais sobre o lado prático, incluindo como escrevemos fluxos como documentos com pré-condições e tratamento de erros, veja documentando fluxos de usuário.
FAQ
O diagrama dinâmico faz parte do modelo C4?
Sim, como diagrama suplementar. Os quatro níveis principais são System Context, Container, Component e Code. O modelo C4 acrescenta três diagramas suplementares: system landscape, dinâmico e de deployment. O diagrama dinâmico reutiliza elementos dos níveis principais e mostra como eles interagem em um cenário.
Qual é a diferença entre um diagrama dinâmico C4 e um diagrama de sequência?
Um diagrama dinâmico C4 pode ser desenhado no estilo colaboração (disposição livre, setas numeradas) ou no estilo sequência (linhas de vida, tempo correndo para baixo). O estilo sequência parece um diagrama de sequência UML, mas seus participantes são sistemas, containers ou componentes C4, e cada seta é um uso de um relacionamento do modelo estático, não uma chamada de método.
Qual nível um diagrama dinâmico deve usar?
O nível que responde à pergunta, e apenas um por diagrama. O nível de container é o mais comum, porque a maioria dos cenários que vale a pena desenhar atravessa várias unidades implantáveis. Use o nível de sistema para fluxos entre sistemas e o nível de componente para explicar o interior de um container.
Quantos passos um diagrama dinâmico deve ter?
Não existe um limite oficial. Depois de uns quinze passos, a maioria dos leitores se perde, então divida o cenário em partes ou desenhe um diagrama por sistema que ele atravessa.
Um diagrama dinâmico C4 pode mostrar mensageria assíncrona?
Sim. Mostre a publicação e o consumo como passos numerados separados, e deixe visível por quais passos quem chama espera e por quais não: uma linha tracejada, um rótulo "async" ou um esquema de numeração separado, explicado na legenda do diagrama.
O archyl suporta diagramas dinâmicos C4?
Sim, como Flows. Cada passo referencia um elemento de origem, um elemento de destino e um relacionamento do seu modelo, e o flow é reproduzido passo a passo sobre o diagrama. Você pode escrever flows à mão ou gerar um rascunho a partir de uma descrição em texto. Os flows guardam um snapshot dos elementos que usam, então revise-os quando os containers que eles tocam mudarem.
Quer desenhar seu primeiro flow sobre um modelo que já existe? Experimente o archyl grátis e gere primeiro o modelo C4 a partir do seu código. Continue lendo: O que é o modelo C4? Um guia completo | Guia do diagrama de containers C4 | Documentando fluxos de usuário | Documentação de fluxos.