Diagrama dinámico C4: guía con ejemplos
Un diagrama de contenedores te dice que la API habla con el order service, que el order service habla con Kafka y que el notification service lee de Kafka. No te dice qué pasa, ni en qué orden, cuando un cliente hace clic en Realizar pedido. ¿Se cobra el pago antes o después de escribir la fila del pedido? ¿El email de confirmación espera al almacén? Esas son las preguntas que surgen en una revisión de incidentes, y los diagramas estáticos no pueden responderlas.
Esa es la función del diagrama dinámico C4. Toma elementos que ya has dibujado y numera las interacciones entre ellos para un escenario concreto. Esta guía explica qué es un diagrama dinámico, en qué se diferencia de un diagrama de secuencia UML, cuándo merece la pena dibujar uno (menos a menudo de lo que crees), un ejemplo completo, los errores habituales y cómo evitar que quede obsoleto cuando cambia el modelo estático.
Si C4 es nuevo para ti, empieza por qué es el modelo C4. El ejemplo de abajo parte del tipo de diagrama que se explica en la guía del diagrama de contenedores.
Qué es un diagrama dinámico
El diagrama dinámico es uno de los diagramas complementarios del modelo C4, junto con el diagrama de paisaje de sistemas y el de despliegue. No es uno de los cuatro niveles principales. Se sitúa junto a ellos y toma prestados sus elementos.
La definición de c4model.com es breve:
- Alcance: «Una funcionalidad, historia, caso de uso, etc. concretos.»
- Elementos: «A tu elección: puedes mostrar sistemas de software, contenedores o componentes que interactúan en tiempo de ejecución.»
- Público: «Personas técnicas y no técnicas, dentro y fuera del equipo de desarrollo de software.»
- ¿Recomendado? «No, los diagramas dinámicos deberían usarse con moderación para mostrar patrones interesantes o recurrentes, o funcionalidades que requieren un conjunto complicado de interacciones.»
De esa definición se desprenden dos cosas.
Primero, un diagrama dinámico muestra instancias de relaciones que ya tienes. Si el diagrama de contenedores tiene una flecha del order service a Kafka, el diagrama dinámico dice: «y en el paso 4 del checkout, esa flecha se usa para publicar OrderPlaced». El DSL de Structurizr lo hace explícito: su documentación dice que con una vista dinámica «estás mostrando instancias de relaciones definidas en el modelo estático», y la relación tiene que existir allí primero (Structurizr DSL reference). Esa restricción es útil. Impide que el diagrama dinámico se invente una llamada que el modelo estático no conoce.
Segundo, es un escenario por diagrama. No «cómo funciona el order service», sino «el cliente realiza un pedido, pago con tarjeta, artículo en stock». El camino de fallo tiene su propio diagrama, si es que merece la pena dibujarlo.
El orden se muestra con números en las flechas. Esa es toda la notación: las mismas cajas, las mismas flechas, más un número de secuencia y una descripción de lo que ocurre en ese paso.
Diagrama dinámico vs diagrama de secuencia
«Diagrama de secuencia C4» es una búsqueda habitual, y la confusión es comprensible: los dos diagramas responden a la misma pregunta. El sitio de C4 dice que el diagrama dinámico puede dibujarse en dos estilos que contienen la misma información:
- Estilo colaboración. Cajas colocadas libremente (normalmente donde están en el diagrama de contenedores) con flechas numeradas entre ellas. C4 señala que este estilo se basa en el diagrama de comunicación de UML, antes llamado diagrama de colaboración.
- Estilo secuencia. Los elementos como columnas en la parte superior, el tiempo avanzando hacia abajo y flechas entre líneas de vida. Parece un diagrama de secuencia UML, pero los participantes son elementos C4.
Así que un diagrama dinámico en estilo secuencia es un tipo de diagrama de secuencia. Las diferencias reales son con un diagrama de secuencia UML clásico, dibujado a partir del código:
| Diagrama dinámico C4 | Diagrama de secuencia UML (uso típico) | |
|---|---|---|
| Participantes | Sistemas, contenedores o componentes de tu modelo C4 | Objetos, clases, a menudo a nivel de método |
| Qué significa una flecha | Un uso de una relación del modelo estático, con su protocolo | Un mensaje o una llamada a un método |
| Nivel de detalle | Arquitectónico: «publica OrderPlaced (Kafka)» |
A menudo de implementación: validate(), save(), valores de retorno |
| Notación | Cajas y flechas numeradas, una leyenda explica lo inusual | Líneas de vida, barras de activación, fragmentos combinados (alt, loop, par) |
| Conexión con otros diagramas | Reutiliza elementos del diagrama de contenedores o de componentes | Normalmente independiente |
Usa el estilo colaboración cuando la disposición espacial aporta significado, por ejemplo cuando los lectores ya conocen el diagrama de contenedores y quieres que el flujo aparezca sobre él. Usa el estilo secuencia cuando el orden es lo esencial, hay más de unos ocho pasos o hay mucho ir y venir entre dos elementos (petición, respuesta, callback). Ninguno es más correcto; C4 te deja elegir.
Si necesitas fragmentos alt y loop para explicar un escenario, suele ser señal de que estás describiendo un algoritmo en lugar de una arquitectura. Dibuja la versión arquitectónica como diagrama dinámico y deja la versión detallada para un diagrama de secuencia UML junto al código, si alguien lo necesita. Nuestra comparación C4 vs UML explica dónde encaja cada notación.
Cuándo merece la pena dibujar uno (y cuándo no)
La propia respuesta de C4 a «¿recomendado?» es no, y conviene tomarla en serio. Cada diagrama dinámico es un artefacto más que tiene que cambiar cuando cambia la arquitectura. Dibuja uno cuando el escenario cumpla al menos una de estas condiciones:
- El orden no es obvio a partir del diagrama estático. Checkout, captura de pago, una saga que compensa cuando algo falla. Si un ingeniero senior del equipo se equivocaría con el orden, dibújalo.
- El escenario cruza varios contenedores o sistemas. Todo lo que toca cuatro contenedores o más, o sale de tu sistema y vuelve (webhooks, callbacks, redirecciones a terceros como 3-D Secure).
- Es asíncrono. En cuanto interviene una cola, el diagrama estático muestra que A y B usan Kafka, pero no que B se ejecuta después de A, ni que A no lo espera.
- Es recurrente. Un patrón que se usa en muchos sitios (cómo cada servicio autentica una petición, cómo cada escritura emite un evento) merece un diagrama al que pueda apuntar el resto de la documentación.
- Alguien lo pide en una revisión o en un incidente. Es el mejor detonante. Si una revisión de incidente pasó veinte minutos reconstruyendo una secuencia en una pizarra, esa secuencia merece un diagrama.
No lo dibujes cuando:
- El flujo es una línea recta. Navegador, API, base de datos y vuelta. El diagrama de contenedores ya lo dice.
- Es CRUD. Cinco diagramas dinámicos para create, read, update, delete y list no aportan nada.
- Nadie lo va a leer. Un diagrama dinámico por cada historia de usuario es un backlog de documentación, no documentación.
Un objetivo razonable para un producto típico es un puñado: los dos o tres recorridos que generan dinero o despiertan a la gente de madrugada, más uno o dos patrones recurrentes.
Ejemplo completo: «el cliente realiza un pedido»
Tomemos el sistema de e-commerce de nuestra guía completa. Su diagrama de contenedores tiene una single-page app en React, un API gateway Kong, servicios en Go para pedidos, productos y usuarios (cada uno con su propia base de datos PostgreSQL), Kafka y un notification service. En el nivel 1, el sistema también habla con Stripe como pasarela de pago y con SendGrid para el email.
Estas son las relaciones del modelo estático que usa este escenario. Cada paso de abajo debe corresponder a una de ellas.
[Cliente] --> [Single-Page Application (React)] : Usa (HTTPS)
[Single-Page Application] --> [API Gateway (Kong)] : Llama a la API (HTTPS/JSON)
[API Gateway] --> [Order Service (Go)] : Enruta las peticiones
[Order Service] --> [Product Service (Go)] : Comprueba el stock (gRPC)
[Order Service] --> [Payment Gateway (Stripe)] : Autoriza pagos (HTTPS/REST)
[Order Service] --> [Order Database (PostgreSQL)] : Lee/escribe pedidos (SQL)
[Order Service] --> [Message Queue (Kafka)] : Publica eventos de pedido
[Notification Service (Go)] --> [Message Queue] : Consume eventos de pedido
[Notification Service] --> [Email Service (SendGrid)] : Envía emails (HTTPS)
El diagrama dinámico, estilo colaboración
Las interacciones numeradas, dibujadas sobre esas mismas cajas:
1. [Cliente] -> [Single-Page Application] : Hace clic en "Realizar pedido"
2. [Single-Page Application] -> [API Gateway] : POST /orders (HTTPS/JSON)
3. [API Gateway] -> [Order Service] : Enruta la petición autenticada
4. [Order Service] -> [Product Service] : Reserva stock para cada línea (gRPC)
5. [Order Service] -> [Payment Gateway (Stripe)] : Autoriza la tarjeta por el total del pedido (HTTPS)
6. [Order Service] -> [Order Database] : Escribe el pedido con estado "placed" (SQL)
7. [Order Service] -> [Message Queue] : Publica OrderPlaced (Kafka)
8. [Order Service] -> [Single-Page Application] : Devuelve 201 con el número de pedido (a través del gateway)
9. [Notification Service] -> [Message Queue] : Consume OrderPlaced (Kafka)
10. [Notification Service] -> [Email Service (SendGrid)] : Envía el email de confirmación (HTTPS)
Colocados sobre el diagrama de contenedores, los números cuentan la historia: los pasos 1 a 8 son síncronos y ocurren mientras el cliente espera; los pasos 9 y 10 ocurren después y el cliente nunca los espera.
El mismo escenario, estilo secuencia
| N.º | Desde | Hacia | Qué ocurre | ¿Síncrono? |
|---|---|---|---|---|
| 1 | Cliente | Single-Page Application | Hace clic en «Realizar pedido» | sí |
| 2 | Single-Page Application | API Gateway | POST /orders |
sí |
| 3 | API Gateway | Order Service | Enruta la petición | sí |
| 4 | Order Service | Product Service | Reserva stock | sí |
| 5 | Order Service | Payment Gateway (Stripe) | Autoriza la tarjeta | sí |
| 6 | Order Service | Order Database | Escribe el pedido | sí |
| 7 | Order Service | Message Queue | Publica OrderPlaced |
no (fire and forget) |
| 8 | Order Service | Single-Page Application | Devuelve 201 con el número de pedido | sí |
| 9 | Notification Service | Message Queue | Consume OrderPlaced |
asíncrono |
| 10 | Notification Service | Email Service (SendGrid) | Envía la confirmación | asíncrono |
Una tabla así es una forma perfectamente válida de escribir un diagrama dinámico. Dibujada con líneas de vida, es el estilo secuencia.
Lo que te dice el diagrama
Al leer los diez pasos, puedes responder preguntas que el diagrama de contenedores no podía:
- ¿Qué pasa si Stripe está caído? El stock ya está reservado en el paso 4 cuando la autorización falla en el paso 5. Alguien tiene que liberarlo. El diagrama deja claro que el order service necesita un camino de compensación, o que los pasos 4 y 5 deberían intercambiarse.
- ¿Puede el cliente recibir una confirmación de un pedido que no existe? No. El evento se publica en el paso 7, después de la escritura del paso 6. Si fuera al revés, una escritura fallida podría enviar igualmente un email. (Si necesitas que la escritura y la publicación sean atómicas, ahí entra una tabla outbox, y merece un ADR.)
- ¿Qué está en el camino crítico del cliente? Los pasos 2 a 8. El email no, por eso pasa por Kafka.
Aquí está el mismo escenario en el DSL de Structurizr, para los equipos que mantienen su modelo como código. Solo compila si cada relación existe en el modelo estático, que es la restricción descrita arriba:
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
}
El paso 8, la respuesta, no es una relación independiente en el modelo estático, así que queda fuera de la versión DSL. Las respuestas suelen darse por implícitas en la petición; dibújalas solo cuando la propia respuesta importa.
Errores habituales
Demasiados pasos
Un diagrama dinámico con treinta flechas numeradas es una secuencia que nadie puede retener. Si un escenario supera unos quince pasos, divídelo: «checkout, hasta el pago» y «checkout, después del pago», o un diagrama por cada sistema que cruza el flujo. Nuestra propia documentación de flows sugiere entre 5 y 15 pasos por flow por la misma razón.
Mezclar niveles
C4 te deja elegir el nivel (sistemas, contenedores o componentes), pero elige uno por diagrama. Un diagrama en el que el paso 3 va al contenedor «Order Service» y el paso 4 al componente PaymentClient que hay dentro obliga al lector a cambiar de zoom a mitad de la historia. Si un paso necesita detalle de componentes, dibuja un segundo diagrama dinámico limitado a ese contenedor.
Flechas que no existen en el modelo estático
Si el diagrama dinámico muestra al notification service llamando directamente al order service, y el diagrama de contenedores no tiene esa relación, uno de los dos está mal. Normalmente es el diagrama dinámico, dibujado de memoria. Trata el modelo estático como la fuente de verdad y haz que cada paso haga referencia a una de sus relaciones.
Dibujar todas las llamadas
Health checks, renovaciones de tokens, envío de logs y recogida de métricas son reales, pero no son el escenario. Deja fuera todo lo que aparecería en cualquier diagrama dinámico que dibujes. Si importa, tendrá una vez su propio diagrama de patrón recurrente.
Ocultar lo asíncrono tras flechas que parecen síncronas
Los pasos 9 y 10 de arriba ocurren cuando el cliente ya tiene una respuesta. Si se dibujan con las mismas flechas que los pasos 1 a 8, los lectores suponen que el email se envía antes de que cargue la página. Marca los pasos asíncronos (línea discontinua, una etiqueta «async» o una numeración separada como 9a) y explica la convención en la leyenda.
Omitir el fallo que importa
Un diagrama del camino feliz es la opción por defecto correcta. Pero si la razón por la que dibujas el flujo es «qué pasa cuando falla el pago», dibuja ese camino, no el feliz.
Mantenerlo fiel cuando cambia el modelo estático
Un diagrama dinámico depende dos veces del modelo estático: de sus elementos y de sus relaciones. Eso lo convierte en una de las primeras cosas en quedar obsoletas. Alguien renombra el order service a «checkout service», sustituye Kafka por SQS o traslada la reserva de stock a un nuevo servicio de inventario, y todos los diagramas dinámicos que tocaban esas cajas quedan mal. Nada te avisa.
Tres hábitos ayudan:
- Dibuja desde el modelo, no al lado. Un diagrama dinámico en una herramienta de dibujo es una copia del diagrama de contenedores, y las copias se desvían. Una vista dinámica que referencia los elementos del modelo por identificador (el DSL de Structurizr lo hace) al menos recoge los cambios de nombre, y falla de forma visible cuando desaparece una relación.
- Mantén la lista corta. Cinco diagramas dinámicos que revisas cada trimestre son mejores que treinta que nunca abres.
- Revísalos cuando cambien los contenedores que tocan. Cuando un pull request cambia un contenedor o una relación, los diagramas dinámicos que lo usan forman parte de la revisión.
Cómo funcionan los flows en archyl
En archyl, un diagrama dinámico es un Flow: una lista ordenada de pasos, cada uno con un elemento de origen, un elemento de destino, una relación y una descripción, que se reproducen paso a paso sobre el diagrama (documentación de flows). Puedes crear uno a mano eligiendo relaciones de tu modelo, o describir el escenario y dejar que el generador de flows con IA redacte los pasos a partir de tu modelo C4. El generador valida cada paso contra el modelo antes de guardarlo: el origen y el destino de cada paso deben existir, y la relación que cita debe conectar esos dos elementos. Un paso que no encaja se descarta en lugar de dibujarse.
Dos límites, dichos con claridad porque son justo el problema del que trata esta sección:
- Un flow guarda una instantánea de los elementos y relaciones que usa, tomada al añadir cada paso. Así un flow sigue siendo legible aunque un elemento se elimine más tarde, pero también significa que renombrar un contenedor en el modelo no lo renombra en los flows existentes. Cuando cambie el modelo, abre los flows que lo tocan y revísalos.
- La puntuación de deriva no comprueba el comportamiento. La puntuación de deriva de archyl te dice si los elementos documentados siguen existiendo en el código. Si una llamada síncrona entre dos servicios pasa a ser un mensaje en una cola y no se renombra ni se mueve nada, la puntuación no cambia, y el flow tampoco.
Para la parte práctica, incluido cómo escribimos los flows como documentos con precondiciones y gestión de errores, consulta documentar flujos de usuario.
FAQ
¿El diagrama dinámico forma parte del modelo C4?
Sí, como diagrama complementario. Los cuatro niveles principales son System Context, Container, Component y Code. El modelo C4 añade tres diagramas complementarios: paisaje de sistemas, dinámico y despliegue. El diagrama dinámico reutiliza elementos de los niveles principales y muestra cómo interactúan en un escenario.
¿Qué diferencia hay entre un diagrama dinámico C4 y un diagrama de secuencia?
Un diagrama dinámico C4 puede dibujarse en estilo colaboración (disposición libre, flechas numeradas) o en estilo secuencia (líneas de vida, el tiempo avanza hacia abajo). El estilo secuencia se parece a un diagrama de secuencia UML, pero sus participantes son sistemas, contenedores o componentes C4, y cada flecha es un uso de una relación del modelo estático, no una llamada a un método.
¿Qué nivel debe usar un diagrama dinámico?
El que responda a la pregunta, y solo uno por diagrama. El nivel de contenedor es el más común, porque la mayoría de los escenarios que merece la pena dibujar cruzan varias unidades desplegables. Usa el nivel de sistema para flujos entre sistemas y el nivel de componente para explicar el interior de un contenedor.
¿Cuántos pasos debe tener un diagrama dinámico?
No hay un límite oficial. A partir de unos quince pasos, la mayoría de los lectores se pierden, así que divide el escenario en partes o dibuja un diagrama por cada sistema que cruza.
¿Puede un diagrama dinámico C4 mostrar mensajería asíncrona?
Sí. Muestra la publicación y el consumo como pasos numerados separados, y deja claro qué pasos espera quien llama y cuáles no: una línea discontinua, una etiqueta «async» o un esquema de numeración separado, explicado en la leyenda.
¿archyl admite diagramas dinámicos C4?
Sí, como Flows. Cada paso referencia un elemento de origen, un elemento de destino y una relación de tu modelo, y el flow se reproduce paso a paso sobre el diagrama. Puedes crear flows a mano o generar un borrador a partir de una descripción en texto. Los flows guardan una instantánea de los elementos que usan, así que revísalos cuando cambien los contenedores que tocan.
¿Quieres dibujar tu primer flow sobre un modelo que ya existe? Prueba archyl gratis y genera primero el modelo C4 a partir de tu código. Sigue leyendo: ¿Qué es el modelo C4? Guía completa | Guía del diagrama de contenedores C4 | Documentar flujos de usuario | Documentación de flows.