Software Design Reference Initiative Lead · Design the System

Lesson 0044 · Initiative Lead · Module 2

Draw the Context, Then Choose Boundaries

The valuable architecture diagram is not the most detailed one. It is the smallest view that lets its audience inspect a decision.

Reading ≈ 8 minPractice ≈ 60 min

Mission tie-in: system design turns drivers and workflows into boundaries with controlled dependency direction. Diagrams are review instruments, not wall art.

Knowledge: three views, three questions

Start with a system context view: the system in scope, the people who use it, and external systems it touches. This answers what is ours, who needs it, and where are the external dependencies and trust boundaries?

Then draw a container view—C4's word for a deployable application or data store, not specifically Docker. This answers what runs, what stores data, how do they communicate, and who owns each part? Finally, draw one dynamic view for the risky workflow. This answers what happens in time when the happy path or failure case runs?

Returns pilot · minimum architecture packet
ViewElementsDecision exposed
Contextcustomer, support agent, returns system, order platform, carrier, paymentsScope, actors, external ownership, PII and trust crossings
Containerexisting web app, returns module, existing database schema, background workerOne deployable plus worker; no new service fleet
Dynamicrequest → decision → persisted intent → label attempt → notificationCarrier failure cannot lose the accepted request

For a 6–8 person cross-functional team, a modular monolith is the responsible default: one deployable, explicit modules, one composition root, and enforced dependency rules. It minimizes coordination and operational surface while preserving seams. Choose another deployable only when a driver requires independent scaling, isolation, security, availability, release cadence, technology, or ownership—and record the cost.

The principle behind the principle

A diagram is a compression of a system for one audience's decision, and compression only helps when the discarded detail is irrelevant to that decision. Mixing levels forces every reader to filter, and filtering is where misreading happens. That is why C4 separates context, container, and component: not as a documentation standard to complete, but so each picture can throw away everything the current argument does not need.

The deeper mechanism is about the cost of a boundary over time. A boundary you can change in a pull request costs a code review, once. A boundary you can only change by coordinating two deployments costs a release plan, every time, forever. Crossing a process boundary converts compile-time errors into runtime errors, refactorings into migrations, and function calls into partial failures you must now design for. So the price of a module seam is paid once and the price of a service is paid weekly—which is why, for one team, the module seam is the default and the service needs a driver to justify its rent.

The default flips under named pressure: genuinely different load shapes that must scale independently, a regulatory or blast-radius isolation requirement, a release cadence owned by someone else, or a technology that cannot live in the same process. It also flips on people. Around three teams sharing one deployable, the coordination cost inside the codebase starts to exceed the operational cost of separate ones, and the same seam that was an asset becomes a queue.

Inside the deployable, draw modules around four kinds of evidence:

Keep technology at the edges, as lesson 0023 established. The domain-facing interface says create_label(intent), not carrier_sdk.shipments.post(payload). On the diagram, label every arrow with the interaction and protocol. Unlabelled arrows conceal assumptions.

A diagram should carry a title, scope, audience, date/version, legend, ownership, and the significant qualities or risks it addresses. Delete boxes that do not help the stated audience make a decision. Code-level detail belongs in code unless it explains an unusually important mechanism.

How the lead actually reviewed it

Week 5, container view on the screen. The lead looks at the arrows first, not the boxes, and specifically at which arrows cross an ownership line. Her question to the room is operational, not aesthetic: who is on call when this arrow fails at two in the morning? Three arrows have an answer. One—the call into the order platform—does not, which is how Asha's team becomes a named adviser on the contract rather than an assumed dependency.

Dana proposes extracting an Eligibility Service. Her reasoning is good: policy changes weekly and the rest of the system does not, so why should a policy change redeploy the carrier code? The lead rejects the extraction and says why in the same terms. Release cadence is only a driver when the cadences belong to different owners; here one team owns both, so an enforced module seam buys the same isolation of change for none of the operational cost. Dana's counter is recorded rather than dismissed: if returns policy moves to a second team next quarter, the seam must become a service. That sentence becomes the revisit trigger on the ADR in lesson 0046.

The seam is then made real, because an agreed boundary that nothing enforces is a naming convention. The team writes an import rule as a test that fails the build:

import ast
import pathlib

ALLOWED = {                      # a module may import only what is listed here
    "journey": {"eligibility", "shared"},
    "eligibility": {"shared"},
    "carrier": {"shared"},
    "shared": set(),
}


def imported_roots(path):
    tree = ast.parse(path.read_text(encoding="utf-8"))
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module:
            yield node.module.split(".")[0]


def test_modules_depend_only_downward():
    root = pathlib.Path("returns")
    for path in root.rglob("*.py"):
        owner = path.relative_to(root).parts[0]
        for target in imported_roots(path):
            if target in ALLOWED and target != owner:
                assert target in ALLOWED[owner], f"{path}: {owner} -> {target}"

Ten weeks later the system is one deployable and one worker: two processes on call rather than five, and the journey module still cannot reach the carrier adapter directly, because the build says so. The cost the team accepted is visible too—a policy release redeploys everything, and twice the team waited for an unrelated change to clear the pipeline.

Field notes · diagram smells
You seeWhat it hidesThe move
one diagram with 70 boxesAudience and decision are undefined.Split by scope and question.
arrows without labelsProtocol, direction, and meaning.Name every interaction.
database shared by “services”State ownership is fictional.Name the writer or merge boundaries.
a service per domain nounDeployment cost has no driver.Return to workflow and change.
What people get wrong after reading this
A modular monolith is a stage on the way to microservices.
It is a design in its own right. The seam is the asset; extracting it is one option the seam keeps open, not a destination. Many systems should never take the second step, and saying so out loud prevents a team from building for a migration that will not happen.
Microservices give teams autonomy.
Services give deployment independence. Autonomy comes from owning a boundary with stable contracts. Split a tangle into services and you get the same coupling with network calls and a release calendar on top.
The C4 levels are a documentation set to complete.
You draw the level whose decision is live. A container view for a system nobody is arguing about is wall art that will be stale in a month.
The diagram should match the code exactly.
It should match the decision. A diagram maintained at code fidelity is either generated or lying; a diagram maintained at decision fidelity stays useful for years.

When this is the wrong move

Do not draw the packet when the audience already shares the model—redrawing the context view for the third time to the same four people is ceremony. Do not draw it when the question can be settled by a twenty-line spike, and do not draw a container view for a system with one container. The counter-case for the enforced import test is smaller than it looks, but it exists: in a codebase of three modules whose owners sit together and review everything, the test costs more attention than the mistake it prevents. Add it when the team grows past the point where every import is seen by someone who cares.

Skill: draw only what earns its place

A context diagram primarily establishes:

When should this team add a deployable?

The critical dynamic view should show:

Practice: make a three-view architecture packet

Draw a context diagram, a container diagram, and one dynamic diagram for your riskiest workflow. On each: state audience and question; label every boundary, owner, arrow, and data store; annotate which driver it addresses. Run a review where every comment must point to a driver, workflow, or risk.

In the lab and the capstone. The lab's phase-4 checkpoint asks you to record one boundary decision as an ADR and to enforce it with a test like the one above, so the seam survives the next contributor. The worked artifact is the three-view architecture packet in the strong capstone packet, where every arrow is labelled and every view names the driver it answers. Compare with the weak version: one crowded diagram, a service per noun, unlabelled arrows, and a shared database drawn as though ownership were obvious.

Reveal: the service-extraction test

Before extracting a deployable, complete this sentence: “We accept network failure, versioning, deployment, observability, security, and on-call overhead because independent _____ is required by driver _____.” If the blanks contain “cleanliness” or “maybe later,” keep a module seam and postpone the distributed system.

Teach it back
  1. In two sentences, explain to a new lead why a module seam is cheap and a service boundary is a subscription.
  2. Take your current system. Name one boundary you would enforce with a test this week, and the mistake that test would have caught in the last three months.
  3. Predict the failure: if a six-person team splits into five services now, what does their on-call rota look like in a quarter, and which change will require three coordinated deploys?

Your win

You can communicate system scope and runtime behavior, choose evidence-based module boundaries, and resist deployment boundaries whose costs have no driver.

Read and watch deeper

Show the diagrams to your agent-teacher with the driver set and ask which box or arrow has no justification and which driver has no visible response.