Theoretical Foundations
Welcome to the curriculum workspace. Here you will find long-form technical guidelines outlining core architectural blueprints and implementation mechanics.
Module 10: Formalized Visual Modeling Standards (C4 & UML)
PREREQUISITE STATEMENT: This module assumes familiarity with distributed system boundaries as taught in Module 0: Systems Thinking 101, service decomposition principles from Module 8: Domain-Driven Design, and event-driven topology from Module 9: Event-Driven Architectures. If you are new to microservice decomposition or have not yet studied how system boundaries map to organizational structures, complete those modules before proceeding. This module is the primary reference for architecture communication standards used throughout the remainder of the MPC curriculum.
Introduction: The Communication Gap in Software Architecture
Software architecture exists in two registers simultaneously. The first register is the running system itself — the compiled binaries, the network sockets, the database schemas, the orchestration manifests. The second register is the shared mental model that engineers, architects, product managers, and security teams carry in their heads. The failure of most software organizations is not a failure of the first register. It is a failure of the second.
When the shared mental model diverges from reality — when the diagram on the conference room whiteboard no longer reflects the production topology — the consequences are severe. Engineers make changes in isolation without understanding upstream dependencies. New team members waste weeks reverse-engineering what could have been communicated in a thirty-minute architecture review. Security auditors cannot identify trust boundaries. Incident responders cannot locate the failure domain during an outage at 3 AM.
This module is a precise technical treatment of how to eliminate this divergence. It covers two complementary standards: the C4 Model by Simon Brown, which provides a hierarchical zoom-based approach to describing software architecture, and the Unified Modeling Language (UML), which provides a standardized palette of diagram types for structural, behavioral, and interaction modeling. Together, these tools form the visual vocabulary of professional software engineering.
A critical underlying theme of this module is Architecture as Code: the practice of expressing diagrams in text-based, version-controlled formats such as the diagram editor and PlantUML, rather than in proprietary binary file formats. When diagrams are code, they participate in pull request reviews, generate meaningful diffs, and evolve alongside the systems they describe.
Section 1: The Whiteboard Problem
A. Why Ad-Hoc Diagrams Fail
Every engineering team draws architecture diagrams. The problem is not a shortage of diagrams. The problem is that most diagrams are informal, unstructured, and illegible to audiences outside the team that drew them.
Consider what happens when five engineers from different backgrounds stand at a whiteboard and begin sketching the same system. The backend lead draws boxes representing database servers and application nodes, connected by arrows labeled with table names. The frontend engineer draws a single box labeled "API" and focuses on the user flows. The DevOps engineer draws Kubernetes pods, load balancers, and ingress controllers. The security engineer draws trust zones and data classification boundaries. The product manager draws personas and feature flows.
All five diagrams are "correct" at some level of abstraction. But none of them is useful to the other four people in the room. There is no agreed-upon notation, no standardized zoom level, no common vocabulary. Each diagram is a personal artifact that becomes incomprehensible the moment its author is not in the room to explain it.
The fundamental failures of ad-hoc diagramming are:
- No abstraction levels: A single diagram attempts to show everything simultaneously — user flows, microservice dependencies, database tables, and cloud infrastructure — causing cognitive overload.
- Different audiences receive the same artifact: A security auditor requires different information than a junior backend engineer joining the team. A single undifferentiated diagram serves neither audience well.
- No standardized notation: An arrow between two boxes might mean "calls synchronously," "publishes an event to," "stores data in," "deploys to," or "is the same physical machine as." Without a legend, arrows are meaningless.
- No ownership or versioning: A photograph of a whiteboard diagram is not a source-controlled artifact. It cannot be reviewed, diffed, or linked in a pull request.
- Context is assumed: Ad-hoc diagrams assume the viewer already understands the system. They serve as memory aids for people who attended the meeting, not as onboarding documents for people who did not.
B. The Cost of Ambiguous Architecture Documentation
The cost of poor architecture documentation is not abstract. Consider the following failure modes drawn from production engineering experience:
[ Symptom ] [ Root Cause ] [ Documentation Failure ]
──────────────────────────────────────────────────────────────────────────────────────────
New engineer breaks auth Did not know AuthService No container diagram showing
in production deployment shared a database with shared UserDB dependency
ProfileService
Security audit fails Auditors cannot determine No context diagram showing
PCI DSS scope review which services handle PCI data external payment system trust
and cross which trust boundaries boundaries
Incident response takes On-call engineer cannot No deployment diagram showing
4 hours instead of 20 mins locate which pod runs the which Kubernetes namespace
PaymentProcessor service hosts which services
Architecture review is Proposal includes a service No sequence diagram showing
blocked for 3 weeks that already exists under existing message flow
a different name in a between OrderService and
different team's namespace NotificationService
These are not hypothetical. They represent the dominant category of engineering productivity losses in medium-to-large engineering organizations. Formalized visual modeling standards eliminate them.
C. The Google Maps Analogy: Visual Abstraction Levels
The most powerful mental model for understanding hierarchical architecture documentation is the Google Maps zoom-level analogy.
When you open Google Maps and zoom out to view the entire planet, you see continents and ocean basins. This is the correct abstraction for answering the question "Which continent is Brazil on?" It would be counterproductive to render individual street names at this zoom level — the visual density would make the map unreadable and the useful information would be hidden.
When you zoom in to a city, you see neighborhoods, major roads, and transit lines. When you zoom further in to a single block, you see individual buildings, parking lots, and pedestrian paths. Each zoom level presents exactly the information relevant to the questions that can be asked at that scale.
Software architecture documentation must work the same way. The question "How does our system integrate with third-party payment providers?" requires a different zoom level than the question "How does the PaymentService controller delegate to the PaymentRepository class?" A single diagram cannot answer both questions without becoming an illegible soup of boxes and arrows.
The zoom-level principle, formalized:
Zoom Level Audience Questions Answered
──────────────────────────────────────────────────────────────────────────────────────────
Planet CTO, Board, Investors What does our system do? Who uses it? What external
(System) partners does it depend on?
Continent Tech Lead, Architects What are the deployable runtime units? How do they
(Container) communicate? What technology stack does each use?
City Senior Engineer, What are the major internal components within a
(Component) Security Auditor single deployable? How is the code organized?
Block Junior Engineer, What classes, interfaces, and methods implement a
(Code) IDE Auto-generation specific component?
The C4 Model formalizes this zoom-level hierarchy into four precisely defined levels, each with its own notation, audience, and purpose.
Section 2: The C4 Model — Architecture at Four Zoom Levels
A. Origins and Design Philosophy
The C4 Model was created by Simon Brown and published in his book "Software Architecture for Developers." It emerged from Brown's observation that the UML specification, while comprehensive, was too complex and too heavily coupled to object-oriented programming paradigms to serve as a universal communication tool for modern distributed systems. Simultaneously, informal whiteboard diagrams were too unstructured to communicate reliably across teams.
The C4 Model makes a deliberate trade-off: it sacrifices the full expressiveness of UML in exchange for a small, learnable set of concepts that can be grasped in minutes. The model has four primary abstractions:
- Person: A human user (or a role, such as "Administrator" or "Customer") who interacts with the system.
- Software System: The highest-level unit of abstraction. A software system is the system you are describing, along with the external systems it depends on or that depend on it.
- Container: A deployable or runnable unit within a software system. A web server process, a mobile application, a database schema, a serverless function, a message broker — these are all containers. Note critically that this is not a Docker container. The word "container" here predates Docker and means any independently deployable execution unit.
- Component: A logical grouping of related functionality within a single container — a controller class, a service object, a repository, a security facade.
These four abstractions map directly to the four diagram levels:
| C4 Level | Primary Abstraction Shown | Typical Viewer |
|---|---|---|
| Level 1: System Context | Software Systems and Persons | Business stakeholders, all engineers |
| Level 2: Container | Containers within a Software System | Tech leads, architects, senior engineers |
| Level 3: Component | Components within a single Container | Backend engineers, security auditors |
| Level 4: Code | Classes, interfaces, methods | Junior engineers, IDE tooling |
B. Level 1: The System Context Diagram
The System Context Diagram is the highest zoom level. It shows your software system as a single box at the center of the diagram and draws the external world around it: the human users who interact with it, and the external software systems it integrates with.
At this level, no internal architectural details are visible. The diagram answers two questions and only two questions: "Who uses the system?" and "What external systems does it depend on or integrate with?"
A well-constructed System Context Diagram should be immediately readable by a product manager, a new engineering hire, or a potential business partner. It requires no technical knowledge to interpret. The level of detail is deliberately coarse.
What to include:
- The software system being described (center box, distinct color)
- Named human user personas (Person shapes — typically a stick figure or rounded rectangle)
- External software systems that are outside the ownership boundary of your organization (distinct color, typically gray)
- Labeled relationships: arrows with descriptions of the nature of the interaction
What to exclude:
- Internal technology stack details
- Database names
- API endpoint paths
- Container-level decomposition
- Deployment infrastructure
Notation Rules for Level 1
Every arrow in a C4 diagram must carry a label. The label must describe what is communicated, not just that communication occurs. "Sends data" is an inadequate label. "Submits order request via HTTPS/JSON" is adequate.
Color conventions (Brown's recommended defaults):
- Your software system: Blue (#1168BD background)
- External systems: Gray (#999999 background)
- Persons: Light blue (#08427B background)
- Relationship arrows: Black, with a brief description
C. Level 2: The Container Diagram
The Container Diagram zooms into the system boundary established at Level 1. It shows the major deployable artifacts that together constitute the software system, how they communicate, and what technology they are implemented in.
This is typically the most important diagram in an organization's architecture documentation. It communicates:
- What processes run in production (and therefore what must be deployed, monitored, and scaled)
- How those processes communicate (synchronous HTTP, asynchronous message queues, shared databases)
- What technology stack each container uses (which determines team ownership and hiring decisions)
- Where the major data stores live
What to include:
- All independently deployable runtime units: web applications, mobile applications, desktop applications, server-side APIs, databases, message brokers, background workers, serverless functions
- The technology used by each container (e.g., "React 18 / TypeScript" or "Node.js / Express" or "PostgreSQL 15")
- The communication protocol on every relationship arrow (e.g., "HTTPS/JSON," "gRPC/Protocol Buffers," "AMQP," "JDBC/SQL")
- A clear visual boundary separating internal containers from external systems
What to exclude:
- Internal code structure of individual containers
- Deployment infrastructure (Kubernetes namespaces, EC2 instance types)
- Class-level details
The Container Misconception
The single most common error engineers make when first learning C4 is conflating "C4 container" with "Docker container." This conflation causes confusion because not every C4 container maps to a Docker image, and not every Docker image represents a meaningful architectural boundary.
In C4 terminology, the following are all containers:
- A React Single-Page Application served from an S3 bucket behind CloudFront
- A React Native mobile application distributed via the App Store
- A Node.js Express API running inside a Docker container
- A PostgreSQL database (whether running on RDS or a bare metal server)
- An AWS Lambda function
- A Kafka cluster
- An Elasticsearch index cluster
The defining characteristic of a C4 container is that it is a separately deployable, runnable unit that communicates with other containers over a defined interface (network, file system, or inter-process communication).
D. Level 3: The Component Diagram
The Component Diagram zooms into a single container and shows the major logical components within it — the controllers, services, repositories, facades, event handlers, and other internal structural units — and their inter-dependencies.
This level is primarily useful for:
- Onboarding new engineers to a specific service
- Security threat modeling (identifying where sensitive data is processed)
- Identifying circular dependencies and architecture violations within a codebase
- Reviewing proposed changes to internal service structure
At this level, the diagram is only concerned with one container at a time. It shows how internal components are wired together and what external systems or containers they reach out to.
What to include:
- Named logical components with their responsibilities
- Internal dependency relationships between components
- External system or container connections that components make directly
- The technology or pattern used (e.g., "REST Controller," "Repository Pattern," "Domain Event Publisher")
What to exclude:
- Class-level implementation details
- Method signatures
- Database table schemas
- Infrastructure details
E. Level 4: The Code Diagram
The Code Diagram is the most granular level. It represents the implementation structure within a single component — class hierarchies, interfaces, design patterns (Strategy, Observer, Factory), and method-level relationships.
In practice, Level 4 diagrams are rarely drawn by hand because the effort required to keep them synchronized with a rapidly changing codebase is prohibitive. Instead, they are generated automatically by IDE tooling such as IntelliJ IDEA's UML class diagram generator, Visual Studio's architecture explorer, or tools like Structurizr DSL or C4Sharp.
The C4 specification explicitly notes that this level is optional and that hand-drawn Level 4 diagrams should be reserved for stable, foundational components that change infrequently — core domain models, shared library interfaces, security primitives.
When Level 4 diagrams are produced, they use standard UML class diagram notation, which is covered in detail in Section 4 of this module.
Section 3: C4 in Practice — The ShopForge E-Commerce Platform
This section presents a complete, worked example of C4 documentation for a fictional e-commerce platform named ShopForge. ShopForge is a mid-scale B2C e-commerce platform serving approximately 2 million registered customers with features for product discovery, cart management, order processing, and fulfillment tracking.
A. System Context Diagram (Level 1) — ShopForge
graph TB
Customer["Customer\n[Person]\nA registered shopper who\nbrowses products, places orders,\nand tracks deliveries"]
Admin["Store Administrator\n[Person]\nManages product catalog,\nprocesses refunds, and\nreviews order reports"]
ShopForge["ShopForge\n[Software System]\nB2C e-commerce platform\nenabling product discovery,\ncart checkout, and order fulfillment"]
StripeSystem["Stripe\n[External System]\nProcesses credit card\npayments and manages\npayment method vaults"]
SendGridSystem["SendGrid\n[External System]\nDelivers transactional\nemails: order confirmations,\nshipping notifications, receipts"]
FedExSystem["FedEx Fulfillment API\n[External System]\nProvides real-time\nshipment tracking data\nand fulfillment status"]
GoogleAnalytics["Google Analytics\n[External System]\nCollects and aggregates\nbrowsing and conversion\nbehavior analytics"]
Customer -->|"Browses products, adds to cart,\nplaces orders via HTTPS"| ShopForge
Admin -->|"Manages catalog and processes\nrefunds via HTTPS"| ShopForge
ShopForge -->|"Submits payment charges and\nrefunds via HTTPS/JSON REST API"| StripeSystem
ShopForge -->|"Sends transactional email\nnotifications via HTTPS/JSON REST API"| SendGridSystem
ShopForge -->|"Queries shipment status\nand fulfillment updates via HTTPS/JSON"| FedExSystem
ShopForge -->|"Streams page view and\nevent telemetry via HTTPS/JavaScript"| GoogleAnalytics
B. Container Diagram (Level 2) — ShopForge
graph TB
Customer["Customer\n[Person]"]
Admin["Store Administrator\n[Person]"]
subgraph SystemBoundary["ShopForge System Boundary"]
WebSPA["Web Storefront\n[Container: React 18 / TypeScript]\nSingle-page application\ndelivered via CDN. Handles\nproduct discovery, cart, checkout"]
MobileApp["Mobile App\n[Container: React Native / TypeScript]\nNative iOS and Android\napplication for on-the-go\nbrowsing and purchasing"]
AdminPortal["Admin Portal\n[Container: Next.js / TypeScript]\nServer-side rendered back-office\napplication for catalog management\nand order operations"]
APIGateway["API Gateway\n[Container: NGINX / Kong]\nReverse proxy. Handles TLS\ntermination, rate limiting,\nrouting, and JWT validation"]
AuthService["Auth Service\n[Container: Node.js / Express]\nIssues and validates JWT tokens.\nManages user registration,\nlogin, and session management"]
CatalogService["Catalog Service\n[Container: Go / Gin]\nManages product listings,\ncategories, inventory counts,\nand search indexing"]
OrderService["Order Service\n[Container: Node.js / Express]\nOrchestrates order creation,\npayment capture, fulfillment\ntriggering, and status tracking"]
PaymentsService["Payments Service\n[Container: Node.js / Express]\nAbstracts payment provider\nintegration. Manages charge\nrecords and refund operations"]
NotificationService["Notification Service\n[Container: Node.js / Worker]\nConsumes events from message\nbroker and dispatches transactional\nemail and push notifications"]
MessageBroker["Message Broker\n[Container: Apache Kafka]\nDurable event bus. Carries\norder events, payment events,\nand fulfillment events"]
UserDB["User Database\n[Container: PostgreSQL 15]\nStores user accounts,\ncredentials (hashed), and\nsession metadata"]
CatalogDB["Catalog Database\n[Container: PostgreSQL 15]\nStores product records,\ncategory hierarchies,\nand inventory levels"]
OrderDB["Order Database\n[Container: PostgreSQL 15]\nStores order records,\nline items, and\npayment references"]
SearchIndex["Search Index\n[Container: Elasticsearch 8]\nInverted index for full-text\nproduct search with\nfaceted filtering"]
SessionCache["Session Cache\n[Container: Redis 7]\nStores JWT revocation lists,\nrate limit counters,\nand cart session state"]
end
StripeAPI["Stripe API\n[External System]"]
SendGridAPI["SendGrid API\n[External System]"]
FedExAPI["FedEx API\n[External System]"]
Customer -->|"Browses and shops\nvia HTTPS"| WebSPA
Customer -->|"Browses and shops\nvia HTTPS"| MobileApp
Admin -->|"Manages catalog\nvia HTTPS"| AdminPortal
WebSPA -->|"API requests\nvia HTTPS/JSON"| APIGateway
MobileApp -->|"API requests\nvia HTTPS/JSON"| APIGateway
AdminPortal -->|"Admin API requests\nvia HTTPS/JSON"| APIGateway
APIGateway -->|"Auth validation requests\nvia HTTP/JSON (internal)"| AuthService
APIGateway -->|"Product and search requests\nvia HTTP/JSON (internal)"| CatalogService
APIGateway -->|"Order requests\nvia HTTP/JSON (internal)"| OrderService
AuthService -->|"Reads and writes user records\nvia JDBC/SQL"| UserDB
AuthService -->|"Reads and writes JWT revocation\nand rate limit state via Redis Protocol"| SessionCache
CatalogService -->|"Reads and writes product records\nvia JDBC/SQL"| CatalogDB
CatalogService -->|"Indexes product records\nvia HTTPS/JSON REST"| SearchIndex
OrderService -->|"Reads and writes order records\nvia JDBC/SQL"| OrderDB
OrderService -->|"Delegates payment capture\nvia HTTP/JSON (internal)"| PaymentsService
OrderService -->|"Publishes order.created and\norder.fulfilled events via Kafka Protocol"| MessageBroker
OrderService -->|"Queries fulfillment status\nvia HTTPS/JSON"| FedExAPI
PaymentsService -->|"Submits charges and refunds\nvia HTTPS/JSON REST API"| StripeAPI
PaymentsService -->|"Publishes payment.succeeded and\npayment.failed events via Kafka Protocol"| MessageBroker
NotificationService -->|"Consumes order and payment events\nvia Kafka Protocol"| MessageBroker
NotificationService -->|"Delivers transactional emails\nvia HTTPS/JSON REST API"| SendGridAPI
C. Component Diagram (Level 3) — Order Service Internals (ASCII Topology)
The following ASCII diagram represents the internal component structure of the Order Service container, which is a Node.js Express application responsible for the full lifecycle of an order.
+-------------------------------------------------------------------------------------------+
| ORDER SERVICE CONTAINER [Node.js 20 / Express 4 / TypeScript] |
| |
| +----------------------------+ +-------------------------------------------+ |
| | HTTP Routing Layer | | Domain Layer | |
| | [Express Router] | | | |
| | | | +-----------------------------------+ | |
| | POST /orders +------>+ | CreateOrderUseCase | | |
| | GET /orders/:id +------>+ | [Application Service] | | |
| | POST /orders/:id/cancel +------>+ | Orchestrates order validation, | | |
| | GET /orders/:id/status +------>+ | payment delegation, and event | | |
| | | | | publishing | | |
| +----------------------------+ | +-----------------------------------+ | |
| | | | | | |
| +----------------------------+ | v v v | |
| | Auth Middleware | | +--------+ +-------+ +--------+ | |
| | [JWT Validator] | | | Order | | Cart | | Price | | |
| | Validates Bearer tokens | | | Domain | | Domain| | Engine | | |
| | against revocation list | | | Model | | Model | | [Value | | |
| | in Redis cache | | +--------+ +-------+ | Object]| | |
| +----------------------------+ | +--------+ | |
| +-------------------------------------------+ |
| | |
| +--------------------------------------------+-----------------------------+ |
| | | | |
| v v v |
| +--------------------+ +--------------------+ +-------------------+ |
| | OrderRepository | | PaymentsClient | | EventPublisher | |
| | [Repository] | | [HTTP Client] | | [Kafka Producer] | |
| | Encapsulates SQL | | Calls internal | | Publishes domain | |
| | queries against | | PaymentsService | | events to Kafka | |
| | OrderDB schema | | via HTTP/JSON | | topic: orders.* | |
| +--------------------+ +--------------------+ +-------------------+ |
| | | | |
| v v v |
| [PostgreSQL OrderDB] [PaymentsService] [Kafka Broker] |
| (External Container) (External Container) (External Cont) |
+-------------------------------------------------------------------------------------------+
The Order Service applies a layered hexagonal architecture. The HTTP Routing Layer is a pure adapter — it transforms HTTP request objects into domain input types and HTTP responses from domain output types. The Domain Layer contains the pure business logic: order creation rules, cancellation policies, and pricing calculations. The Repository, HTTP Client, and Event Publisher are infrastructure adapters, each implementing a domain-defined port interface. This structure means that the PostgreSQL database, the PaymentsService, and the Kafka broker can all be replaced with test doubles during unit testing without changing any domain code.
D. Code Diagram (Level 4) — Order Domain Model Description
At Level 4, the structure of the Order domain model maps to the following TypeScript class hierarchy:
// Core domain entity — the aggregate root for the order lifecycle
export class Order {
private readonly id: OrderId;
private readonly customerId: CustomerId;
private lineItems: ReadonlyArray<LineItem>;
private status: OrderStatus;
private readonly createdAt: Date;
private updatedAt: Date;
private constructor(props: OrderProps) {
this.id = props.id;
this.customerId = props.customerId;
this.lineItems = props.lineItems;
this.status = OrderStatus.PENDING;
this.createdAt = new Date();
this.updatedAt = new Date();
}
// Factory method — enforces invariants at creation time
static create(props: CreateOrderProps): Result<Order, OrderValidationError> {
if (props.lineItems.length === 0) {
return Result.fail(new OrderValidationError('Order must contain at least one line item'));
}
return Result.ok(new Order({
id: OrderId.generate(),
customerId: props.customerId,
lineItems: props.lineItems,
}));
}
calculateTotal(): Money {
return this.lineItems.reduce(
(acc, item) => acc.add(item.unitPrice.multiply(item.quantity)),
Money.zero('USD')
);
}
confirm(): Result<void, OrderStateError> {
if (this.status !== OrderStatus.PENDING) {
return Result.fail(new OrderStateError(
`Cannot confirm order in status: ${this.status}`
));
}
this.status = OrderStatus.CONFIRMED;
this.updatedAt = new Date();
return Result.ok(undefined);
}
cancel(): Result<void, OrderStateError> {
const cancellableStatuses = [OrderStatus.PENDING, OrderStatus.CONFIRMED];
if (!cancellableStatuses.includes(this.status)) {
return Result.fail(new OrderStateError(
`Cannot cancel order in status: ${this.status}`
));
}
this.status = OrderStatus.CANCELLED;
this.updatedAt = new Date();
return Result.ok(undefined);
}
}
// Value object — immutable money representation
export class Money {
private constructor(
private readonly amount: number,
private readonly currency: string
) {}
static of(amount: number, currency: string): Money {
if (amount < 0) throw new Error('Money amount cannot be negative');
return new Money(amount, currency);
}
static zero(currency: string): Money {
return new Money(0, currency);
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error(`Currency mismatch: ${this.currency} vs ${other.currency}`);
}
return new Money(this.amount + other.amount, this.currency);
}
multiply(factor: number): Money {
return new Money(this.amount * factor, this.currency);
}
}
// Repository port interface — defined by the domain, implemented by infrastructure
export interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
findByCustomerId(customerId: CustomerId): Promise<ReadonlyArray<Order>>;
}
// Domain event — published after successful order creation
export class OrderCreatedEvent {
readonly type = 'order.created' as const;
constructor(
public readonly orderId: string,
public readonly customerId: string,
public readonly totalAmountUsd: number,
public readonly occurredAt: Date
) {}
}
The class structure above represents the Level 4 view. Note that the OrderRepository interface is defined in the domain layer and implemented by the PostgresOrderRepository class in the infrastructure layer — this is the dependency inversion principle applied at the boundary between domain and data access.
Section 4: C4 Notation Rules and Common Mistakes
A. Container is Not a Docker Container
This distinction deserves its own subsection because the conflation is so persistent in practice. In C4:
| C4 Container | May or May Not Be a Docker Container |
|---|---|
| React SPA (served from S3) | Not a Docker container |
| Node.js API (running in ECS) | Yes, runs inside a Docker container |
| PostgreSQL Database (on RDS) | Managed service, no Docker container |
| Mobile App (React Native) | Not a Docker container |
| Lambda Function | Not a Docker container (unless container-packaged) |
| Redis (on ElastiCache) | Managed service, no Docker container |
| Kafka Cluster (on Confluent Cloud) | Managed service, no Docker container |
The C4 "container" concept is a higher-level architectural abstraction. Docker containers are one implementation mechanism for running C4 containers, but the mapping is not one-to-one.
B. Arrow Labeling Requirements
Every relationship arrow in a C4 diagram must carry a label. The label structure should convey:
- What is being communicated (the nature of the interaction)
- How it is communicated (the protocol and data format)
Examples of correct and incorrect arrow labels:
| Incorrect | Correct |
|---|---|
| "calls" | "Submits order creation requests via HTTP/JSON" |
| "data" | "Reads user credentials via JDBC/SQL" |
| "messages" | "Publishes order.created events via AMQP (RabbitMQ)" |
| "API" | "Queries product inventory via gRPC / Protocol Buffers" |
| "talks to" | "Streams page view telemetry via HTTPS/JavaScript SDK" |
C. Color and Shape Conventions
Brown's official C4 notation uses shape and color conventions to communicate element type at a glance:
| Element Type | Shape | Default Background Color | Border |
|---|---|---|---|
| Person | Rounded box or stick figure | Light blue | Blue |
| Your Software System | Box | Blue | Dark blue |
| External Software System | Box | Gray | Dark gray |
| Container (yours) | Box | Blue | Dark blue |
| Container (external) | Box | Gray | Dark gray |
| Component | Box | Light blue | Blue |
| Database | Cylinder | Any (consistent) | Matching border |
These conventions can be adapted to organizational style guides, but the critical requirement is internal consistency within a diagram set. If a gray box means "external system" in your Level 1 diagram, a gray box must mean the same in your Level 2 diagram.
D. Boundary Notation
System boundaries must be explicitly drawn. The most common failure mode is drawing a container diagram without a system boundary, leaving viewers uncertain about which containers are owned by the team and which are external dependencies.
INCORRECT (no boundary): CORRECT (explicit boundary):
+---------------------------+
[WebSPA] [APIGateway] | ShopForge System Boundary|
[OrderDB] [StripeAPI] | [WebSPA] [APIGateway] |
| [OrderDB] |
+---------------------------+
|
[StripeAPI] (External)
The boundary makes it immediately clear which elements are under the team's operational ownership and which are external.
E. When to Use Each C4 Level — Decision Table
| Question Being Asked | Appropriate Level |
|---|---|
| "What does our system do and who uses it?" | Level 1: System Context |
| "What do we deploy to production?" | Level 2: Container |
| "What external systems do we depend on?" | Level 1 or Level 2 |
| "What technology stack does the API use?" | Level 2: Container |
| "How is the Order Service internally structured?" | Level 3: Component |
| "What classes implement the payment processing?" | Level 4: Code |
| "Where is PCI data processed?" | Level 2 + Level 3 |
| "How do I onboard to Service X?" | Level 3: Component |
| "What changes when we swap PostgreSQL for DynamoDB?" | Level 3: Component |
Section 5: UML Diagram Types — A Complete Reference
While C4 provides the macro-level zoom hierarchy for distributed system documentation, the Unified Modeling Language (UML) provides a comprehensive vocabulary of diagram types for structural, behavioral, and interaction modeling. UML 2.5.1 defines fourteen diagram types organized into two categories.
A. Structural Diagrams
Structural diagrams capture the static structure of a system — what exists and how it is organized, independent of time.
1. Class Diagrams
The class diagram is the most widely used structural UML diagram. It shows classes, interfaces, their attributes and methods, and the relationships between them.
Relationship types in class diagrams:
Association (uses):
ClassA ────────────────────> ClassB
(ClassA has a reference to ClassB)
Aggregation (has-a, weak ownership):
ClassA ◇──────────────────> ClassB
(ClassA aggregates ClassB; ClassB can exist without ClassA)
Composition (has-a, strong ownership):
ClassA ◆──────────────────> ClassB
(ClassB cannot exist without ClassA; ClassA owns ClassB's lifecycle)
Inheritance (is-a):
ConcreteClass ────────────|> AbstractBase
(ConcreteClass extends AbstractBase)
Interface Realization:
ConcreteClass - - - - - ->|> IInterface
(ConcreteClass implements IInterface)
Dependency (uses temporarily):
ClassA - - - - - - - - -> ClassB
(ClassA depends on ClassB, typically as a method parameter)
Multiplicity notation specifies cardinality at relationship endpoints:
1— exactly one0..1— zero or one (optional)0..*— zero or more1..*— one or moren..m— between n and m instances
classDiagram
class Order {
+OrderId id
+CustomerId customerId
+OrderStatus status
+Date createdAt
+create(props) Order
+confirm() Result
+cancel() Result
+calculateTotal() Money
}
class LineItem {
+ProductId productId
+int quantity
+Money unitPrice
+calculateSubtotal() Money
}
class Money {
+number amount
+string currency
+add(other Money) Money
+multiply(factor number) Money
+zero(currency string) Money
}
class OrderRepository {
<<interface>>
+findById(id OrderId) Order
+save(order Order) void
+findByCustomerId(id CustomerId) Order[]
}
class PostgresOrderRepository {
-Pool pgPool
+findById(id OrderId) Order
+save(order Order) void
+findByCustomerId(id CustomerId) Order[]
}
Order "1" *-- "1..*" LineItem : contains
LineItem "1" *-- "1" Money : priced with
PostgresOrderRepository ..|> OrderRepository : implements
Order ..> Money : uses
2. Sequence Diagrams
Sequence diagrams model the chronological flow of messages between participants (objects, services, actors) over time. They are indispensable for understanding request lifecycles, race conditions, and protocol handshakes.
The vertical axis represents time flowing downward. Each participant has a lifeline — a vertical dashed line. Messages are horizontal arrows between lifelines, labeled with the message name or method call. An activation box on a lifeline represents the period during which the participant is executing.
sequenceDiagram
actor Customer
participant WebSPA as Web Storefront [React SPA]
participant APIGateway as API Gateway [NGINX / Kong]
participant AuthService as Auth Service [Node.js]
participant OrderService as Order Service [Node.js]
participant PaymentsService as Payments Service [Node.js]
participant Stripe as Stripe API [External]
participant Kafka as Message Broker [Kafka]
Customer->>WebSPA: Click "Place Order" button
WebSPA->>APIGateway: POST /orders (Bearer JWT, order payload) via HTTPS/JSON
APIGateway->>AuthService: POST /auth/validate (Bearer JWT) via HTTP/JSON
AuthService-->>APIGateway: 200 OK (customerId: "cust_123", scopes: ["order:create"])
APIGateway->>OrderService: POST /orders (customerId, lineItems) via HTTP/JSON
OrderService->>OrderService: Validate order invariants (items > 0, stock available)
OrderService->>PaymentsService: POST /charges (customerId, amount: 149.99 USD) via HTTP/JSON
PaymentsService->>Stripe: POST /v1/payment_intents (amount, currency, paymentMethodId) via HTTPS/JSON
Stripe-->>PaymentsService: 200 OK (paymentIntentId: "pi_xyz", status: "succeeded")
PaymentsService-->>OrderService: 200 OK (chargeId: "chg_abc", status: "captured")
OrderService->>OrderService: Persist order to OrderDB (status: CONFIRMED)
OrderService->>Kafka: Publish order.created event (orderId, customerId, totalAmount)
Kafka-->>OrderService: Acknowledgment (offset committed)
OrderService-->>APIGateway: 201 Created (orderId: "ord_789")
APIGateway-->>WebSPA: 201 Created (orderId: "ord_789")
WebSPA-->>Customer: Display "Order Confirmed" confirmation page
Key UML sequence diagram notation:
Actor -->Synchronous message (solid arrowhead)Actor -->>Return message (dashed line)Note over X, Y:Annotation spanning participants X through Yloop,alt,opt,par— combined fragments for loops, conditionals, optional blocks, and parallel execution
3. State Diagrams (State Machine Diagrams)
State diagrams model the lifecycle of an object by showing the finite set of states it can occupy and the transitions between them triggered by events or conditions. They are particularly valuable for modeling order lifecycles, user authentication flows, and protocol handshakes.
stateDiagram-v2
[*] --> Pending : Order created
Pending --> Confirmed : Payment captured successfully
Pending --> Cancelled : Customer cancels / Payment fails
Pending --> Expired : TTL exceeded (24 hours without payment)
Confirmed --> Processing : Warehouse accepts fulfillment request
Confirmed --> Cancelled : Customer cancels before processing starts
Processing --> Shipped : Carrier picks up package
Processing --> Failed : Warehouse unable to fulfill (out of stock)
Shipped --> Delivered : Carrier confirms delivery scan
Shipped --> ReturnRequested : Customer initiates return
Delivered --> ReturnRequested : Customer requests return (within 30-day window)
ReturnRequested --> ReturnApproved : Store administrator approves return
ReturnRequested --> ReturnRejected : Outside return window / damaged
ReturnApproved --> Refunded : Payment reversed via Stripe
Cancelled --> [*]
Expired --> [*]
Failed --> [*]
Delivered --> [*]
Refunded --> [*]
ReturnRejected --> [*]
The state diagram makes the order lifecycle unambiguous. A developer reading this diagram immediately understands that cancellation is only possible from the Pending and Confirmed states, that a refund is only possible after a return is approved, and that there are three terminal failure states. This is information that would otherwise be buried in scattered conditional logic across multiple service files.
4. Activity Diagrams
Activity diagrams are the UML equivalent of a workflow or flowchart. They model the flow of control through a process, including decision points, parallel branches, and synchronization bars. They are most useful for documenting business processes, data pipelines, and deployment workflows.
Key notation elements:
- Initial node: Filled black circle (start)
- Activity node: Rounded rectangle (an action or step)
- Decision node: Diamond (if/else branch)
- Fork/Join bar: Thick horizontal bar (parallel execution split/merge)
- Final node: Filled circle inside a larger circle (end)
Activity diagrams can be expressed using flowchart diagram syntax. A representative checkout flow:
flowchart TD
Start([Customer clicks Checkout]) --> ValidateCart{Cart contains\nvalid items?}
ValidateCart -->|No| ShowCartError[Display cart\nvalidation error]
ShowCartError --> End1([End])
ValidateCart -->|Yes| LoadPayment[Display payment\nmethod selection]
LoadPayment --> CustomerEntersPayment[Customer enters\ncredit card details]
CustomerEntersPayment --> ValidatePaymentDetails{Card details\npass Luhn check?}
ValidatePaymentDetails -->|No| ShowPaymentError[Display card\nformat error]
ShowPaymentError --> CustomerEntersPayment
ValidatePaymentDetails -->|Yes| SubmitOrder[POST /orders to API Gateway]
SubmitOrder --> AuthCheck{JWT valid and\nnot revoked?}
AuthCheck -->|No| Show401[Return 401\nUnauthorized]
Show401 --> End2([End])
AuthCheck -->|Yes| CapturePayment[Delegate to\nPayments Service]
CapturePayment --> PaymentResult{Stripe charge\nsucceeded?}
PaymentResult -->|No| ShowPaymentFailure[Display payment\nfailure message]
ShowPaymentFailure --> End3([End])
PaymentResult -->|Yes| CreateOrder[Persist Order\nto OrderDB]
CreateOrder --> PublishEvent[Publish order.created\nto Kafka]
PublishEvent --> SendConfirmation[Notification Service\nsends confirmation email]
SendConfirmation --> ShowConfirmation[Display order\nconfirmation to customer]
ShowConfirmation --> End4([End])
5. Component Diagrams
UML Component Diagrams are distinct from C4 Component Diagrams in scope and intent. In UML, a component diagram shows how software components are assembled from smaller parts and interact with each other through defined interfaces, using provided interfaces (lollipop notation) and required interfaces (socket notation).
Component diagrams are most useful for:
- Documenting plugin architectures
- Showing assembly of microservice meshes
- Describing how build artifacts depend on shared libraries
In modern distributed systems documentation, the C4 Container Diagram typically serves the role of UML Component Diagrams at the macro scale. UML Component Diagrams remain valuable for documenting internal library assembly.
6. Deployment Diagrams
UML Deployment Diagrams show the physical or virtual infrastructure topology on which software artifacts are deployed. They use nodes (boxes representing servers, clusters, or cloud services) and artifacts (the software components deployed to those nodes).
+--------------------------------------------------+
| AWS Region: us-east-1 |
| |
| +--------------------+ +-------------------+ |
| | Availability Zone | | Availability Zone | |
| | us-east-1a | | us-east-1b | |
| | | | | |
| | +---------------+ | | +---------------+ | |
| | | ECS Cluster | | | | ECS Cluster | | |
| | | [Fargate] | | | | [Fargate] | | |
| | | | | | | | | |
| | | <<artifact>> | | | | <<artifact>> | | |
| | | order-service | | | | order-service | | |
| | | :3000 | | | | :3000 | | |
| | | | | | | | | |
| | | <<artifact>> | | | | <<artifact>> | | |
| | | auth-service | | | | auth-service | | |
| | | :3001 | | | | :3001 | | |
| | +---------------+ | | +---------------+ | |
| +--------------------+ +-------------------+ |
| | | |
| +--------+----------------+--------+ |
| | Application Load Balancer | |
| | [AWS ALB] | |
| +----------------------------------+ |
| | |
| +-----------------------+ | +----------------+ |
| | RDS Cluster [Postgres]| | | ElastiCache | |
| | Primary (us-east-1a) | | | [Redis Cluster]| |
| | Standby (us-east-1b) | | | 3 shards | |
| +-----------------------+ | +----------------+ |
| | |
| +------------------------+--------------------+ |
| | Amazon MSK (Managed Kafka) | |
| | 3 brokers across 3 AZs | |
| +---------------------------------------------+ |
+--------------------------------------------------+
B. Behavioral Diagrams Summary
| UML Diagram Type | Primary Use Case | Best For |
|---|---|---|
| Class Diagram | Static structure and relationships | Domain model documentation, API contract review |
| Sequence Diagram | Message flow over time | Request lifecycle, protocol design, debugging |
| State Machine | Object lifecycle states | Order/workflow state management, auth flows |
| Activity Diagram | Process and workflow flow | Business process, data pipelines, CI/CD |
| Component Diagram | Software assembly | Plugin systems, library dependency trees |
| Deployment Diagram | Infrastructure topology | Cloud architecture, capacity planning |
| Use Case Diagram | Actor-system interaction | Requirements gathering, stakeholder review |
| Communication Diagram | Structural message paths | Alternative to sequence for structural emphasis |
| Timing Diagram | Time-constrained behavior | Real-time systems, protocol timing analysis |
Section 6: Architecture as Code
A. The Case for Text-Based Diagram Formats
Architecture diagrams maintained in binary formats — Visio files, Lucidchart exports, draw.io XML files stored in shared drives — suffer from a fundamental problem: they cannot be diffed, reviewed, or version-controlled alongside the code they describe.
When a developer submits a pull request that adds a new microservice, creates a new Kafka topic, and removes a direct database dependency from the Order Service, they should also submit a diff of the architecture diagram that shows those changes. With binary diagram tools, this is impossible. The diagram exists as a separate, manually maintained artifact that drifts from reality over months.
Architecture as Code (AaC) is the practice of expressing architecture diagrams in text-based, version-controlled formats that are:
- Diff-able: Pull requests show exactly which connections, containers, or relationships changed
- Reviewable: Architecture changes receive the same PR review scrutiny as code changes
- Co-located: Diagrams live alongside the code they describe, in the same repository
- Automatable: CI/CD pipelines can validate diagram syntax, generate SVG outputs, and publish diagrams to documentation sites
- Searchable:
grepand code search tools work on diagram source files
The two dominant text-based diagram formats are the diagram editor and PlantUML.
B. the diagram editor
the diagram editor is a JavaScript-based diagramming library that renders diagrams from Markdown-compatible code blocks. It is natively supported in:
- GitHub Markdown (pull requests, issues, wiki pages)
- GitLab Markdown
- Notion
- Obsidian
- Confluence (via plugin)
- VS Code (via extension)
- MkDocs, Docusaurus, and most modern documentation platforms
A architecture diagram is written in a diagram editor with the mermaid language identifier:
```mermaid
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[Alternative]
```
Mermaid supports the following diagram types relevant to software architecture:
graph/flowchart— flowcharts and activity diagramssequenceDiagram— UML sequence diagramsclassDiagram— UML class diagramsstateDiagram-v2— UML state machine diagramserDiagram— Entity-relationship diagramsgitGraph— Git branch history visualizationC4Context,C4Container,C4Component— Native C4 diagram support (via C4 diagram notation)
Mermaid strengths:
- Zero tooling setup — renders natively in GitHub PRs
- Familiar Markdown-adjacent syntax
- Excellent for Level 1 and Level 2 C4 diagrams
- Fast iteration cycle — edit source, save, refresh preview
Mermaid limitations:
- Complex diagrams with many nodes can produce poor auto-layout
- Less precise control over visual positioning compared to GUI tools
- Native C4 support is less mature than sequence and flowchart support
C. PlantUML
PlantUML is a more mature, Java-based text-to-diagram tool with a longer history in enterprise environments. It is the dominant choice for teams that require:
- Precise UML compliance (all fourteen UML 2.5.1 diagram types)
- Custom diagram theming and style control
- Integration with enterprise wikis (Confluence via PlantUML plugin)
- C4 diagrams via the
C4-PlantUMLlibrary by Ricardo Nicolau
A PlantUML sequence diagram:
@startuml
actor Customer
participant "WebSPA" as SPA
participant "API Gateway" as GW
participant "Order Service" as OS
Customer -> SPA : Click Place Order
SPA -> GW : POST /orders [HTTPS/JSON]
GW -> OS : POST /orders [HTTP/JSON]
OS --> GW : 201 Created
GW --> SPA : 201 Created
SPA --> Customer : Order Confirmation
@enduml
PlantUML files are typically rendered in CI/CD pipelines using a Docker-based PlantUML server or the PlantUML CLI, and the resulting SVG/PNG files are committed to the repository or published to a documentation site.
D. Workflow: Architecture Changes as Pull Requests
The recommended workflow for organizations adopting Architecture as Code:
1. Engineer creates feature branch
└── git checkout -b feature/add-recommendation-service
2. Engineer modifies architecture diagram source
└── docs/architecture/shopforge-containers.md (C4 Container diagram)
└── docs/architecture/recommendation-service-components.md (Mermaid Level 3)
3. Engineer opens pull request
└── PR diff shows:
- New container "RecommendationService [Node.js]" added
- New arrow: OrderService --> RecommendationService (order.created event)
- New arrow: RecommendationService --> SearchIndex (product queries)
- New container: RecommendationDB [Redis] added
4. Architecture review occurs on the PR
└── Senior engineer comments: "Should RecommendationService own its own Redis
instance or share SessionCache? Add a note on data isolation rationale."
5. Engineer updates diagram and code together
└── Both changes merge atomically in the same PR
6. CI/CD pipeline validates diagram syntax
└── mermaid --input docs/architecture/*.md --output /dev/null (syntax check)
└── Publishes rendered SVGs to docs.shopforge.internal on merge
This workflow ensures that the architecture diagram is never more than one PR behind the actual system state.
E. Structurizr: C4 Model as Code
For organizations requiring a dedicated C4 toolchain, Simon Brown's own Structurizr platform provides a DSL for expressing C4 models in code:
workspace {
model {
customer = person "Customer" "A registered shopper"
shopForge = softwareSystem "ShopForge" "B2C e-commerce platform" {
webSpa = container "Web Storefront" "React SPA" "React 18 / TypeScript"
apiGateway = container "API Gateway" "Reverse proxy" "NGINX / Kong"
orderService = container "Order Service" "Order lifecycle management" "Node.js"
orderDB = container "Order Database" "Persistent order storage" "PostgreSQL 15" {
tags "Database"
}
}
stripe = softwareSystem "Stripe" "Payment processing" {
tags "External"
}
customer -> webSpa "Browses and shops" "HTTPS"
webSpa -> apiGateway "API requests" "HTTPS/JSON"
apiGateway -> orderService "Routes order requests" "HTTP/JSON"
orderService -> orderDB "Reads and writes orders" "JDBC/SQL"
orderService -> stripe "Captures payment charges" "HTTPS/JSON REST"
}
views {
systemContext shopForge "SystemContext" {
include *
autoLayout
}
container shopForge "Containers" {
include *
autoLayout
}
}
}
Structurizr DSL can be rendered to Mermaid, PlantUML, or the proprietary Structurizr viewer. The model is defined once and rendered at multiple zoom levels automatically, eliminating the inconsistency that arises when maintaining separate Level 1 and Level 2 diagrams that must be manually kept in sync.
Section 7: Enterprise Tooling Landscape
A. Tool Comparison Matrix
Organizations must make deliberate choices about which diagram tools to use for which contexts. The following matrix captures the key dimensions:
| Dimension | the diagram editor | PlantUML | Lucidchart | draw.io | Structurizr |
|---|---|---|---|---|---|
| Format | Text (Markdown) | Text (DSL) | Binary (Cloud) | XML (Binary) | Text (DSL) |
| Version Control | Native git diff | Native git diff | Limited (export) | Partial (XML diff) | Native git diff |
| PR Integration | Native (GitHub/GitLab) | Via CI render | No | No | No |
| C4 Support | Good (C4 extension) | Excellent (C4-PlantUML) | Manual | Manual | Native (by creator) |
| UML Compliance | Partial | Full (UML 2.5.1) | Partial | Partial | Partial |
| Stakeholder Accessibility | Developer-focused | Developer-focused | Business-friendly | Business-friendly | Developer-focused |
| Collaboration | Async (PR comments) | Async (PR comments) | Real-time | Real-time | Async |
| Rendering | Browser/GitHub | CLI/Server | Cloud | Desktop/Cloud | Cloud/CLI |
| Cost | Free (OSS) | Free (OSS) | Paid (SaaS) | Free (OSS) | Freemium |
| Learning Curve | Low | Medium | Low (GUI) | Low (GUI) | Medium |
B. When to Use GUI Tools (Lucidchart, draw.io)
GUI-based diagramming tools are appropriate when:
Stakeholder presentation: Executive stakeholders, external auditors, and board members expect polished, professionally formatted diagrams. Lucidchart and draw.io provide the layout control necessary to produce presentation-quality outputs quickly.
Collaborative workshops: Real-time architecture discovery workshops — where multiple participants add, move, and connect elements simultaneously — benefit from GUI tools' live collaboration features.
One-time artifacts: Diagrams that will not need to be maintained over time (e.g., a point-in-time security audit diagram, a diagram for a conference presentation) do not need to live in version control.
Non-technical stakeholders as primary authors: When the diagram authors are business analysts or product managers who are not comfortable with text-based diagram syntax.
The key discipline for organizations using GUI tools is to treat the exported SVG or PNG as the artifact and to establish a clear process for updating diagrams when the system changes — typically by assigning diagram ownership and including diagram review as part of the architecture review checklist.
C. When to Use Text-Based Tools (Mermaid, PlantUML, Structurizr)
Text-based architecture-as-code tools are appropriate when:
Developer workflows: Diagrams embedded in GitHub PR descriptions, README files, ADRs (Architecture Decision Records), and runbooks are most effective when rendered inline without requiring external tools.
Architecture under active development: Systems that change frequently require diagrams that can change alongside them in the same PR. The diff-ability of text-based formats makes this feasible.
CI/CD validation: Text-based formats can be linted and validated in pipelines. A broken architecture diagram fails the CI build, just as a TypeScript compilation error would.
Consistency at scale: Large engineering organizations with multiple teams can enforce consistent diagram styles through shared Mermaid or PlantUML style files checked into a central configuration repository.
Section 8: Real-World Case Studies in Architecture Documentation
A. Netflix: Living Architecture Documentation
Netflix Engineering publicly documents their architecture evolution across blog posts, conference talks, and their open-source projects. Their approach to architecture documentation shares several characteristics with the C4 methodology:
Zone-of-responsibility diagrams: Netflix maintains explicit documentation of which teams own which system boundaries — equivalent to the C4 System Context diagram. Their microservice mesh (reported to contain over 700 services) makes context diagrams essential for understanding which teams a new service will depend on.
The Simian Army and failure domain documentation: Netflix's chaos engineering practice required precise deployment diagrams to identify blast radius. The Chaos Monkey tool needed accurate topology maps to know which instances it could safely terminate without violating pre-defined availability zones.
Architecture Decision Records (ADRs): Netflix Engineering pairs architectural changes with ADRs that often include embedded sequence diagrams documenting the before-and-after message flow. This matches the Architecture as Code principle: diagrams change alongside code.
The Netflix case illustrates the operational necessity of formal architecture documentation at scale. With 700+ microservices, informal ad-hoc diagramming is not merely inconvenient — it is operationally impossible.
B. Amazon: The "Two-Pizza Team" Service Map
Amazon's microservice culture, which preceded the public discourse on microservices by over a decade, required what internally became known as the "service map" — a living, machine-readable directory of service boundaries, ownership, and dependencies. This artifact is conceptually equivalent to a C4 Level 2 Container Diagram for the entire Amazon platform.
Amazon's public API documentation style also reflects C4 principles:
- The AWS Well-Architected Framework uses multi-level architecture diagrams with explicit boundary notation
- AWS reference architecture diagrams differentiate between "your application" and "AWS managed services" using color coding consistent with C4's internal/external distinction
- AWS Service Control Policies enforce the trust boundary model that System Context diagrams are designed to make visible
The mathematical implication of Amazon's scale: if a system contains $N = 500$ microservices and every service communicates with an average of $k = 10$ other services, the number of unique inter-service relationships is on the order of: $$\text{Relationships} \approx \frac{N \times k}{2} = \frac{500 \times 10}{2} = 2{,}500 \text{ unique dependencies}$$
A graph with 2,500 edges cannot be communicated through ad-hoc whiteboard diagrams. It requires hierarchical zoom levels — exactly what C4 provides — to remain comprehensible.
C. Spotify: The Squad Model and Architecture Documentation
Spotify's publicized "Squad Model" — autonomous cross-functional teams owning individual microservices — introduced the organizational challenge of architecture documentation at tribal scale. When eight squads each own between three and ten services, maintaining shared understanding of the inter-squad dependency graph requires formal documentation standards.
Spotify Engineering's approach, as described in their engineering blog posts, uses several patterns consistent with C4 and UML best practices:
- Service ownership registries: A machine-readable catalog of services, their owning squad, their API contracts, and their direct dependencies — equivalent to a queryable C4 model.
- Sequence diagrams for cross-squad features: When a feature requires coordination across the Accounts squad, the Playlist squad, and the Playback squad, Spotify engineers produce sequence diagrams documenting the expected message flow before implementation begins. This prevents mismatched expectations about event schemas and synchronization points.
- State diagrams for playlist lifecycle: Spotify's playlist model (with states such as draft, published, collaborative, shared, archived) is documented using state machine diagrams that are shared across the platform team, mobile teams, and the API team.
The Spotify case demonstrates a key principle: formal architecture documentation pays dividends proportional to organizational scale. A solo developer building a side project does not need C4 diagrams. A 500-engineer organization with 50 autonomous teams cannot function without them.
D. The Complexity Threshold
Based on publicly available engineering case studies, the inflection point at which formal architecture documentation becomes operationally necessary appears to be approximately:
| Organization Metric | Informal Docs Sufficient | Formal Docs Required |
|---|---|---|
| Number of engineers | < 10 | > 25 |
| Number of services | < 5 | > 15 |
| Number of teams | 1 | > 3 |
| Deployment frequency | Weekly | Daily or higher |
| Geographic distribution | Co-located | Remote or multi-timezone |
$$\text{Documentation Value} = f(\text{team size}) \times g(\text{service count}) \times h(\text{deployment frequency})$$
Where $f$, $g$, and $h$ are monotonically increasing functions. The cost of maintaining formal documentation is roughly constant per diagram, while the value scales super-linearly with organizational complexity. Beyond the thresholds in the table above, the cost of not maintaining formal architecture documentation — measured in onboarding time, incident response time, and architecture drift — systematically exceeds the cost of maintaining it.
Section 9: Diagram Type Decision Matrix
The following matrix is the primary reference for selecting the appropriate diagram type when communicating a specific architectural question:
| Architecture Question | Recommended Diagram | Tool Recommendation |
|---|---|---|
| "What does our system do and who uses it?" | C4 Level 1: System Context | Mermaid / Structurizr |
| "What do we deploy and how does it communicate?" | C4 Level 2: Container | Mermaid / Structurizr |
| "How is Service X internally structured?" | C4 Level 3: Component | Mermaid / PlantUML |
| "What classes implement this component?" | C4 Level 4 / UML Class | IDE auto-gen / PlantUML |
| "What is the request flow for Feature Y?" | UML Sequence Diagram | Mermaid / PlantUML |
| "What states can an Order occupy?" | UML State Machine | state diagram |
| "How does the checkout business process flow?" | UML Activity / Flowchart | flowchart diagram |
| "What runs on which server?" | UML Deployment Diagram | draw.io / PlantUML |
| "What is our domain model?" | UML Class Diagram | Mermaid classDiagram |
| "How does data flow through our ETL pipeline?" | UML Activity / Data Flow | flowchart diagram |
| "What are our trust boundaries for PCI compliance?" | C4 Context + Component | Mermaid + draw.io overlay |
| "How does a distributed transaction span services?" | UML Sequence + State | Mermaid (both) |
| "What changed in this PR architecturally?" | Any (text-based, diffable) | Mermaid (native GitHub render) |
Section 10: Integration with the MPC Curriculum
The formalized visual modeling standards introduced in this module are the communication substrate for all subsequent architectural discussions in the MPC curriculum. The following connections are particularly important:
Module 8: Domain-Driven Design: The C4 Component Diagram (Level 3) is the natural documentation format for a DDD Bounded Context's internal structure. The aggregate root, repositories, domain services, and domain events identified through Event Storming map directly to components in a Level 3 diagram. UML Class Diagrams document the domain model's entity relationships and value object compositions.
Module 9: Event-Driven Architectures: Sequence Diagrams are essential for documenting event-driven request flows where the causal chain spans multiple asynchronous consumers. State Machine Diagrams document the eventual-consistency lifecycle of aggregates that evolve through events (Event Sourcing).
Module 11: API Design: C4 Level 2 Container Diagrams identify which containers expose which APIs and the protocol used on each relationship arrow. UML Sequence Diagrams are the standard format for documenting API endpoint request/response lifecycles in API specifications.
Module 13: Edge Gateways: The API Gateway container appears explicitly in C4 Level 2 diagrams, with its routing relationships to backend services labeled with the protocols it handles. Deployment Diagrams document the gateway's position in the network topology.
Module 14: Fault Tolerance: Circuit breaker implementations are best documented using UML State Machine Diagrams (states: Closed, Open, Half-Open) and Sequence Diagrams showing the fallback behavior during failure conditions.
Practice Challenge: C4 Level 2 Container Diagram
This is the hands-on implementation exercise for Module 10. The goal is to construct a formalized C4 Level 2 Container Diagram for a simplified authentication and payments system using the diagram editor.
Requirements:
- Define a
Customerperson who interacts with the system. - Draw two client-side containers: a
WebSPA(React Single Page App) and aMobileApp(React Native app). - Connect both client containers to a central
APIGatewaycontainer (reverse proxy) usingHTTPS/JSONlabeled arrows. - Route the
APIGatewayto two backend service containers:- An
AuthServicecontainer, which reads and writes to a dedicatedUserDBdatabase container viaJDBC/SQL. - A
PaymentsServicecontainer, which communicates with an externalStripeAPIsystem viaHTTPS/JSON REST API.
- An
- Group all internal containers within a
SystemBoundarysubgraph to clearly demarcate ownership from external systems. - Ensure every relationship arrow carries a descriptive protocol label.
Reference Solution:
graph TB
Customer["Customer\n[Person]\nA registered user who\nshops and pays via\nweb and mobile clients"]
subgraph SystemBoundary["ShopForge System Boundary"]
WebSPA["Web Storefront\n[Container: React 18 / TypeScript]\nSingle-page application\nserved via CDN for\nbrowser-based shopping"]
MobileApp["Mobile App\n[Container: React Native / TypeScript]\nNative iOS and Android\napplication for mobile\nshopping experiences"]
APIGateway["API Gateway\n[Container: NGINX / Kong]\nReverse proxy handling\nTLS termination, rate limiting,\nJWT validation, and routing"]
AuthService["Auth Service\n[Container: Node.js / Express]\nHandles user registration,\nlogin, JWT issuance,\nand token validation"]
UserDB["User Database\n[Container: PostgreSQL 15]\nStores user accounts,\nhashed credentials, and\nsession metadata"]
PaymentsService["Payments Service\n[Container: Node.js / Express]\nManages payment capture,\nrefunds, and payment method\nvault integration"]
end
StripeAPI["Stripe API\n[External System]\nPCI-compliant payment\nprocessing and payment\nmethod vault"]
Customer -->|"Browses products and\nchecks out via HTTPS"| WebSPA
Customer -->|"Browses products and\nchecks out via HTTPS"| MobileApp
WebSPA -->|"Submits API requests\nvia HTTPS/JSON"| APIGateway
MobileApp -->|"Submits API requests\nvia HTTPS/JSON"| APIGateway
APIGateway -->|"Routes authentication\nrequests via HTTP/JSON\n(internal network)"| AuthService
APIGateway -->|"Routes payment\nrequests via HTTP/JSON\n(internal network)"| PaymentsService
AuthService -->|"Reads and writes\nuser records via JDBC/SQL"| UserDB
PaymentsService -->|"Submits charges and\nrefunds via HTTPS/JSON REST API"| StripeAPI
What to observe in the reference solution:
- The
SystemBoundarysubgraph visually delineates the containers your organization owns from the externalStripeAPIsystem. - Every arrow carries a description of both what is communicated and how (the protocol).
- The
UserDBandPaymentsServiceare correctly placed inside the system boundary even thoughPaymentsServiceconnects to an external system — the container is internal; the external system it calls is external. - The
APIGatewaylabels its downstream connections as(internal network)to distinguish them from the public-facingHTTPSconnections from the client containers. - The
StripeAPInode has no subgraph container and is positioned outside theSystemBoundarysubgraph, correctly representing an external system under a third party's operational ownership.
Extension challenges:
- Add a
NotificationServicecontainer that subscribes to aMessageBroker(Kafka) container, and have theAuthServicepublish auser.registeredevent to the broker on successful registration. - Add a
SessionCache(Redis) container that theAuthServiceuses to store JWT revocation lists. - Add a
CDN(CloudFront) external system that serves theWebSPAstatic assets, with theCustomerconnecting to theCDNrather than directly toWebSPA.
These extensions will produce a more realistic Level 2 diagram that reflects the complexity of production systems and will prepare you for the deeper container-to-component decomposition work in Module 11.
Cross-Reference: Architecture visualization standards established in this module are applied directly in Module 11: API Design Patterns, where C4 Level 2 diagrams document API gateway topology, and in Module 14: Fault Tolerance, where UML State Diagrams document circuit breaker state machines. When you encounter architecture diagrams in subsequent modules, you now have the formal vocabulary to read, critique, and extend them.