alama@world: ~/
./toggle-theme
The C4 Model – Misconceptions, Misuses & Mistakes thumbnail

# The C4 Model – Misconceptions, Misuses & Mistakes

by Simon Brown
Published on September 13, 2024
Software Architecture Modeling Documentation C4 Model

Description

Simon Brown, C4's creator, walks through the myths, misuses, and mistakes he sees teams make with the model — from renaming abstractions and complaining about blue-and-gray to modelling microservices, message brokers, and shared libraries the wrong way.

Venue

GOTO 2024

cat notes.md

My Notes

Key Takeaways

C4 in one paragraph — for context

Simon spends the first few minutes reminding the room what C4 actually is before going after the misconceptions. Worth capturing because most of the mistakes flow from getting this bit wrong.

  • The four abstractions: Software System → Container → Component → Code
  • The four diagrams are named after those abstractions — "C4" is the diagram set, not the abstractions
  • Notation and tooling independent by design. Boxes and arrows are fine, so long as the notation is self-describing
  • Analogy: diagrams as maps. Zoom out for system context, zoom in for container/component detail, tell different stories to different audiences
  • Simon only recommends drawing the top two levels (system context + container) in practice — component and code diagrams exist mostly for grounding: they connect the abstractions back to real code and close the model-code gap (George Fairbanks)

Notation myths

A handful of complaints Simon hears constantly, and his responses.

  • "Blue and gray is boring." It's notation independent. He picked those colours to draw the example diagrams on c4model.com. Refresh the site today and it randomises the palette — a small trolling gesture at the people who wouldn't stop mentioning it.
  • "C4 forces too much text in the boxes." It doesn't. Simon's actual recommendation is more text, not less: put responsibilities and the kind of data stored inside each box. Default to the verbose version in docs; strip it for a presentation slide if you need to.
  • "Remove the [Container] / [Person] metadata in square brackets — it's cluttered." Don't. That metadata is what removes ambiguity about which abstraction a box represents.
  • "C4 can't show decisions." Correct — diagrams show the outcome of decisions. Record decisions in ADRs (Architecture Decision Records) and link them, don't cram them into the picture.
  • "C4 omits deployment." Only the static structure diagrams do. Deployment diagrams exist on c4model.com — they show instances of containers/systems mapped onto infrastructure, and you can freely mix them with the fancy cloud-icon style if you want.
  • "C4 can't show runtime behaviour." C4 defines a dynamic diagram modelled after the UML collaboration diagram — boxes are C4 elements, arrows are numbered/ordered. Or just draw a UML sequence diagram. Nothing stops you.

"We don't need C4, we do DDD / event modelling instead"

Simon's reaction, unedited: this literally makes no sense. DDD and event modelling describe orthogonal concerns — they're part of the design process. C4 describes the runtime artefacts (the things on servers, and what lives inside those things) that come out of it. They're complementary, not substitutes.

Do not rename the abstractions

He knows "container" is an unfortunate name (he had it before Docker did), but pleads:

  • Don't remap Container → Component and Component → Subcomponent in your org. It breaks conversations with the rest of the C4-using world.
  • Don't stretch the Software System boundary to represent a Spotify-style squad. A Software System is a thing that's being changed; multiple squads changing shared code is still one Software System.

Adding "arbitrary levels of abstraction" is a circle, not a straight line

There's a family of C4-adjacent tools (e.g. "LikeC4") that let you declare more than four levels — a service with components, and those components have components. Simon's argument:

  • The whole point of C4 was to fix the mess of "ad-hoc boxes with no defined abstractions"
  • If you add new levels without giving each one a precise, unambiguous definition, you're back to the pre-C4 mess
  • Word "component" in that tool ends up meaning multiple things at multiple levels — exactly the ambiguity C4 was created to remove
  • Fine to extend, but the discipline of "what does this abstraction precisely mean?" has to stay

The real power of C4 is the abstractions, not the diagrams

A line worth pulling out. The diagrams get all the attention (onboarding, comms, threat modelling), but Simon argues the pictures are the outcome. The value is that the abstractions force teams to have precise conversations — is this thing separately deployable? Then it's a container. Not deployable, sits inside one? Component. That precision is what fixes phrases like "microservices shouldn't share a database" — do you mean a schema, a server instance, a set of tables? C4 forces you to answer.

Layers, subsystems, and bounded contexts are organisational, not abstractional

A recurring pattern: people want to draw a diagram of "customer / order / billing" boxes over three million lines of code and call it a subsystem view. Simon considers that useless — it's a bullet list dressed up as a picture.

  • Architectural layers (web / service / repo) aren't abstractions, they're organisational. Draw them as a boundary around the components that live in each layer, on the same component diagram.
  • Same for jars / modules / deployment units — layer them on top as organisational boundaries.
  • Same critique applies (controversially, and he flagged it) to bounded contexts when they're just used as a grouping shape on a system boundary.

Message-driven architectures — model topics, not brokers

The mistake he sees: draw Kafka as a single C4 container, with every service pushing/pulling from it. Technically accurate, but it hides the real coupling.

  • A C4 container is an application or a data store. A Kafka topic is a data store — a bucket you put stuff into and pull stuff out of.
  • Model each topic as its own container. Deploy multiple topics onto one broker, or across brokers — that's a deployment concern.
  • This surfaces the point-to-point coupling ("A → topic X → C") that the Kafka-as-blob diagram conceals.
  • Pub/sub with one-to-many? The explicit-topic version is clearer. Simple point-to-point? A direct arrow labelled "via topic X" is fine — a visual-simplicity trade-off.

Shared libraries are not containers

Two apps using a shared logging jar/dll. It is tempting — and wrong — to draw that library as its own C4 container with arrows going into it from both apps. That picture implies interprocess communication with a standalone service.

  • Reality: the shared component is copy-pasted into both apps at build time.
  • Correct picture: the logging component appears twice, once inside each application's component diagram.
  • Notation trick to signal it's shared: colour those components grey and note it in the diagram key.

Modelling microservices — depends who owns them

This is the part he says people get wrong most often. His rule: a microservice is modelled as one of three things — a Software System, a Container, or a grouping of containers — and which depends on team boundaries and what implementation details you can see.

  • Single team, monolith stage. One web app + one database schema. Standard container diagram.
  • Same team, moves to microservices. Each service is a pair of containers (API + database schema). Draw an organisational boundary around each pair to make the service boundary visible. Colour-code to disambiguate.
  • Single-container microservice. A stateless AWS Lambda with no data store — that's just one container.
  • Different teams own different services (Conway's law kicks in). Now those services become Software Systems in your context diagram — because you no longer see their internals, you only depend on their API. Each owning team draws their own C4 for their system.
  • The wrong version he sees: modelling a microservice as a container with API + schema as components inside it. Components aren't independently deployable — the mismatch breaks the model.

Don't show external containers

A related team-boundary rule. On your container diagram, show only your containers. External systems appear as software system boxes.

  • Showing "my container A talks to their container B" leaks their internal implementation into your diagram — makes yours volatile and creates coupling to information you shouldn't have.
  • Simon: if you see a diagram like that, question whether those two teams should actually be one team.
  • Exception: shared database schema across systems. Anti-pattern smell — worth surfacing explicitly on the diagram so it's visible as debt.

Scaling C4 to lots of services

The critique that "C4 doesn't scale to distributed architectures" — from a quote he says is going into the second edition of a well-known book — is really a critique of the visualisation, not the model. Three ways he handles it:

  1. One diagram per service. Focus each container diagram on a single service and show only its inbound (afferent) and outbound (efferent) neighbours. Easy with diagrams-as-code (Structurizr); painful with Visio-style tools that require copy-paste.
  2. Force-directed graph visualisation. Same data, rendered as an interactive D3.js graph — click for nearest neighbours, drag things around. Better for exploring a large landscape.
  3. Feed the model into another tool. Simon showed exporting to a graph-viewer tool for interactive drill-down. Because it's models as code, you can also stick it in Neo4j and run Cypher queries, or generate the model automatically by scraping the codebase or AWS config.

The framing: once you treat the architecture as data — a graph of typed nodes and edges — the visualisation is one of many possible views over it. That's the real payoff of models-as-code.

Notable Quotes

On what people actually value from C4:

The real power of C4 is not the diagrams. The diagrams are super useful — onboarding new developers is easier, communication is easier, you can do threat modelling on them. But the real power is the set of abstractions. It's forcing you to have more precise, better conversations to better identify what the architectural building blocks are.

On teams that add extra levels of abstraction to fix C4's "limitations":

This isn't a straight line, it's a circle. Once you start adding additional levels of abstraction, you basically get back to the mess you had before — if those additional levels are not defined clearly.

On "database is a database":

What do we mean by database? A database server? A schema? A collection of tables? Microservices shouldn't share a database — I'm happy for multiple services to share a database server instance. I'm less happy for multiple microservices to share a single database schema.

On the blue-and-gray complaint:

I was utterly fed up of people saying blue and gray looks boring. I chose blue and gray. End of story. Don't read too much into it.

How I'll Apply This

Three concrete things I'm taking away.

  1. Stop drawing Kafka as one blob. Every message-driven diagram I've drawn has been the Kafka-as-hub version. Model the topics as containers next time — the point-to-point coupling that surfaces will be worth the extra boxes.
  2. Question the shared-library-as-container reflex. Same fix as above — a shared jar/npm package appears inside each consuming app's component diagram, not floating between them. If it deserves its own box, it should also deserve its own deployment.
  3. Decide microservice modelling by ownership, not by technology. Before drawing anything, ask who owns this service and can I see its code? Owner = me → group of containers with a boundary. Owner = another team, opaque API → software system. That single question replaces the container-vs-software-system debate I keep having.

Related tools worth exploring after this talk: Structurizr (Simon's models-as-code tool), ADRs for pairing decisions with diagrams, and ArchUnit / JQAssistant for keeping the code honest to the model.