Why DDD: bounded context

The problemEvery service is arranged its own way: its own directory layout, a muddle of terms, a wheel reinvented and the same mistake made for the hundredth time. What you know about one service does not carry over to the next — not for the author, not at review, not for whoever arrives a year later.

DDD answers that with a single agreement: a service has levels, and each level has exactly one job. Knowledge only runs from the outside in: the transport, the application and the database know about the domain, and the domain knows nothing about them. That is why the domain depends on neither the database nor the protocol of the call.

Transportwhere we are called, and which code we answer with1Applicationwhat jobs the software does2Aggregatewhich states are possible3Repositoryhow the aggregate reaches storage and comes back4Bounded contextwhat we own and what we call it5
  1. Transport

    where we are called, and which code we answer with

    The point at which the service is called: by a person or by another system. Data arrives here, and the answer to the request leaves from here.

    No decision about the domain is made at this level — transport translates: protobuf, JSON or Avro into domain entities and back, through a DTO, one per protocol.

    The DTO goes no further: what reaches application is a command built from domain entities rather than whatever the caller sent. How a command differs from a query, and why it is worth naming as a type, is in the chapter on CQRS.

  2. Application

    what jobs the software does

    The level that defines the jobs the software is supposed to do. One business scenario is one use case with its own logic: complete a sale, issue an invoice, refund money.

    The logic here is the order of the steps: fetch the aggregate, call it, save it, announce what happened.

    No domain rules live at this level and it checks none: application hands the work to the domain and holds only the progress of the operation — the transaction, the permissions, idempotency.

  3. Aggregate

    which states are possible

    A cluster of objects that changes as one whole, and a root through which everything outside refers to it.

    The point of that boundary is the invariant: a statement that has to hold after every change. The lines add up to the invoice total; an invoice that is already paid does not change its amount.

    The aggregate checks its own invariants, inside one transaction — which is why from the outside it cannot be left in a state the domain does not allow.

  4. Repository

    how the aggregate reaches storage and comes back

    The port is declared by the domain: take an aggregate by id, save it together with the facts it produced.

    The implementation is written by infrastructure — rows, indexes, one transaction; a table row here is the same kind of DTO, only for the database.

    Knowledge runs from the outside in: the database knows about the aggregate, the aggregate knows nothing about the database.

  5. Bounded context

    what we own and what we call it

    The boundary inside which a word has one meaning. Everything else lives outside it and is translated at it.

    This decision comes before the others: until it is said what the context owns and which words it uses for that, there is nothing to build the other levels out of.

What follows is a walkthrough of one example. An online shop: the catalogue, billing and delivery.

Say “payment” inside it and every department hears its own thing.

  • Product. The purchase went through, the user saw the checkmark.
  • Processing. The money is held, nothing was captured.
  • Accounting. The money will show up in a statement two days later, or will not show up at all.

Three different events under one word — and that is where the walkthrough starts.

  1. Step 1 of 12

    C1: what a bounded context is

    The diagram on the right is C1: the system as a whole, one block.

    The three departments from the example are three contexts, and each has its own truth about the word "payment". Arguing over which one is correct goes nowhere: all three are, in different places.

    A bounded context is such a place: the stretch of a system inside which a word has one meaning, with its own model and its own team answering for both.

    The language sets the boundary. Where a word starts to mean something else, the context has ended; the directory layout and the database schema have nothing to do with that boundary — they are chosen inside it.

  2. Step 2 of 12

    What an online shop is made of

    A customer walks into the shop, and inside the shop there are three areas.

    • Catalogue — the basket and the checkout.
    • Billing — what the customer owes and what has been paid.
    • Delivery — parcels, carriers and where the order is now.

    Three areas, three teams, three glossaries. There is one arrow so far and it points at the shop as a whole: who talks to whom inside comes later.

  3. Step 3 of 12

    Core, supporting, generic

    The same three areas, now labelled by what each is to the business.

    • Core is what makes this shop different from the one next door: the catalogue and billing. Built in-house and staffed with the best people.
    • Supporting is needed but gives no competitive edge: delivery.
    • Generic is a solved problem, the same everywhere: authentication. The customer logs in there first and only then walks into the shop, and on the diagram it stands outside the boundary: nobody builds it — it is bought. Writing your own login gives the shop nothing somebody else's would not.

    The distinction is practical: it decides where the money and the people go.

  4. Step 4 of 12

    One word, three meanings

    The customer pays, and the arrow no longer points at the shop as a whole but at billing: billing is what owns the word "payment".

    Until the boundary was drawn, the "payment" from the example meant three different events, and everybody nodded at the meeting, each keeping their own meaning; in code that shows up like this: one service's payment.completed is about the checkmark, another's is about money in the account.

    So the first file in billing's directory is a glossary.

  5. Step 5 of 12

    What is inside the glossary

    One line per word: the word and a single definition — the short sentence the team is willing to defend. Synonyms have no place here: two words with one meaning mean an argument that is still going.

    The table about the three departments comes first: it picks no winner, it says what each of the three meanings is called here.

    The glossary is a file beside the code, edited in the same commit; a wiki lives its own life and six months later disagrees with what the service does. The rule is simple: a word that is not here is not in the code either. Name it first, write it after.

  6. Step 6 of 12

    C2: domains inside the context

    Down to C2.

    A context is not one box. Inside it there are domains, each with its own state and its own rules. Billing has three, and all three are named with words from its glossary.

    • Invoice — what the customer owes: the lines, the total, the state.
    • Payment — the attempt to collect the money: authorization, capture, decline.
    • Settlement — whether the money reached our account and whether it matches the statement.

    Domains have kinds too: inside a core context not everything is core. Invoice and payment are what billing exists for; settlement is needed but gives no edge.

    The catalogue and delivery have gone grey: from here on the walkthrough is about billing, and relative to billing the neighbours are external services. What is inside them, and where they keep it, is none of billing's business.

  7. Step 7 of 12

    A database per domain

    A domain's state lives in a database of its own. Three domains, three databases, and no shared one.

    That buys the thing that matters: "do not read somebody else's tables" stops being an agreement people forget while on call. There are no other tables next door — invoice physically cannot read payment's.

    The price is just as clear: two domains can no longer be changed in one transaction. So consistency between them is asynchronous, which is the next step.

  8. Step 8 of 12

    A bus for asynchronous interaction

    Billing is back as one card here, without its domains: on the bus it is services that talk.

    The catalogue does not call billing — it publishes the fact that an order was placed. Billing reads that fact and issues an invoice. It publishes the paid invoice as a fact too, and delivery reads it and starts packing.

    Who is subscribed is not the publisher's business. That buys the thing that matters: a second listener appears, and billing does not change.

  9. Step 9 of 12

    Who moves the money

    Billing does not move the money. It asks the provider and waits for the answer — a synchronous call, and a decline is an answer too.

    The provider is outside the boundary: it is not ours, its glossary is not ours, and the conversation goes through translation. Billing keeps its own state regardless — what was asked and what came back is written down here, not only at the provider.

    That finishes the context diagram: what the context owns, where its state is, who it calls and who it tells. Next, billing's own C2: the parts it is built of inside.

  10. Step 10 of 12

    C2: the whole picture

    C2 in full: billing with its three domains and their databases, the neighbours in the shop, the bus, and the external services — authentication and the payment provider.

  11. Step 11 of 12

    C2: one domain and nothing else

    From here only billing is left, and inside billing only the invoice.

    The shop with its neighbours has been shown and is no longer needed in the frame. Payment and settlement were there to show that a context is not one domain: several services live inside its boundary, each owning its own piece of the work — issue the invoice, take the money, reconcile with the statement. From here the story runs through the invoice, and the rest would only distract.

    What remains is one domain, its database, and the bus it tells that an invoice was paid. From this frame on, it is code.

  12. Step 12 of 12

    Modules inside the domain

    A domain is not one lump of code. The tree on the right shows three levels: billing is the context, and the glossary sits at its root because there is one language per context; invoice is a domain inside the context; the directories inside the domain are its modules. There are three of them.

    • issuing — issue the invoice: turn the "order placed" fact into lines, a total and a due date.
    • payment — apply a payment: put the money against the invoice and tell the outside that it is paid.
    • overdue — chase the late ones: find the invoices whose due date has passed.

    What splits them is the use case, not the layer. Such a module is a vertical slice: transport, use case and the work with the aggregate live inside it together — not "all the handlers here, all the repositories there".

    The entry points already show it: issuing is called by the bus, payment by a synchronous call from processing, overdue by a schedule. Three different ways in, and none of them belongs to the domain.

Use ← and → to move between steps.