Visual Paradigm Desktop VP Online

From Structure to Story: How Sequence Diagrams Bring the C4 Model to Life

Introduction

In today’s complex software development landscape, architecture documentation serves as the critical bridge between conceptual design and practical implementation. While the C4 model has gained widespread adoption for its clear, hierarchical approach to visualizing system structure, many teams struggle to capture the dynamic, time-based interactions that bring these static diagrams to life. This case study explores how supplementing C4 model diagrams with sequence diagrams creates a comprehensive documentation strategy that answers both “what” comprises a system and “how” its components collaborate to deliver value.

Through a real-world scenario involving a payment processing system for an e-commerce platform, we demonstrate practical techniques for aligning sequence diagrams with C4 abstraction levels, maintaining visual consistency, and knowing when dynamic views add the most value. This integrated approach enables development teams, stakeholders, and new team members to understand architecture at the appropriate level of detail while preserving the agility needed for iterative development.


Case Study: Payment Processing System for “ShopFlow” E-Commerce Platform

Project Context

ShopFlow, a mid-sized e-commerce company, was modernizing its legacy payment processing system. The architecture team adopted the C4 model to document the new microservices-based design but quickly identified a gap: stakeholders struggled to understand complex transaction flows, error handling scenarios, and cross-service communication patterns using static diagrams alone.

Solution Approach: Layered Dynamic Views

Level 1: System Context Sequence – High-Level User Journey

Focus: External actors and system boundaries

The team created a sequence diagram showing the end-to-end customer checkout experience. First, here’s the corresponding C4 System Context diagram, and in PlantUML code:

PlantUML Diagram

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml

Person(customer, "Customer", "Shops on ShopFlow")
System_Boundary(shopflow, "ShopFlow System") {
    Container(web_app, "ShopFlow Web App", "React, Node.js", "Handles user interface and checkout flow")
}
System_Ext(payment_gateway, "Payment Gateway", "Stripe/PayPal", "Processes payment transactions")
System_Ext(bank_api, "Bank API", "Financial Institution", "Authorizes and settles transactions")
System_Ext(notification_svc, "Notification Service", "SendGrid/Twilio", "Sends order confirmations")

Rel(customer, web_app, "Uses", "HTTPS")
Rel(web_app, payment_gateway, "Initiates payment", "HTTPS/REST")
Rel(payment_gateway, bank_api, "Authorizes transaction", "ISO 8583/API")
Rel(web_app, notification_svc, "Triggers confirmation", "Async Event")
Rel(notification_svc, customer, "Sends receipt", "Email/SMS")

@enduml

The corresponding sequence diagram for the “Happy Path” checkout flow:

@startuml
actor Customer
participant "ShopFlow Web App" as WebApp
participant "Payment Gateway" as Gateway
participant "Bank API" as Bank
participant "Notification Service" as Notify

Customer -> WebApp: Submits checkout form
WebApp -> Gateway: POST /charge {amount, token}
activate Gateway
Gateway -> Bank: Authorize transaction
activate Bank
Bank --> Gateway: Authorization approved
deactivate Bank
Gateway --> WebApp: Payment success + transaction_id
deactivate Gateway
WebApp -> Notify: Publish OrderConfirmed event
activate Notify
Notify -> Customer: Send confirmation email/SMS
deactivate Notify
WebApp --> Customer: Display order confirmation
@enduml

Key benefits included:

  • Clear visualization of third-party dependencies (Payment Gateway, Bank API)

  • Identification of potential failure points in the external integration chain

  • Alignment with business stakeholders on the “happy path” user experience

Level 2: Container Sequence – Cross-Service Collaboration

Focus: Web App, API Services, Database, and Message Queue interactions

Zooming into the ShopFlow system boundary, here’s the C4 Container diagram:

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml

Person(customer, "Customer", "Shops on ShopFlow")
System_Boundary(shopflow, "ShopFlow System") {
    Container(web_app, "ShopFlow Web App", "React, Node.js", "Handles UI and checkout flow")
    Container(payment_api, "Payment API", "Go, gRPC", "Orchestrates payment processing")
    Container(order_svc, "Order Service", "Java, Spring", "Manages order state and validation")
    ContainerDb(order_db, "Order Database", "PostgreSQL", "Stores order and transaction records")
    ContainerQueue(event_queue, "Event Queue", "Kafka", "Async event distribution")
    Container(notification_svc, "Notification Service", "Python, FastAPI", "Sends user communications")
    Container(audit_svc, "Audit Service", "Go", "Logs compliance and security events")
}
System_Ext(payment_gateway, "Payment Gateway", "Stripe", "External payment processor")

Rel(customer, web_app, "Uses", "HTTPS")
Rel(web_app, payment_api, "POST /payments", "gRPC")
Rel(payment_api, order_svc, "Validate order", "gRPC")
Rel(order_svc, order_db, "Read/Write", "SQL")
Rel(payment_api, payment_gateway, "Process charge", "HTTPS/REST")
Rel(payment_api, event_queue, "Publish PaymentProcessed", "Kafka")
Rel(event_queue, notification_svc, "Consume event", "Kafka")
Rel(event_queue, audit_svc, "Consume event", "Kafka")
Rel(notification_svc, customer, "Send confirmation", "Email/SMS")

@enduml

The corresponding container-level sequence diagram for payment processing:

@startuml
participant "Web App" as WebApp
participant "Payment API" as PaymentAPI
participant "Order Service" as OrderSvc
database "Order Database" as OrderDB
participant "Payment Gateway" as Gateway
queue "Event Queue" as Queue
participant "Notification Service" as Notify
participant "Audit Service" as Audit

WebApp -> PaymentAPI: POST /payments {order_id, payment_token}
activate PaymentAPI
PaymentAPI -> OrderSvc: GET /orders/{id}
activate OrderSvc
OrderSvc -> OrderDB: SELECT order details
OrderDB --> OrderSvc: Return order data
OrderSvc --> PaymentAPI: Order valid + amount
deactivate OrderSvc

PaymentAPI -> Gateway: Charge request
activate Gateway
Gateway --> PaymentAPI: Success + txn_id
deactivate Gateway

PaymentAPI -> Queue: Publish PaymentProcessed {txn_id, status}
activate Queue
Queue -> Notify: Consume event
Queue -> Audit: Consume event
deactivate Queue

Notify -> Customer: Send confirmation
Audit -> OrderDB: INSERT audit_log

PaymentAPI --> WebApp: 200 OK + {transaction_id}
deactivate PaymentAPI
WebApp --> Customer: Show success page
@enduml

[Image: Rendered sequence diagram showing cross-container collaboration with synchronous gRPC calls, async Kafka events, and database interactions]

This container-level sequence became invaluable for:

  • Cross-team integration planning between frontend, backend, and infrastructure teams

  • Identifying synchronous vs. asynchronous communication patterns

  • Documenting retry logic and timeout configurations at service boundaries

Level 3: Component Sequence – Internal Module Coordination

Focus: Components within the Payment API container

For the Payment API container, the team documented internal component interactions. Here’s the C4 Component diagram:

@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml

Container_Boundary(payment_api, "Payment API") {
    Component(validation_comp, "Request Validator", "Go", "Validates input schema and business rules")
    Component(fraud_comp, "Fraud Detection", "Python ML", "Evaluates transaction risk score")
    Component(orchestrator, "Transaction Orchestrator", "Go", "Manages payment state machine")
    Component(gateway_adapter, "Gateway Adapter", "Go", "Abstracts external payment provider APIs")
    Component(response_formatter, "Response Formatter", "Go", "Prepares API responses")
    
    ComponentDb(cache, "Risk Cache", "Redis", "Stores recent fraud scores")
    ComponentDb(txn_log, "Transaction Log", "PostgreSQL", "Audit trail for payments")
}

Rel(validation_comp, fraud_comp, "Forward valid requests")
Rel(fraud_comp, cache, "Check/update risk score")
Rel(fraud_comp, orchestrator, "Pass risk assessment")
Rel(orchestrator, gateway_adapter, "Execute payment")
Rel(gateway_adapter, txn_log, "Log transaction attempt")
Rel(orchestrator, response_formatter, "Provide result")
@enduml

The corresponding component-level sequence for fraud-aware payment processing:

@startuml
participant "Request Validator" as Validator
participant "Fraud Detection" as Fraud
database "Risk Cache" as Cache
participant "Transaction Orchestrator" as Orchestrator
participant "Gateway Adapter" as Adapter
database "Transaction Log" as TxnLog
participant "Response Formatter" as Formatter

Validator -> Fraud: ValidateAndAssess(request)
activate Fraud
Fraud -> Cache: GET risk_score:user_id
alt Cache hit
    Cache --> Fraud: Return score
else Cache miss
    Fraud -> Fraud: Run ML inference
    Fraud -> Cache: SET risk_score
end
Fraud --> Validator: Risk assessment {score, flags}
deactivate Fraud

alt risk_score > THRESHOLD
    Validator --> Formatter: Reject: HIGH_RISK
    Formatter --> API_Response: 403 Forbidden
else
    Validator -> Orchestrator: ProcessPayment(validated_request)
    activate Orchestrator
    Orchestrator -> Adapter: ExecuteCharge(payment_details)
    activate Adapter
    Adapter -> Adapter: Call external gateway
    Adapter -> TxnLog: INSERT transaction_attempt
    Adapter --> Orchestrator: Gateway result {status, txn_id}
    deactivate Adapter
    
    Orchestrator -> TxnLog: UPDATE transaction status
    Orchestrator -> Formatter: FormatResponse(result)
    deactivate Orchestrator
    
    Formatter --> API_Response: 200 OK + {transaction_id}
end
@enduml

This granular view supported:

  • Internal team design reviews and code architecture decisions

  • Debugging complex race conditions and state management issues

  • Onboarding new engineers to the payment domain logic

Level 4: Code-Level Interactions (Optional)

Focus: Critical class interactions for complex algorithms

For the fraud detection algorithm—a high-risk, complex component—the team optionally documented class-level interactions. Example PlantUML sequence for the ML inference pipeline:

@startuml
participant "FeatureExtractor" as FE
participant "ModelLoader" as ML
participant "RiskScorer" as RS
database "ModelStore" as MS
database "FeatureStore" as FS

FE -> FS: GET user_behavior_features(user_id)
FS --> FE: Return feature vector
FE -> RS: ExtractFeatures(raw_data)
RS -> ML: LoadModel("fraud_v2.onnx")
activate ML
ML -> MS: Retrieve model artifact
MS --> ML: Return serialized model
ML --> RS: Loaded model instance
deactivate ML

RS -> RS: RunInference(features, model)
RS --> FE: Risk score + confidence interval
FE --> Caller: FraudAssessment {score, explanation}
@enduml

This was reserved only for:

  • Novel machine learning inference pipelines

  • Complex state machine implementations

  • Security-critical cryptographic operations

Implementation Practices That Ensured Success

Naming and Visual Alignment

The team established strict naming conventions: a container labeled “Payment API [Go]” in C4 diagrams appeared identically as a lifeline in sequence diagrams. They adopted the C4 color scheme (blue for internal elements, grey for external systems) across both diagram types, reducing cognitive load when switching between views.

Tooling Strategy: Diagrams as Code

Using C4-PlantUML, the team defined their architecture model once in text format and generated both static C4 diagrams and dynamic sequence views. Example of shared model definition:

' shared_model.puml - Single source of truth
!define SHOPFLOW_SYSTEM "ShopFlow System"
!define PAYMENT_API "Payment API" [Go, gRPC]
!define ORDER_SVC "Order Service" [Java, Spring]
!define PAYMENT_GATEWAY "Payment Gateway" [Stripe, External]

' Usage in Container diagram:
Container(payment_api, PAYMENT_API, "Orchestrates payment processing")

' Usage in Sequence diagram:
participant PAYMENT_API as PaymentAPI

This approach:

  • Eliminated drift between static and dynamic documentation

  • Enabled version control and code review for architecture changes

  • Allowed automated generation of documentation during CI/CD pipelines

[Image: Side-by-side comparison showing C4 Container diagram and corresponding sequence diagram with consistent naming, colors, and element positioning]

Scenario-First Documentation Philosophy

Instead of documenting every possible interaction, the team focused sequence diagrams on high-value scenarios:

  • Cross-boundary calls: Payment API to external Payment Gateway integration

  • Complex logic: Retry handling with exponential backoff during gateway timeouts

  • Security flows: OAuth token exchange and signature verification processes

  • Edge cases: Partial failure recovery when database writes succeed but notifications fail

Example: Sequence diagram for retry logic with exponential backoff:

@startuml
participant "Payment API" as API
participant "Payment Gateway" as Gateway

API -> Gateway: Charge request (attempt 1)
activate Gateway
Gateway --> API: Timeout (504)
deactivate Gateway

API -> API: Schedule retry (delay: 2s)
API -> Gateway: Charge request (attempt 2)
activate Gateway
Gateway --> API: Timeout (504)
deactivate Gateway

API -> API: Schedule retry (delay: 4s)
API -> Gateway: Charge request (attempt 3)
activate Gateway
Gateway --> API: Success + txn_id
deactivate Gateway
API --> Caller: Return transaction_id
@enduml

[Image: Rendered sequence diagram showing retry pattern with exponential backoff delays]

Each sequence diagram was linked from the corresponding static C4 diagram using numbered references, creating a navigable documentation ecosystem.

Measurable Outcomes

After six months of using the integrated C4 + sequence diagram approach:

Metric Before After Improvement
Onboarding time for new engineers 3 weeks 1.5 weeks 50% reduction
Cross-team integration defects 12/month 4/month 67% reduction
Architecture review cycle time 5 days 2 days 60% reduction
Stakeholder confidence in system understanding (survey) 62% 94% 32-point increase

Lessons Learned

  1. Start small: Begin with one critical user journey rather than attempting to document the entire system

  2. Maintain synchronization: Use “diagrams as code” tools to prevent static and dynamic views from diverging

  3. Document decisions, not just flows: Include notes explaining why certain interaction patterns were chosen

  4. Review with multiple audiences: Validate diagrams with engineers, product managers, and operations teams to ensure clarity at all levels

  5. Embrace evolution: Treat architecture diagrams as living documents that evolve with the system


Conclusion

Integrating sequence diagrams with the C4 model transforms architecture documentation from a static reference into a dynamic, multi-dimensional understanding of software systems. By mapping sequence diagrams to C4 abstraction levels, teams can provide the right level of detail to the right audience—whether that’s executives reviewing system boundaries, architects designing container interactions, or developers implementing component logic.

The key to success lies not in documenting everything, but in strategically selecting scenarios where dynamic behavior adds clarity: complex integrations, critical user journeys, and high-risk workflows. When combined with consistent naming, visual alignment, and “diagrams as code” tooling, this integrated approach creates documentation that scales with your system and evolves with your team.

As software systems grow increasingly distributed and asynchronous, the ability to visualize both structure and behavior becomes not just valuable, but essential. The C4 model provides the map; sequence diagrams provide the journey. Together, they empower teams to build, maintain, and evolve complex systems with confidence and clarity.

Turn every software project into a successful one.

We use cookies to offer you a better experience. By visiting our website, you agree to the use of cookies as described in our Cookie Policy.

OK