03 / 04 Boundaries and contracts between services: bounded contexts, shared kernel and published language
  1. ← Service architecture models: integration, autonomy and federation
  2. 01 Integrated monolith and autonomous services: two opposite answers to concept sharing
  3. 02 The federated model: control plane, local projections and reconciliation
  4. 03 Boundaries and contracts between services: bounded contexts, shared kernel and published language
  5. 04 Choosing your model: decision grid, warning signs and migration trajectories
Service architecture models: integration, autonomy and federation · 03 / 04

Boundaries and contracts between services: bounded contexts, shared kernel and published language

Why 'customer' does not mean the same thing in two services, why the shared types library is a trap, and which Domain-Driven Design patterns healthily organize the relations between services.

The first two chapters described how to organize cross-cutting concepts. This chapter explains why the federated model is the right one, using the conceptual tools of Domain-Driven Design. Because DDD formalized, twenty years ago, exactly the problem we are handling: what happens when the same word designates different things depending on where you stand?

By the end of this chapter, you will be able to read a multi-service system as a map of bounded contexts, situate the six main relationship patterns between contexts, recognize the shared kernel anti-pattern and its symptom (the distributed monolith), and formulate the minimal conventions an ecosystem must publish to stay consistent without coupling itself.

Same word, different meanings: the bounded context

Domain-Driven Design starts from a linguistic observation: in any organization, the meaning of a word depends on context. “Customer” does not designate the same thing for the sales department (a signed prospect), accounting (an account to invoice) and support (a contract holder). Forcing a single definition produces a monstrous model that satisfies nobody.

DDD names bounded context the boundary within which a model and its vocabulary are consistent and unambiguous. The central lesson: do not unify the models, unify the map. You accept several definitions of “customer”, one per context, and you make the relations between contexts explicit.

Verify it yourself on our commerce suite. Browse the three contexts below and observe: no attribute coincides, except the identifier. Your mission: figure out what would happen if these three definitions were merged into a single universal “Customer” entity.

The same word, seen from three contexts

Customer = a visitor with preferences

  • customer_idlocal identifier
  • language, currency
  • browsing history
  • wish lists

Here, the customer is a personalization profile. No notion of payment or legal obligation: this context does not need them, and its model stays simple precisely because it ignores the rest.

The merge would produce an entity with thirty attributes of which each service would use a fifth, subject to the payment context’s regulatory constraints even to display a wish list, and modified by three teams with incompatible agendas. That is exactly the “monstrous model” the bounded context avoids.

Same name, same intuition, three definitions. They share a link (it is “the same” customer in the user’s eyes), but neither their attributes, nor their lifecycle, nor their invariants coincide. Each service is a bounded context, and that is perfectly fine.

The relationship map: six patterns to know

Once the contexts are laid out, DDD names the possible relations between them: this is context mapping. Six patterns cover most of the territory:

PatternRelationWhen to use it
PartnershipTwo teams co-evolve their contexts, linked successTwo services at the heart of the same critical flow
Customer-SupplierDownstream expresses needs, upstream plans for itThe supplier agrees to serve its consumer
ConformistDownstream adopts the upstream model as-isUpstream does not negotiate (dominant external service)
Anticorruption LayerDownstream translates the upstream model into its ownProtecting your model from an imposed or legacy upstream
Open Host ServiceUpstream publishes a stable API open to allA service consumed by many clients
Published LanguageA common exchange format, published and versionedWhole ecosystem, broad interoperability

Two of these patterns deserve a closer look, because they are the two poles of our subject: the shared kernel (which the table deliberately omits, we are coming to it) and the Open Host Service + Published Language pair, which describes exactly the federation contract of chapter 2: each service publishes a stable API (OHS) whose conventions (external references, minimal semantics) constitute a published language (PL).

The anticorruption layer also deserves a word: it is the translation layer a context places at its boundary to convert someone else’s model into its own. When the support service consumes the payment service’s events, it does not store “a transaction”: it translates it into “a possible contact reason”. The foreign model never penetrates; it is translated at customs. That is the concrete mechanism that keeps bounded contexts watertight.

The anti-pattern: the involuntary shared kernel

Facing our three definitions of “customer”, the temptation from chapter 1 comes back in a sneakier form: “since the services are separate, let us at least share the types”. A common library, suite-core, defining the Customer structure once and for all, imported by every service.

DDD calls this pattern the shared kernel: a model fragment jointly owned by several contexts. And it comes with a warning the industry learned the hard way: a shared kernel is only viable between very close teams, continuously coordinated, on a tiny scope. Everywhere else, it produces the worst of both worlds:

  • Every evolution of the shared type requires re-versioning and re-deploying all the services importing it: you have recreated the monolith’s evolution coupling.
  • But without the monolith’s benefits: no common transactions, no native integration, and the operational complexity of distribution on top.

This outcome has a name: the distributed monolith. Services technically separated, logically welded together. You pay the price of both architectures and collect the dividends of neither.

The involuntary shared kernel: a type owned by nobody, imported by everybody

Note the nuance: publishing a types library from one service, towards its clients (the payment service’s contract, consumed by whoever wants to call it) is healthy: the dependency follows the direction of the call and the provider versions its contract. That is the Open Host Service. What is toxic is the type owned by nobody and imported by everybody.

The healthy pattern: the published language

If you share neither a database (chapter 1) nor types (above), what is left? DDD’s answer is called the published language: a published exchange language, stable, versioned, that each context translates to and from its internal model.

The canonical examples are the great interoperability standards: federated identity protocols, normalized event formats, open API specifications, accounting or banking exchange formats. None shares internal types; all publish a boundary format that everyone implements at home.

For a federated ecosystem, the published language takes a concrete and minimal form, in three conventions:

  1. A vocabulary and a minimal semantics. What a “shop” is for the ecosystem: a stable key, a display name, membership in an organization. Nothing more: each context enriches locally.
  2. A normalized identifier scheme. The format of external references (suite:org/{org}/shop/{key}…): structured for the control plane that emits them, opaque for the services that store them.
  3. The federation contract. Every building block exposes an idempotent upsert by external reference on its topological entities, documented in a versioned API specification.

One documentation page is enough to fix these three conventions. It is the most profitable investment of the whole architecture: it costs one decision, and it saves years of coupling.

The complete map

You now hold the complete reading of the first three chapters:

QuestionAnswerConceptual tool
Who owns the topology?The control planeFederation (ch. 2)
Who owns the business resources?Each serviceBounded context
What does “customer” mean?One definition per contextLocal ubiquitous language
What is shared between services?Identifiers + conventions, never typesPublished language
How do you protect yourself from a foreign model?Translation at the boundaryAnticorruption layer
How do we converge?Idempotent reconciliationDeclarative desired state
What do we never do?The types library imported by allShared kernel anti-pattern

This grid applies far beyond commercial software suites: development platforms, industrial systems, internal microservice ecosystems of a large organization, information system mergers after an acquisition. Everywhere, the same tension between integration and autonomy, and everywhere the same way out: sovereign contexts, a published language, declarative convergence.

Check your understanding

Quiz
  1. 1. What does a bounded context designate in DDD?

  2. 2. What is the role of an anticorruption layer?

  3. 3. Why is the types library shared between all services an anti-pattern?

  4. 4. Which pair of DDD patterns describes the federation contract of chapter 2?

Key takeaways

  • Each service is a bounded context: “customer” has a legitimate local definition there. You do not unify the models, you unify the map.
  • Context mapping names the possible relations: partnership, customer-supplier, conformist, anticorruption layer, open host service, published language.
  • The involuntary shared kernel (the types library imported by all) produces the distributed monolith: the monolith’s coupling, the distribution’s complexity, the benefits of neither.
  • The published language is the healthy pattern: minimal vocabulary, normalized identifier scheme, federation contract, all versioned and documented.
  • Between contexts circulate identifiers and messages, never internal structures; the anticorruption layer translates at the boundary.
  • One practical question remains: facing a real system, how do you choose your model, and how do you migrate from one to another? That is the subject of the last chapter.