Skip to main content

Relationships Between Contexts: Partnership, Customer-Supplier, Conformist

What This Concept Is

Bounded contexts are never independent. Every edge between two contexts is a contract -- a promise about what the upstream provides and what the downstream consumes. DDD names a small set of relationship patterns. Each pattern answers two questions:

  • Who has the power? Does the upstream dictate the contract, or do they negotiate?
  • How is coordination done? Through joint work, formal interface, or not at all?

The six patterns divide into three groups:

Cooperation (teams collaborate intensively):

  • Partnership -- two teams coordinate changes ad hoc. No one team owns the contract. Requires strong communication.
  • Shared Kernel -- two contexts share a small overlapping model, implemented as a shared library or monorepo source. Any change forces joint release.

Customer-Supplier (one team provides, the other consumes):

  • Customer-Supplier -- downstream ("customer") can influence the contract but upstream ("supplier") builds and ships first. Implemented cleanly, this is most modern API relationships.
  • Conformist -- downstream has no influence and accepts the upstream's model as-is. Common when the supplier is external or organizationally dominant.

Separate Ways (teams don't integrate):

  • Separate Ways -- duplicate the functionality in both contexts; integration is not worth the cost. Never use for core subdomains.

(Two further patterns -- Anticorruption Layer and Open Host Service / Published Language -- are translation mechanisms that overlay these relationships. See concept 5.)

Why It Matters Here

You cannot design an integration without naming the relationship. Without the pattern:

  • you end up with an "API" between two teams that nobody owns and everybody breaks
  • you accidentally conform to a messy legacy model and corrupt your own
  • you build a partnership with a team whose only feedback channel is a ticket queue
  • you share a kernel with five teams and cannot release anything

Every edge on your context map (concept 6) is one of these six. Forcing yourself to name the pattern often reveals that the edge does not work the way you thought.

Concrete Example

Case: Parcel Shipping Co. -- six edges, six patterns

Walk through each edge:

  • Pricing -> Shipping (Customer-Supplier): Pricing is upstream. Shipping is a customer. Shipping asks Pricing for features ("add a field for rate_reference so we can reconcile later") and Pricing decides. Pricing commits to not breaking the contract. Two different teams, one language.
  • Shipping ↔ Tracking (Partnership): Both contexts are core, both teams sit together, both know the domain. They coordinate ad hoc when the AWB schema changes. If they drifted apart, this would have to become a formal customer-supplier.
  • Shipping -> Billing (Customer-Supplier): Shipping emits ShipmentBooked events. Billing consumes. Billing cannot ask Shipping for schema changes mid-release; they file a request.
  • Shipping -> Carrier Gateway (Conformist inside our org): The Carrier Gateway wraps external carriers. Internally, shipping conforms to the gateway's uniform DTO. We chose conformist because the gateway's model is already a clean abstraction over chaos.
  • Carrier Gateway -> External carriers (Conformist): We have zero influence on FedEx, UPS, etc. We accept their payload and translate inside the gateway.
  • Okta -> Support (Conformist): Okta is a generic identity provider. We accept their JWT, their group model, everything.
  • Support ↔ Billing (Separate Ways): Support has its own notion of "credit issued to customer." Billing has its own "credit note." We do not integrate; Support staff file a manual ticket in Billing when they issue a credit. Cheap and correct for the volume we have.
  • Shared Kernel rate-lib: Both Pricing and Shipping need rating logic (Shipping must cache the rate on the label). Instead of calling Pricing from Shipping at every booking, both depend on the rate-lib package. Any change to rate-lib forces both to redeploy. We accept this because the rate math is stable and shared exactly.

Power and coordination matrix

PatternPowerCoordinationTypical use
Partnershipbalancedad hoc, intensetwo core contexts owned by close teams
Shared Kernelbalanced, tightjoint releasesmall shared model, usually in a library
Customer-Supplierupstream dominant, but responsivecontract review cadencemost healthy cross-team APIs
Conformistupstream dominant, unresponsivenone; accept or leaveexternal vendor, legacy system, industry standard
Separate Waysn/ano integrationsmall duplicated functionality, too cheap to share

Red flags per pattern

  • Partnership gone bad: silent breakages, "why didn't anyone tell me this field changed?" -> escalate to customer-supplier with a real contract.
  • Shared Kernel gone bad: three teams need to release for every change; the kernel has 40 classes -> shrink the kernel or split it.
  • Customer-Supplier gone bad: supplier ignores customer requests, says "this is our API." -> it is now a conformist edge in all but name. Recognize it.
  • Conformist gone bad: the upstream's messy model leaks into the downstream core. -> add an anticorruption layer (concept 5).
  • Separate Ways gone bad: duplication drifts, key rule is implemented differently in both places. -> promote to customer-supplier or shared kernel.

Common Confusion / Misconception

"Partnership means 'we are friends.'" No. Partnership is a work model: both teams coordinate changes intensively, neither dictates. It requires synchronous communication and a shared release cadence. Two distributed teams with a ticket queue between them are not in a partnership, even if they are friendly.

"Shared Kernel is just a shared library." A shared library is a packaging choice. A shared kernel is a DDD agreement that the two contexts accept joint ownership of a small overlapping model. You can have a shared library that is not a shared kernel (if only one team owns it and publishes versions like a vendor). You can have a shared kernel without a literal shared library (if the two contexts live in a monorepo and import the same source files).

"Conformist is a bad smell." It is not, inherently. If the upstream's model is reasonable (e.g., industry standard, widely adopted), conforming is cheap and correct. Conformist is only bad when the upstream's model is a mess and it pollutes your core. Then add an ACL.

"Customer-Supplier is the default REST API." Close -- if the customer's input actually influences the contract. If it does not, you are in a conformist relationship with extra steps.

"We should not use Separate Ways because duplication is bad." Duplication is cheap when it is rare and small. Coordination is expensive. For generic subdomains with tiny footprints (logging config, feature flags) separate ways is often the right answer.

How To Use It

For every edge on your context map:

  1. Identify upstream vs downstream by data flow.
  2. Ask: can the downstream influence the contract? If no, it is conformist or conformist-with-ACL.
  3. Ask: do the two teams co-design changes, or does one announce and the other adapts? Co-design -> partnership. Announce/adapt -> customer-supplier.
  4. Ask: is there a shared model implemented in both sides? Yes -> shared kernel.
  5. Ask: is there no integration at all, only duplication? Yes -> separate ways.
  6. Write the pattern name on the edge. If the pattern you wrote is not what the reality does, fix the reality or fix the pattern.

Check Yourself

  1. Name one real integration you have seen that was described as "partnership" but was really "customer-supplier with nobody acknowledging it."
  2. Why is "Conformist to a messy upstream" a frequent prelude to introducing an Anticorruption Layer?
  3. A new startup buys Stripe, Okta, and Twilio. How many of its integration edges are Conformist by default, and why is that correct rather than alarming?

Mini Drill or Application

For a real or fictional system with at least six bounded contexts:

  1. List every edge.
  2. For each edge, pick one of the six patterns.
  3. For each Customer-Supplier, name who the customer is and what feedback channel exists.
  4. For each Conformist, name the upstream and state whether an ACL is needed.
  5. For any Shared Kernel, name the shared model and estimate its size (lines of code or classes).
  6. Identify one edge where the pattern is clearly wrong, and propose what to change.

Read This Only If Stuck