Module 3: Communicating Architecture

4. Component and Code: when to go down and when to stop

Overview

By the end of this lesson you'll know the two deepest levels of C4 —the Component (level 3, the neighborhood map) and the Code (level 4, the street map)— and, above all, you'll learn the skill almost no one teaches: when to stop going down. The Component shows what's inside a container: its code pieces and how they collaborate, for the dev who's going to work on that piece. The Code shows the exact classes and functions of a component. But the most valuable lesson here isn't how to draw them —it's understanding that detail isn't free: each level you go down is another diagram someone has to maintain, and that rots if no one takes care of it. That's why the experienced architect draws Context and Container almost always, Component only where the complexity warrants it, and Code almost never by hand. The discipline isn't "document everything at maximum detail"; it's going down only where the detail helps a real person.

This matters because the beginner's instinct is the opposite: believing that "documenting well" means "documenting everything to the bottom". That instinct produces the diagram museum —thirty Component and Code sheets that were made once, no one looked at again, and today they lie because the code changed and the drawings didn't—. Documentation that isn't maintained is worse than having no documentation, because it looks true and deceives. The architect who understands the cost of detail produces little and alive instead of much and dead: two or three diagrams people actually use and that are maintained because they're few, instead of thirty that rot. Knowing when to stop is as much part of the craft as knowing how to draw.

Connection with the module: in lesson 3 you drew the two levels you'll use 80% of the time —Context and Container—. Here you go down the two remaining rungs and, more importantly, you learn the brake: the zoom rule that decides how far to keep going. With this you complete the four Cs of C4 (we named them in lesson 2, drew them in lessons 3 and 4). In lesson 5 you'll use all four for the central exercise: choosing the right level for each audience. And the "produce little and alive" rule you learn here reappears in lesson 7, when we attack the 500-page document and the diagram that rots.

Google Maps: why you don't memorize the country street by street

Return to Google Maps one more time, but notice now something you don't do. When you plan a road trip from one end of the country to the other, you don't memorize every street of every town you're going to cross. You look at the map at the level of cities and highways —the zoom that answers your question— and trust that, when you reach a specific town and need the detail of its streets, you'll zoom in the map at that moment, for that town, and not one town more. It would be absurd to print the street-by-street map of the forty towns on the route "just in case": most of them you won't need, and by the time you'd need them, the streets may already have changed.

There are two ideas there, and both are the heart of this lesson. The first: you go down to the detail on demand, only where you need it, not preventively everywhere. The second, deeper: detail ages faster the finer it is. The general shape of the country doesn't change in years; the streets of a town can change in months; a house number, any day. That's why the world map maintains itself almost effortlessly, and the street-by-street map requires constant updating. In software it's identical: Mercado's Context (what it does, who it talks to) is stable for years; the Code of a component (its exact classes) changes every week. Drawing and maintaining by hand the finest level is fighting a moving target every day —that's why it's almost never worth it—.

The Component: the neighborhood map

The Component answers: what's inside this container and how do its parts collaborate? You zoom into one container —not all— and show its components: groupings of code with a clear responsibility. Let's draw it for Mercado's most complex container, the one that concentrates the business's critical flow: the checkout part inside the API.

C4Component
    title Mercado API - Checkout flow components (level 3)
    Container_Boundary(api, "API (FastAPI)") {
        Component(checkout_ctrl, "Checkout Controller", "FastAPI router", "Receives the purchase request")
        Component(cart, "Cart Service", "Module", "Builds the cart and validates stock")
        Component(tax, "Tax Calculator", "Module", "Computes taxes by region")
        Component(pay_client, "Payment Client", "Module", "Talks to the payment gateway")
        Component(order_repo, "Order Repository", "Module", "Persists the order")
    }
    ContainerDb(db, "Database", "PostgreSQL", "Orders")
    System_Ext(payments, "Payment Gateway", "Stripe")

    Rel(checkout_ctrl, cart, "Builds the cart")
    Rel(checkout_ctrl, tax, "Requests the tax")
    Rel(checkout_ctrl, pay_client, "Charges")
    Rel(checkout_ctrl, order_repo, "Saves the order")
    Rel(pay_client, payments, "Charges", "HTTPS/API")
    Rel(order_repo, db, "INSERT/UPDATE", "SQL")

Read it as the dev who's going to fix a bug in the tax calculation: the Checkout Controller receives the request and orchestrates; it asks the Cart Service to build the cart, the Tax Calculator to compute the tax, the Payment Client to charge (and this one talks to Stripe), and the Order Repository to persist the order (and this one writes to PostgreSQL). In thirty seconds the dev knows where to touch —the Tax Calculator— and what surrounds it, without having read a line of code. That's the value of the Component: orienting inside a complex piece.

But notice the word complex. We drew the checkout Component because it's Mercado's critical and tangled flow —where the order and the payment embrace, the one that caused problems in module 2—. We do not draw the Component of the web app, nor of the search index, nor of the database, because there was no need: those pieces are simple or their interior isn't where people get lost. The Component rule is selective: one per container that really warrants it, not one per container just because.

The Code: the street map (and why you almost never draw it)

The Code answers: how exactly is this component written? You zoom into one component —say the Tax Calculator— and show its classes, interfaces, and methods. Here's how it would look, as a class diagram:

classDiagram
    class TaxCalculator {
        +calculate(order, region) TaxResult
    }
    class TaxRule {
        <<interface>>
        +applies(region) bool
        +rate() Decimal
    }
    class RegionRule {
        +applies(region) bool
        +rate() Decimal
    }
    class ExemptRule {
        +applies(region) bool
        +rate() Decimal
    }
    class TaxResult {
        +amount Decimal
        +breakdown list
    }
    TaxCalculator --> TaxRule : uses
    TaxRule <|.. RegionRule : implements
    TaxRule <|.. ExemptRule : implements
    TaxCalculator --> TaxResult : produces

It's a correct and detailed diagram. And here comes the lesson: you'll almost never draw this by hand. Three reasons. First, it rots in hours: as soon as someone adds a TieredRule or renames a method, the diagram lies, and no one is going to remember to update the drawing every time they touch the code. Second, the real code is better documentation of itself: to understand exactly how the TaxCalculator is written, opening the file in the IDE —with autocomplete, navigation, and today's version— beats any class diagram drawn by hand last month. Third, if you really need the visual view, you generate it from the code with a tool (many IDEs and libraries produce class diagrams automatically), so it regenerates up to date when you need it and there's nothing to maintain.

That's why C4 includes the Code level for completeness, but Simon Brown himself recommends not drawing it except in very specific cases —a delicate algorithmic piece worth explaining in detail—, and even there, preferring to generate it. The architect's communication work lives up top, in Context and Container; the Code level is the dev's and the IDE's territory, not the hand-maintained sheet's.

Worked example: how many diagrams you really maintain

Let's put the zoom rule into numbers. The practical question isn't "how many levels does C4 have?" (four), but "how many diagrams are really worth maintaining in a system like Mercado?". The following code counts the reasonable inventory of diagrams per level and shows the answer is three, not thirty —because you go down only where the detail helps—.

# Going down a level costs: each zoom you open is another diagram to maintain.
# That's why almost no one reaches Code, and almost no one draws ALL the components.

# Mercado, counted by level (real pieces of the case):
inventory = {
    "Context":   {"diagrams": 1, "note": "a single diagram for the whole system"},
    "Container": {"diagrams": 1, "note": "a single diagram for all the containers"},
    "Component": {"diagrams": 1, "note": "only the complex container: the checkout API"},
    "Code":      {"diagrams": 0, "note": "generated from code when needed, not drawn by hand"},
}

print("How many diagrams you'd maintain in Mercado, by level:")
print()
print(f"{'Level':<11}{'Diagrams':<11}Comment")
print("-" * 72)
maintained = 0
for level, info in inventory.items():
    maintained += info["diagrams"]
    print(f"{level:<11}{info['diagrams']:<11}{info['note']}")
print("-" * 72)
print(f"{'Total':<11}{maintained:<11}hand-drawn diagrams (they rot if you don't care for them)")
print()

# The zoom rule: go down ONLY where the complexity calls for it.
print("Zoom rule: go down a level only where the detail helps someone work.")
print("  Context + Container: almost always worth it (2 diagrams).")
print("  Component: only in the complex containers (here: 1, checkout).")
print("  Code: almost never by hand; the IDE generates it from real code and it doesn't age.")

What to expect. Running it:

How many diagrams you'd maintain in Mercado, by level:

Level      Diagrams   Comment
------------------------------------------------------------------------
Context    1          a single diagram for the whole system
Container  1          a single diagram for all the containers
Component  1          only the complex container: the checkout API
Code       0          generated from code when needed, not drawn by hand
------------------------------------------------------------------------
Total      3          hand-drawn diagrams (they rot if you don't care for them)

Zoom rule: go down a level only where the detail helps someone work.
  Context + Container: almost always worth it (2 diagrams).
  Component: only in the complex containers (here: 1, checkout).
  Code: almost never by hand; the IDE generates it from real code and it doesn't age.

Three. That's the number that separates living documentation from dead. Compare: if you'd drawn a Component of the five containers and a Code of each component, you'd easily have thirty sheets. No one maintains thirty sheets —they all rot, and documentation that lies is worse than none—. Instead, three diagrams are maintained: they're few, they're reviewed in the same code review, and people use them because they trust they're up to date. The zoom rule isn't laziness; it's the condition for the documentation to stay true. Fewer diagrams, better maintained, more used.

The zoom rule, said in one sentence

This whole lesson fits in one rule: go down a level only where the detail helps a real person work, and stop as soon as it stops helping. Applied:

  • Context: almost always. Every system deserves its world map; it's cheap, stable, and used by many people.
  • Container: almost always. Every system with more than one piece deserves its city map; it's the technical-orientation tool par excellence.
  • Component: sometimes, selective. Only the complex containers —where people get lost inside— deserve their neighborhood map. In Mercado, checkout yes; the web app no.
  • Code: almost never by hand. The street map ages too fast; when you really need it, you open the code or generate it. Drawing it by hand is the very rare exception, reserved for a delicate algorithm worth its weight in gold to explain.

The criterion at each rung is the same: is there a concrete person with a concrete task for whom this level of detail saves time? If yes, go down. If you're going down "so it's complete" or "just in case", stop —you're manufacturing a museum—.

Generating the Code instead of drawing it: why that keeps it alive

We said the Code is almost never drawn by hand and that, when you really need it, it's generated. It's worth understanding what that means concretely, because it's the same principle that in lesson 7 we'll call docs-as-code, applied to the finest level.

"Generating" a Code diagram means a tool produces it from the real code, not from your memory or a drawing you made last month. Many IDEs generate a class diagram of a package with a couple of clicks; tools like the ones that accompany C4 (for example, the ones that draw from a textual description of the system) can regenerate the view every time you ask for it. The difference from drawing by hand is total: the generated diagram reflects today's version because it's produced from today, and if the code changes tomorrow, tomorrow you regenerate it up to date. There's nothing to "maintain" —there's no separate artifact that can go out of sync—; there's the code, which is the source of truth, and a view distilled from it on demand.

This changes the economics of the Code level completely. A hand-drawn class diagram has a creation cost (once) plus an infinite and growing maintenance cost (updating it with every change, forever, something no one sustains). A generated class diagram has zero creation cost (the tool produces it) and zero maintenance cost (it isn't maintained, it's regenerated). That's why the recommendation isn't "never look at the classes" —sometimes you need that view— but "don't draw or store it by hand; generate it when you need it and let it die when you finish".

The same reasoning explains why, even for the Component (level 3), many teams prefer to describe the diagram as versioned text (mermaid, PlantUML C4) alongside the code, instead of an exported image. A diagram-as-text lives in the repository, changes in the same commit as the code it describes, and is reviewed in the same pull request —so it ages much more slowly than an image someone drew and uploaded to a wiki—. The general rule that emerges: the finer the level (the closer to the code that changes daily), the more the diagram should be generated or versioned with the code instead of drawn and stored separately. The Context survives drawn by hand because it barely changes; the Code doesn't, because it changes every day.

Common mistakes

Drawing the Code by hand and swearing to maintain it (of optimism). What happens: the team makes a beautiful class diagram of the star component, puts it in the wiki, and promises to update it with every change. Three weeks later the code changed five times and the diagram none; now it lies. Why it happens: at the moment of drawing it, maintaining it seems easy; no one projects the accumulated cost of updating it every time. How to spot it: if you have a hand-drawn class diagram from more than a month ago, it almost surely no longer matches the code. How to fix it: don't draw Code by hand; if you need the view, generate it from the code at the moment, or simply open the code —it's the source of truth and it's always up to date—.

A Component of every container (of symmetry). What happens: "we made the checkout Component, let's make one for all of them so it's even". Result: five Components, four of which show simple pieces no one needed to break down, and now there are five diagrams to maintain instead of one. Why it happens: symmetry feels tidy ("all containers documented the same"). How to spot it: if you drew a container's Component and can't name the concrete person who's going to use it, you shouldn't have drawn it. How to fix it: Component is selective by design —one where the complexity earns it, zero where it doesn't—; the asymmetry (checkout yes, web app no) is correct, not an oversight.

Confusing "complete" with "useful" (of exhaustiveness). What happens: the team takes pride in having "the four levels documented for the whole system" —thirty sheets— and is surprised that no one uses them and they're out of date. Why it happens: the amount of documentation is equated with its quality; "more complete" sounds like "better". How to spot it: measure how many of your diagrams were consulted or updated last quarter; if most weren't, you have a museum, not communication. How to fix it: the goal isn't to cover all levels of everything, it's for each diagram that exists to help someone and stay alive; three used diagrams beat thirty ignored.

Exercises

Exercise 1 — Component yes or no? For each container of a system, decide whether it's worth drawing a Component and justify: (a) a PostgreSQL database; (b) an API that orchestrates a reservation flow with availability validation, dynamic price calculation, coupon application, and notifications; (c) a React web app that only consumes the API and renders; (d) a worker that reads a queue and sends emails.

See solution

(a) The database → No. A PostgreSQL doesn't have "internal components" to draw in the C4 sense —its relevant structure is the schema, which is documented another way (an ERD), not with a Component—. Nothing to gain by going down.

(b) The API with a complex flow → Yes. Here several tangled responsibilities coexist (availability, dynamic price, coupons, notifications) and a dev who enters easily gets lost. A Component showing those modules and how they collaborate saves hours of reading code blind. It's the classic case of "complex container that warrants zoom".

(c) The web app that only consumes and renders → Probably not. If it's a standard front-end that calls the API and paints screens, its internal structure is predictable and a Component adds nothing. Exception: if it had very elaborate client logic (a collaborative editor, a rules engine in the browser), then yes.

(d) The email worker → No. It's simple —reads queue, sends email—; its behavior is understood from the Container and its name. Going down to Component saves no one time.

The moral: the criterion isn't the type of piece, it's whether someone gets lost inside. The complex API (b) yes; the three simple ones, no. Component selective, not symmetric.

Exercise 2 — The class diagram that aged. A team has, in its wiki, a class diagram of the pricing module made eight months ago. A new dev uses it to understand the code, but the diagram shows three classes that no longer exist (they were refactored) and omits two new ones. The dev loses half a day confused. What failed in the original decision to create that diagram, and what should they have done?

See solution

What failed wasn't drawing the diagram badly —at the time it was correct—; what failed was the decision to maintain by hand an artifact of the Code level, which is exactly the level that ages fastest. A hand-drawn class diagram has an expiration date of days or weeks, because the code at that scale changes constantly and no one remembers (nor should have to remember) to update the drawing with every refactor. The diagram didn't just become useless: it became actively harmful, because it looked like authority and cost the dev half a day. A diagram that lies is worse than having no diagram, because the dev would have read the code directly and wouldn't have gotten confused.

What they should have done: not maintain a class diagram by hand. Correct options: (1) to understand the pricing module, let the dev read the real code —it's always up to date— with perhaps a short README that explains the purpose and the why (which is stable); (2) if they really wanted a visual view of classes, generate it from the code with a tool when needed, so it reflects today's version; (3) document the Component level (more stable) instead of the Code, if what they wanted was to orient. The rule: what changes every day isn't drawn by hand; it's read from the source or generated.

Exercise 3 — The maintenance budget. Your team has time to maintain well three diagrams of a medium-complexity system (an app, an API with two complex flows, a database, a worker). If you had to choose exactly three diagrams for the documentation to be maximally useful and sustainable, which would you choose and why would you leave out the rest?

See solution

The three: (1) the Context, (2) the Container, and (3) a Component of the API's most complex flow.

  • Context (1): cheap to maintain (barely changes), serves the business and any newcomer to understand what the system is. Non-negotiable.
  • Container (2): the technical orientation map; every dev who enters needs it to know where each thing lives. Barely changes (the deployable pieces are stable). Non-negotiable.
  • A Component of the complex flow (3): of the API's two complex flows, you choose the one more people touch or the more tangled; that Component saves hours of getting lost. It's the only level 3 that warrants the expense.

What you leave out and why: the Component of the second flow (if it's less critical, live without it; people can read the code with the Container as a map); any Code diagram (it rots too fast; it's generated or read from the code); and the Component of the simple pieces (app, worker, database), which don't warrant it. With three living diagrams —two stable and one critical— you cover the two audiences (business and devs) and the point where people get lost, and everything is maintained because they're few. It's exactly the result the worked example measured: three, not thirty.

Summary and next step

In this lesson you completed the four Cs: you drew the Component of Mercado's checkout (the neighborhood map, for the dev working inside that complex piece) and saw the Code (the street map) to understand why it's almost never drawn by hand —it rots in hours, the code is a better source of itself, and if anything it's generated—. Above all, you learned the zoom rule: you go down a level only where the detail helps a real person work, and you stop as soon as it stops helping. You measured it: in Mercado, the living documentation is three diagrams (Context, Container, one Component), not thirty —because fewer diagrams, better maintained, get used; thirty rot—.

Before moving on you should be able to: draw a Component of a complex container and decide, container by container, whether it warrants one; explain why the Code is almost never drawn by hand; and apply the zoom rule to choose the small set of diagrams that's really worth maintaining.

What follows is the heart of the module. With the four tools drawn, lesson 5 answers the question that governs them all: which do I give to whom? You'll build, executed, a recommender that matches each audience with its zoom level, and a mismatch detector that measures what happens when someone receives a level too high (drowns in detail) or too low (can't work). It's the step from "I know how to draw the four levels" to "I know how to choose the right one for the person in front of me" —which is, in the end, the whole craft of communicating—.

Resources

  • Simon Brown — Component diagram (c4model.com) — the canonical definition of level 3 and, key, the recommendation to draw it only where it adds value. Contrast it with your checkout Component.
  • Simon Brown — Code diagram (c4model.com) — the definition of level 4 and why its own author recommends not drawing it except in specific cases, and preferring to generate it. The direct source of the "why you almost never draw it" section.
  • Simon Brown — "Diagrams as code" — tools (Structurizr, PlantUML C4) that let you maintain and generate diagrams from text or code, the antidote against the diagram that rots. It anticipates the docs-as-code of lesson 7.
  • Martin Fowler — Software Architecture Guide — for the underlying judgment: what's worth documenting and what isn't. It helps internalize "produce little and alive" as a stance, not just a trick.