Event-Driven Architecture Documentation with C4 Model
Event-driven architecture has become the default for building scalable, loosely coupled systems. Services publish events when something happens. Other services subscribe to those events and react. The publisher doesn't know who's listening. The subscriber doesn't know who published. The system is decoupled, resilient, and flexible.
It's also nearly invisible.
When you look at a traditional C4 diagram of an event-driven system, you see services with arrows pointing to a message broker. Service A publishes to Kafka. Service B consumes from Kafka. But which events flow between them? What's the schema? Who else is listening? What happens if a consumer fails? The diagram shows the plumbing but hides the behavior.
Documenting event-driven architecture requires techniques that make the invisible visible -- that show not just the infrastructure (brokers, queues, topics) but the events themselves, their flows, their schemas, and their guarantees. This guide covers how to do it using the C4 model and how Archyl's event channel features make event-driven systems first-class citizens in your architecture documentation.
Why Event-Driven Systems Are Hard to Document
Event-driven architecture introduces several documentation challenges that don't exist in synchronous, request-response systems.
Invisible Control Flow
In a synchronous system, you can trace a request from client to server by following the call chain. Service A calls Service B, which calls Service C. The control flow is explicit and visible in the code.
In an event-driven system, the control flow is implicit. Service A publishes an OrderCreated event. Somewhere, Service B reacts to that event by reserving inventory. Somewhere else, Service C reacts by sending a confirmation email. Service A doesn't know about B or C. The control flow is defined by the event subscriptions, not by the code of the publisher.
This indirection means you can't understand the system's behavior by reading a single service's code. You need a higher-level view that shows the event flows across services -- and that view is exactly what architecture documentation should provide.
Many-to-Many Relationships
In synchronous architectures, relationships are typically one-to-one or one-to-few. Service A calls Service B. The relationship is direct and documented by the API call.
In event-driven architectures, relationships are many-to-many. A single event type might have one producer and five consumers. A single service might consume events from ten different producers. The relationship graph is denser and more complex than in synchronous systems.
Traditional architecture diagrams struggle with this density. Drawing an arrow from every producer to every consumer through every topic creates a diagram that looks like a plate of spaghetti. You need a documentation approach that shows event flows at the right level of abstraction.
Schema Evolution
Event schemas evolve over time. The OrderCreated event might start with five fields and grow to fifteen over two years. Consumers might depend on specific fields. Schema changes can break consumers if they're not backward-compatible.
Documenting the current schema is necessary but not sufficient. You also need to document the schema versioning strategy, compatibility guarantees, and the history of breaking changes.
Eventual Consistency
Event-driven systems are eventually consistent by nature. When Service A publishes an event, Service B might process it milliseconds later or minutes later (if the consumer is behind or if retries are needed). The system is in an inconsistent state during that window.
Documentation should capture these consistency boundaries. Which parts of the system are strongly consistent? Which are eventually consistent? What's the expected propagation delay? What happens during the inconsistency window?
Dead Letter Queues and Error Handling
When an event consumer fails to process a message, the event typically goes to a dead letter queue (DLQ). But what happens then? Who monitors the DLQ? What's the retry strategy? How are poison messages handled?
These error handling patterns are critical to the system's behavior but are rarely documented. They're part of the architecture, and they should be visible in the documentation.
Modeling Event-Driven Systems with C4
The C4 model can represent event-driven architectures effectively with a few adaptations to how you use each level.
System Context: Focus on Data Flows, Not Calls
At the System Context level, event-driven and synchronous architectures look similar. Your system interacts with users and external systems. The key difference is in how you label the relationships.
Instead of "calls" or "queries," use labels that describe data flow:
- "Sends order events to"
- "Receives payment confirmations from"
- "Publishes analytics events to"
These labels hint at the asynchronous nature of the communication without cluttering the high-level view with implementation details.
Container Diagram: Make the Broker Visible
The Container diagram is where event-driven architecture becomes distinct. The message broker (Kafka, RabbitMQ, Amazon SQS/SNS, Google Pub/Sub) should be a first-class container in your diagram, not an invisible implementation detail.
Here's how a Container diagram for an event-driven e-commerce system might look:
systems:
- name: E-Commerce Platform
type: software_system
containers:
- name: Order Service
type: service
technologies: [Go, PostgreSQL]
- name: Inventory Service
type: service
technologies: [Java, PostgreSQL]
- name: Notification Service
type: service
technologies: [Python, Redis]
- name: Analytics Service
type: service
technologies: [Python, ClickHouse]
- name: Event Bus
type: queue
technologies: [Apache Kafka]
relationships:
- from: Order Service
to: Event Bus
label: "Publishes OrderCreated, OrderCancelled"
- from: Event Bus
to: Inventory Service
label: "Delivers order events"
- from: Event Bus
to: Notification Service
label: "Delivers order and payment events"
- from: Event Bus
to: Analytics Service
label: "Delivers all domain events"
- from: Inventory Service
to: Event Bus
label: "Publishes InventoryReserved, InventoryReleased"
Notice how the Event Bus is at the center of the diagram, and the relationships explicitly name the event types that flow through it. This makes the event flows visible without creating direct arrows between every producer and every consumer.
Event Channels: A First-Class Concept
Individual topics, queues, and streams within the message broker deserve their own documentation. Each event channel has properties that matter for understanding the system:
- Channel name: The Kafka topic, RabbitMQ queue, or SQS queue name
- Event types: What events flow through this channel
- Producers: Which services publish to this channel
- Consumers: Which services consume from this channel
- Serialization: How events are encoded (JSON, Avro, Protobuf)
- Partitioning strategy: How events are distributed across partitions
- Retention policy: How long events are kept
- Ordering guarantees: Whether order is preserved and at what granularity
In Archyl, event channels are a dedicated entity type. You create an event channel, specify its properties, and link it to the services that produce and consume from it. This creates a structured, queryable model of your event flows.
For example, an "orders" event channel might be documented as:
- Name: orders
- Broker: Kafka
- Producers: Order Service
- Consumers: Inventory Service, Notification Service, Analytics Service, Billing Service
- Event Types: OrderCreated, OrderUpdated, OrderCancelled, OrderCompleted
- Serialization: Avro with Schema Registry
- Partitioning: By order ID
- Retention: 7 days
This level of detail makes the invisible infrastructure visible and queryable. When a developer needs to know who's consuming order events, the answer is documented and findable.
Component Diagram: Event Handlers and Publishers
At the Component level, event-driven services have distinctive internal structures worth documenting for complex services:
- Event Handlers: Components that consume and process specific event types
- Event Publishers: Components that produce events
- Sagas / Process Managers: Components that orchestrate multi-step workflows through events
- Projections: Components that build read models from event streams
A Component diagram for the Order Service might include:
- Order Controller -- handles HTTP requests for order management
- Order Processor -- core business logic for order creation and validation
- Event Publisher -- publishes OrderCreated, OrderUpdated, OrderCancelled to Kafka
- Payment Event Handler -- consumes PaymentProcessed and PaymentFailed events
- Order Saga -- manages the order fulfillment workflow across services
Document these components when the service is complex enough to warrant it -- particularly for services that participate in choreography or orchestration patterns.
Documenting Common Event-Driven Patterns
Certain patterns appear repeatedly in event-driven systems. Documenting them explicitly saves teams from having to reverse-engineer the patterns from code.
Event Sourcing
In event-sourced systems, the state of an entity is derived from a sequence of events rather than stored as a snapshot. The event stream is the source of truth, and the current state is a projection.
Document event sourcing by:
- Identifying which entities are event-sourced
- Listing the event types in each entity's event stream
- Documenting the projections that derive read models from the streams
- Noting the snapshotting strategy (if any)
CQRS (Command Query Responsibility Segregation)
CQRS separates write operations (commands) from read operations (queries), often using events to keep the read model synchronized with the write model.
Document CQRS by:
- Clearly separating command-side containers from query-side containers in the C4 model
- Documenting the event flow from the write side to the read side
- Noting the consistency model (how far behind the read model can be)
Choreography vs. Orchestration
In choreography, services react to events independently. No central coordinator exists. In orchestration, a central service (the orchestrator or saga) coordinates the workflow by sending commands and listening for responses.
Document which pattern your system uses for each workflow. If you use choreography, document the expected sequence of events and the services that participate. If you use orchestration, document the saga's state machine and the commands it issues.
Dead Letter Queues and Retry Patterns
Document your error handling strategy for event processing:
- Which events have dead letter queues?
- What's the retry policy (count, backoff strategy)?
- Who is responsible for monitoring and reprocessing DLQ events?
- What alerting is in place for DLQ accumulation?
In Archyl, you can model DLQs as additional event channels linked to the primary channels. This makes the error handling infrastructure visible in the architecture documentation.
Documenting Event Schemas
Event schemas are contracts between producers and consumers. They deserve the same level of documentation as API contracts in synchronous systems.
Schema Documentation
For each event type, document:
- Event name: A clear, domain-specific name (OrderCreated, not GenericEvent)
- Schema version: The current version of the schema
- Fields: All fields with their types, descriptions, and whether they're required or optional
- Example payload: A representative JSON/Avro/Protobuf example
- Compatibility: Whether the schema is forward-compatible, backward-compatible, or fully compatible
Archyl's API Contract feature can be used to document event schemas alongside REST and gRPC specifications. Link the contract to the event channel to create a direct connection between the schema and the infrastructure.
Schema Evolution Strategy
Document your team's approach to schema evolution:
- Do you use a schema registry (Confluent Schema Registry, AWS Glue)?
- What compatibility mode is enforced (backward, forward, full)?
- How are breaking changes communicated to consumers?
- What's the deprecation process for old schema versions?
This information belongs in an ADR linked to your event infrastructure. It's a decision that affects the entire system and should be documented once, clearly, and referenced by all teams.
Visualizing Event Flows
Static diagrams can show event infrastructure, but they struggle to show event flows -- the sequence of events that implement a business process.
Use Flows for Business Processes
Archyl's Flow feature lets you document the sequence of events that implement a business process. For example, an "Order Placement" flow might show:
- Customer submits order via Web App
- API Gateway forwards request to Order Service
- Order Service validates and persists the order
- Order Service publishes OrderCreated event to Kafka
- Inventory Service consumes OrderCreated, reserves inventory
- Inventory Service publishes InventoryReserved event
- Payment Service consumes InventoryReserved, processes payment
- Payment Service publishes PaymentProcessed event
- Notification Service consumes PaymentProcessed, sends confirmation email
This flow shows the end-to-end behavior that emerges from the event-driven architecture. No single service's code reveals this flow -- it only exists at the architecture level.
Use Overlays for Different Views
Create overlays to show different aspects of your event-driven architecture:
- Event Flow Overlay: Highlights only the event-related relationships, hiding synchronous communication
- Producer/Consumer Overlay: Color-codes services based on whether they produce, consume, or both
- Error Handling Overlay: Shows DLQs, retry paths, and monitoring
Overlays let you create focused views from a single architecture model, avoiding the need for multiple redundant diagrams.
Best Practices
Name Events After Domain Actions, Not Technical Operations
Use names like OrderCreated, PaymentFailed, InventoryReserved -- not DataUpdated, MessageSent, or RecordInserted. Domain-specific names make the event flows readable at the architecture level.
Document the "Why Not Sync" Decision
For each event-driven interaction, there was a decision to use async instead of sync communication. Document that decision. Why does the Order Service publish an event instead of calling the Inventory Service directly? The answer (decoupling, resilience, scalability) should be captured in an ADR.
Keep Event Channel Documentation Close to the Code
If your event schemas are defined in code (Protobuf files, Avro schemas, JSON Schema), link the architecture documentation to those files. This creates a connection between the abstract documentation and the concrete implementation.
Review Event Flows During Architecture Reviews
During quarterly architecture reviews, walk through your documented event flows. Ask:
- Are there new event types that aren't documented?
- Are there documented event types that are no longer used?
- Have any new consumers been added without updating the documentation?
- Are the documented schemas still accurate?
Archyl's drift detection helps answer these questions automatically, but periodic human review catches things that automated checks miss.
Conclusion
Event-driven architecture documentation requires intentional effort to make the invisible visible. Events, by design, decouple producers from consumers. This is an architectural strength but a documentation challenge.
The C4 model provides the framework: System Context for the big picture, Container diagrams with the message broker as a first-class element, event channels for detailed topic and queue documentation, and Component diagrams for complex event handlers and sagas.
Archyl provides the tooling: event channels as first-class entities, flows for documenting event-driven business processes, API contracts for event schemas, overlays for focused views, and drift detection to catch undocumented changes.
Document your event-driven architecture the same way you design it: with intention, structure, and the understanding that the system's behavior emerges from the interactions between services, not from any single service alone.
Get started with Archyl and make your event-driven architecture visible, documented, and understood.