Domain-Driven Design

Domain-Driven Design in PHP — Aggregates, Sagas as process managers, Bounded Contexts via Distributed Bus, Domain Events as first-class citizens, on Laravel and Symfony

For a PHP team building DDD-shaped software, Ecotone's vocabulary is DDD vocabulary — Aggregate, Identifier, Repository, Domain Event, Saga (as process manager), Bounded Context — and each pattern is a declarative PHP attribute, not a base class to extend or a contract to implement.

The Problem You Recognize

You've read Evans, you've read Vernon, you understand strategic and tactical DDD. The team has done the modelling work and identified bounded contexts. Now you're trying to express that model in PHP — and the tooling pushes back.

  • Aggregates end up extending a base class from some DDD library that brings in its own command bus, its own event publication mechanism, and its own opinions about repositories.

  • Domain events are dispatched through a third library's event dispatcher, which has its own subscription model that doesn't compose with the application's other event handlers.

  • Sagas are home-grown state machines because the framework doesn't have a saga concept at all.

  • Bounded contexts are aspirational — there's no operational boundary between them; one application database, one shared model, "bounded" only in the wiki.

  • The PHP DDD libraries you've evaluated turned out to be effectively dormant (Prooph's event-sourcing and service-bus packages untouched since 2021; project site frozen at 2019), stable-but-not-evolving (Broadway's last functional release was May 2023; subsequent commits are CI and dependency bumps), or focused on one narrow slice (just ES, just aggregates, just buses).

You want a single, maintained framework where the DDD vocabulary is also the framework's vocabulary.

What the Industry Calls It

Domain-Driven Design — model the business domain as the central concern of the software; use the ubiquitous language of domain experts in the code; segment the system into bounded contexts with explicit translations between them; encapsulate invariants inside aggregates; reflect state changes as domain events; coordinate long-running processes via process managers (often called sagas). Pattern catalog from Evans (2003), refined by Vernon (2013).

In PHP, faithful tactical DDD has historically meant assembling the building blocks from disparate libraries — and the libraries don't share retry policies, transaction boundaries, identity mappings, or event publication. Ecotone provides the full set on one model.

How Ecotone Solves It

Aggregates — invariants, command handlers, events

Plain final class. #[Aggregate] marks the type, #[Identifier] declares the identity, command handlers live on the aggregate (#[CommandHandler] on a static factory for creation, on an instance method for state-changing operations). No base class, no interface. Domain events are plain PHP classes the aggregate records.

For event-sourced aggregates, #[EventSourcingAggregate] with #[EventSourcingHandler] event-application methods — see the Event Sourcing landing for the full story.

Repositories — abstraction without imposition

Repositories use the inbuilt DBAL or Eloquent repository, or implement the Repository contract yourself for any persistence model. The aggregate stays free of persistence concerns; the framework wires the repository in.

Sagas as process managers

The saga is a process manager in Vernon's sense — it remembers state across events arriving over time, decides what to do next, and dispatches commands to keep the process moving. State persisted per #[Identifier]; on each event arrival, Ecotone reloads the saga from the database. Event-to-saga binding via payload field, header, or expression (identifierMapping).

Bounded Contexts — operational, not aspirational

The Distributed Bus and Service Map (Enterprise) move commands and events between PHP services over the brokers you operate. Each bounded context runs as its own deployable with its own database, communicating with other contexts via well-defined domain events on the bus. The translation between contexts becomes explicit: events emitted by Context A are subscribed to by translators in Context B (using #[Distributed] handlers on each side). Strategic DDD made operational.

Domain Events as first-class citizens

Domain events are plain PHP classes — no DomainEventInterface, no recordThat() mixin required by the framework. The aggregate records events on itself; Ecotone publishes them onto the event bus when the aggregate is persisted. Subscribers (other aggregates, sagas, projections, integration handlers) bind via #[EventHandler] with optional identifierMapping or routing constraints.

Value Objects and conversion

Value objects pass through the JMS Converter pipeline cleanly — serialize through type converters; deserialize on the way in. The same #[Sensitive] attribute that encrypts fields in the event store encrypts them in messages on the broker, so PII boundaries respect the domain's classification.

How It Compares

Dimension
Broadway
Prooph
Spatie laravel-event-sourcing
Assemble it yourself
Ecotone

Maintenance status

Stable since May 2023 — recent commits are CI / dependency bumps

Core event-sourcing and service-bus packages untouched since 2021; site frozen at 2019

Active

Yours forever

Active, in production since 2019

Framework support

Symfony-leaning (broadway/broadway-bundle)

Framework-agnostic

Laravel only

You pick

Laravel + Symfony + Ecotone Lite

Aggregate model

Yes

Yes

Yes

Hand-roll

#[Aggregate] / #[EventSourcingAggregate]

Saga as process manager

Limited

Yes (Prooph Service Bus)

Reactors, not full sagas

Hand-roll a state machine

#[Saga] with #[Identifier] mapping

Bounded-context distribution

Out of scope

Manual

Out of scope

Six libraries

Distributed Bus + Service Map

Repository abstraction

Yes

Yes

Eloquent-bound

You pick

DBAL / Eloquent / custom

Shared message-driven middleware (retry, DLQ, dedup, OTel, PII)

No

Partial

No

Per-library

One model across every building block

Domain Event publication

Library-specific

Library-specific

Library-specific

Hand-roll

Plain PHP events on the event bus

PHP's DDD library landscape in 2026 is sparse: Broadway has been on dependency-bump-only maintenance since its May 2023 functional release, Prooph's event-sourcing and service-bus packages haven't received a commit since 2021, and Spatie's library scopes to Laravel-only event sourcing. Ecotone covers the tactical DDD vocabulary as one declarative model on Laravel, Symfony, or Ecotone Lite.

Next Steps

Last updated

Was this helpful?