Learnixo

Solution Architecture · Lesson 4 of 6

C4 Model — Communicating Architecture with Diagrams

Why the C4 Model

The problem with most architecture diagrams:
  → Too abstract for developers ("boxes and arrows, what does this mean?")
  → Too detailed for stakeholders ("I see 47 boxes and 83 arrows, I'm lost")
  → Inconsistent notation — every diagram uses different symbols
  → Nobody can tell if an arrow means HTTP, events, SQL, or "it talks to it somehow"

C4 solves this with four zoom levels, each for a different audience:
  Level 1 — System Context:  for business stakeholders ("what does this system do and who uses it?")
  Level 2 — Container:       for architects and tech leads ("what are the deployable units?")
  Level 3 — Component:       for developers ("what modules/services are inside a container?")
  Level 4 — Code:            for individual developers ("what classes/types implement this?")

You rarely need Level 4 — the code already shows Level 4.
Most teams need Levels 1-3.

Level 1 — System Context

Audience: business stakeholders, product owners, non-technical managers
Question it answers: "What is this system and who uses it?"

What to show:
  → The system (one box)
  → External users (personas)
  → External systems it integrates with
  → The relationship/protocol between them

What NOT to show:
  → Internal structure of the system
  → Technologies
  → Database tables

Example: Clinical Prescription Platform

  [Ward Nurse] ──uses──> [Clinical Platform] ──reads patient data──> [NHS Patient Registry (FHIR R4)]
                                                ──sends alerts──> [Email/SMS Gateway]
                                                ──reports──> [MHRA Audit System]
  [Clinical Pharmacist] ──uses──> [Clinical Platform]
  [Hospital Admin] ──uses──> [Clinical Platform]

This tells a non-technical director: "the platform serves nurses and pharmacists,
pulls patient data from NHS, and reports to MHRA." That's all they need.

Level 2 — Container Diagram

Audience: technical architects, tech leads, DevOps
Question: "What deployable things exist and how do they communicate?"

A "container" in C4 = any independently deployable/runnable unit:
  → Web application, API, mobile app, database, message queue, cache

Example: Clinical Platform containers

  [SPA React App] ──HTTPS──> [ASP.NET Core API (modular monolith)]
                                      |         |        |
                               [SQL Server] [Redis] [Azure Service Bus]
                                      |
                              [Azure Blob Storage (documents)]

  The API container communicates with:
    → SQL Server: EF Core / Dapper over TCP
    → Redis: IDistributedCache over TCP
    → Azure Service Bus: integration events
    → Azure Blob Storage: patient document uploads

Label every arrow with: protocol, direction, and what flows.
"Sends prescription events (AMQP)" is better than just an arrow.

Level 3 — Component Diagram

Audience: developers working inside a specific container
Question: "What are the major structural parts inside this container?"

For the Clinical Platform API (modular monolith):

  [ASP.NET Core API]
    ├── Patients Module
    │     ├── PatientsController
    │     ├── PatientsDbContext
    │     └── IPatientQueryService (impl: PatientQueryService)
    ├── Prescriptions Module
    │     ├── PrescriptionsController
    │     ├── PrescriptionsDbContext
    │     └── IPrescriptionRepository (impl: PrescriptionRepository)
    ├── LabResults Module
    │     └── ...
    └── SharedKernel
          ├── Result
          ├── IModuleEventBus
          └── Entity

Each module shows its public surface (controller, public service interface)
and its major internal components. You don't list every class — just the
components another developer needs to understand to navigate the system.

Drawing C4 Diagrams in Code

C#
// Option 1: Structurizr DSL (text-based, version-controlled)
// https://structurizr.com/dsl

workspace "Clinical Platform" {
    model {
        nurse       = person "Ward Nurse"
        pharmacist  = person "Clinical Pharmacist"
        platform    = softwareSystem "Clinical Platform" {
            spa = container "React SPA" "Web UI" "React + TypeScript"
            api = container "ASP.NET Core API" "Modular monolith" "C# .NET 8" {
                patientsModule       = component "Patients Module"
                prescriptionsModule  = component "Prescriptions Module"
                labModule            = component "LabResults Module"
            }
            db      = container "SQL Server" "" "SQL Server 2022"
            cache   = container "Redis" "" "Redis 7"
        }
        fhir    = softwareSystem "NHS FHIR Registry" "External"
        mhra    = softwareSystem "MHRA Audit System" "External"

        nurse      -> spa "Uses" "HTTPS"
        pharmacist -> spa "Uses" "HTTPS"
        spa        -> api "Calls" "HTTPS/JSON"
        api        -> db  "Reads/writes" "SQL over TCP"
        api        -> cache "Caches" "Redis protocol"
        api        -> fhir "Fetches patient data" "FHIR R4 REST"
        api        -> mhra "Submits audit events" "HTTPS"
    }
    views {
        systemContext platform "SystemContext" { include * }
        container     platform "Containers"    { include * }
        component     api      "Components"    { include * }
    }
}
// Option 2: Mermaid (inline in Markdown/GitHub)

graph TD
    Nurse["Ward Nurse"]
    Pharmacist["Clinical Pharmacist"]
    SPA["React SPA"]
    API["ASP.NET Core API"]
    DB["SQL Server"]
    FHIR["NHS FHIR Registry"]

    Nurse --> SPA
    Pharmacist --> SPA
    SPA -->|HTTPS/JSON| API
    API -->|SQL| DB
    API -->|FHIR R4 REST| FHIR

Common C4 Mistakes

Mistake 1: Drawing C4 once and never updating it
  Fix: Treat diagrams as living documentation. Update on major structural changes.
  A stale diagram is worse than no diagram — it misleads.

Mistake 2: Too much detail at Level 1
  Fix: Level 1 shows relationships, not technology. No SQL Server boxes at Level 1.

Mistake 3: Arrows with no labels
  Fix: Every arrow must say what flows and which protocol.
  "Uses" is not a protocol. "HTTPS/JSON REST" is.

Mistake 4: Drawing the ideal architecture, not the actual one
  Fix: C4 is descriptive, not aspirational. Draw what exists.
  Use a separate "target architecture" diagram if you need to show the future state.

Mistake 5: Making Level 3 a class diagram
  Fix: Components are groups of related classes — not individual classes.
  If you're showing 50 boxes, you're drawing Level 4 code, not Level 3 components.

Production issue I've seen: A team handed me a "system architecture diagram" before a review. It was a Level 2 container diagram with 63 boxes, 118 arrows, and 4 different arrow styles with no legend. Nobody in the review could read it. When I asked the architect what the different arrow colors meant, the answer was: "I think blue is HTTPS and red is database, but I drew this in 2019 and I'm not sure anymore." We replaced it with three C4 diagrams (Levels 1, 2, 3) maintained in Structurizr DSL, version-controlled in the repo. New engineers could read Level 1 in 2 minutes and orient themselves in Level 3 within 20 minutes.


Key Takeaway

C4 provides four zoom levels: Context (business stakeholders), Container (deployable units), Component (internal structure), Code (classes). Use Level 1 for boardroom conversations, Levels 2-3 for engineering. Label every arrow with protocol and direction — unlabeled arrows are useless. Maintain diagrams as code (Structurizr DSL or Mermaid) in the repository alongside the system they describe. A stale diagram is worse than no diagram — keep them accurate or remove them.