0Pricing
PHP Academy · Lesson

Bounded Contexts and Context Mapping

Split large domains into bounded contexts that talk safely.

Bounded Contexts and Context Mapping is a free PHP Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the PHP Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Strategic DDD

Tactical patterns model objects inside a domain; strategic DDD decides how to split a large system into pieces that can evolve independently. The central concept is the Bounded Context — an explicit boundary within which a domain model and its language are consistent. Context Mapping describes how those contexts relate. This is often more impactful than any single class design.

The Ubiquitous Language

Inside a bounded context, the team shares a ubiquitous language — the same terms used in conversation, code and the model. The catch: a word means different things in different contexts. A Customer in Sales (leads, discounts) is not the same as a Customer in Support (tickets, SLAs). A bounded context makes that boundary explicit instead of forcing one bloated shared model.

Same word, different models

The same business concept gets a context-specific model. A Product in Catalog cares about descriptions and media; in Inventory it cares about stock and location. Each context defines only what it needs.

<?php
namespace Catalog;
final class Product {
    public function __construct(
        public readonly string $sku,
        public readonly string $description
    ) {}
}

namespace Inventory;
final class Product {
    public function __construct(
        public readonly string $sku,
        public readonly int $onHand
    ) {}
}

namespace App;
$c = new \Catalog\Product('A1', 'Blue mug');
$i = new \Inventory\Product('A1', 42);
echo $c->description, ' / ', $i->onHand, PHP_EOL;

Why not one big model

Trying to make one canonical Customer serve every team produces a god class with conflicting requirements, contention between teams, and brittle change. Bounded contexts let each team own its model and language, deploy independently, and translate at the edges. The boundary is where you pay a small integration cost to avoid a large coupling cost.

Context maps

A Context Map is a high-level picture of your bounded contexts and the relationships between them. It records both the technical integration and the team/political relationship (who depends on whom, who can dictate change). DDD names several recurring relationship patterns, which we survey next.

Conformist in code

Relationships are often directional — an upstream context influences a downstream one (Customer/Supplier, Conformist, Anticorruption Layer). A Conformist downstream has no leverage, so it simply adopts the upstream's model as-is: it consumes the foreign shape directly, accepting the coupling.

<?php
// Upstream's shape, used verbatim downstream (Conformist)
final class UpstreamUser {
    public function __construct(public readonly int $userId, public readonly string $login) {}
}
final class Greeter { // conforms: depends directly on upstream type
    public function greet(UpstreamUser $u): string {
        return "Hello {$u->login} (#{$u->userId})";
    }
}
echo (new Greeter())->greet(new UpstreamUser(7, 'jane')), PHP_EOL;

Anticorruption Layer

An ACL translates an external/legacy model into your context's clean model so foreign concepts never leak inward. It is the Adapter pattern applied at a context boundary, defending your ubiquitous language.

<?php
// Our clean domain model
final class Customer {
    public function __construct(public readonly string $id, public readonly string $name) {}
}
// Legacy/external shape we must not let leak in
$legacy = ['CUST_NO' => '0042', 'FULL_NM' => 'Jane Doe'];

final class LegacyCustomerAcl {
    public function toDomain(array $row): Customer {
        return new Customer($row['CUST_NO'], $row['FULL_NM']);
    }
}
$customer = (new LegacyCustomerAcl())->toDomain($legacy);
echo $customer->id, ' ', $customer->name, PHP_EOL;

Shared Kernel in code

  • Shared Kernel: two contexts share a small, jointly-owned subset (below, a common Money type) — change it only by agreement.
  • Partnership: two teams succeed or fail together.
  • Separate Ways: integration isn't worth the cost.
  • Open Host Service / Published Language: upstream offers a stable, documented protocol for many consumers.
<?php
namespace Shared; // small, jointly-owned kernel
final class Money {
    public function __construct(public readonly int $cents, public readonly string $cur) {}
    public function format(): string { return number_format($this->cents / 100, 2) . ' ' . $this->cur; }
}

namespace Billing;          // depends on the shared kernel
use Shared\Money;
function invoiceTotal(): Money { return new Money(4999, 'USD'); }

namespace Payroll;          // also depends on the SAME shared kernel
use Shared\Money;
function salary(): Money { return new Money(500000, 'USD'); }

Integrating contexts in PHP

Concretely, contexts integrate via well-defined contracts: a translating method, an API client wrapping a Published Language, or messages. The receiving side maps the foreign DTO into its own model so coupling stops at the boundary.

<?php
// Published Language DTO from an upstream Open Host Service
final class PricingDto {
    public function __construct(public readonly string $sku, public readonly int $cents) {}
}
// Our downstream model
final class CatalogPrice {
    public function __construct(public readonly string $sku, public readonly float $amount) {}
}
final class PricingTranslator {
    public function translate(PricingDto $dto): CatalogPrice {
        return new CatalogPrice($dto->sku, $dto->cents / 100);
    }
}
$p = (new PricingTranslator())->translate(new PricingDto('A1', 1599));
echo $p->amount, PHP_EOL; // 15.99

Contexts and architecture

Bounded contexts map naturally onto deployment units: a modular monolith with clear module boundaries, or separate microservices. The boundary should be drawn around the business capability, not technical layers. Microservices that split a single tightly-coupled context create a distributed monolith; well-aligned context boundaries make services that change independently.

Events as the seam

In practice, a clean way for contexts to integrate without sharing models is to exchange events. The upstream publishes a small, stable event (a Published Language); the downstream subscribes and maps it into its own model — never importing the upstream's classes.

<?php
// Published Language: a minimal stable contract
final class OrderPlacedEvent {
    public function __construct(public readonly string $orderId, public readonly int $cents) {}
}
// Downstream (Shipping context) maps it to its own model
final class Shipment {
    public function __construct(public readonly string $forOrder) {}
}
final class ShippingListener {
    public function on(OrderPlacedEvent $e): Shipment {
        return new Shipment($e->orderId); // ignores fields it doesn't need
    }
}
$shipment = (new ShippingListener())->on(new OrderPlacedEvent('o1', 1300));
echo 'shipment for ', $shipment->forOrder, PHP_EOL;

Quick Check

Context relationships.

Recap

You moved from tactical to strategic DDD. A Bounded Context is the boundary within which a model and its ubiquitous language stay consistent, freeing teams from one bloated shared model. A Context Map documents the relationships — Customer/Supplier, Conformist, Anticorruption Layer, Shared Kernel, Partnership, Separate Ways, Open Host Service/Published Language — capturing both technical integration and team dynamics. Draw boundaries around business capabilities, translate at the edges, and your system can evolve in independently deployable parts.

Frequently asked questions

Is the “Bounded Contexts and Context Mapping” lesson free?

Yes — the full text of “Bounded Contexts and Context Mapping” is free to read here on the web, and the PHP Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the PHP Academy course, upgrade to CoddyKit PRO.

What will I learn in “Bounded Contexts and Context Mapping”?

Split large domains into bounded contexts that talk safely. You practise PHP Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start PHP Academy?

No prior experience is required. PHP Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Bounded Contexts and Context Mapping” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this PHP Academy lesson?

Yes. Every PHP Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. DDD Building Blocks: Entities and Value Objects
  2. Aggregates, Repositories and Factories
  3. Domain Events and Domain Services
  4. Bounded Contexts and Context Mapping
← Back to PHP Academy