Domain-Driven Design in PHP

#[Aggregate], #[AggregateIdentifier], aggregate command handlers, repository abstraction, #[Saga] as process managers, bounded-context isolation via Distributed Bus, Domain Events as first-class plain PHP classes.

Composer package · Laravel or Symfony · PostgreSQL or MySQL · RabbitMQ, Kafka, SQS, Redis, or DBAL outbox

How it compares

PHP alternatives for this category

Each row names what you'd need to add to match Ecotone, and the architectural ceiling that integration cannot fix.

Spatie laravel-event-sourcing

An event-sourcing library, not a DDD framework. Laravel-only.

  • Aggregates extend `Spatie\EventSourcing\AggregateRoots\AggregateRoot`; every domain event must implement the marker interface `ShouldBeStored` — infrastructure bleeds into the domain model.
  • Read models extend the package's Eloquent-backed `Projection` class.
  • Laravel-only — no Symfony parity, no standalone container option.
  • Covers the ES slice of tactical DDD; everything around it (CQRS message buses, saga as process manager, bounded-context distribution, outbox, distributed bus, multi-tenant routing) is your responsibility to assemble from other libraries.
  • No saga / process-manager primitive — Reactors run on live events only, with no identifier-mapped state machine that lives across events arriving over time.
EventSauce

Cleanest DDD ergonomics — but a library, not an architecture.

  • Best DDD ergonomics among pure-ES PHP libraries — no marker interface on events, aggregates use `AggregateRootBehaviour` trait (composition not inheritance), well-documented snapshotting.
  • No CQRS message-bus primitive — bring your own dispatcher.
  • No saga as process manager — process managers are plain `MessageConsumer` implementers you wire yourself.
  • No bounded-context distribution (no Distributed Bus / Service Map equivalent), no orchestrator, no shared middleware across building blocks.
  • The DDD vocabulary Ecotone delivers on one model is five-to-six separate library decisions on top of EventSauce.
Patchlevel event-sourcing

Strong attribute-based aggregates — but an ES package, not a DDD architecture.

  • Strong attribute-based modelling (`#[Aggregate]`, `#[Id]`, `#[Apply]`, `#[Event]`, `#[Stream]`, `#[AutoInitialize]`) with Stream-splitting and Micro-Aggregates for large aggregates.
  • An event-sourcing package — no saga as a first-class process manager (no identifier-mapped state machine across events arriving over time).
  • No bounded-context distribution (no Distributed Bus / Service Map equivalent), no messaging primitives, no async dispatch, no orchestrator.
  • **Production-critical failure mode:** the subscription engine advances position but doesn't isolate failure per partition — one failing handler blocks the whole processing pipeline.
  • Tightly coupled to Doctrine DBAL; Symfony bundle is first-party, Laravel is not officially supported.
Ecotone

DDD vocabulary as PHP attributes — no base classes, no DSL between domain and framework.

  • Aggregates as plain final classes with `#[Aggregate]`.
  • Event-sourced aggregates with `#[EventSourcingAggregate]` and `#[EventSourcingHandler]` event-application methods.
  • Sagas as process managers via `#[Saga]` with `#[Identifier]` mapping events to instances by payload field, header, or expression.
  • Bounded contexts isolated operationally by the Distributed Bus + Service Map — strategic DDD made operational, not aspirational.
  • Domain Events as first-class plain PHP classes — no `recordThat()` mixin, no `DomainEventInterface` to implement, no library-specific event dispatcher to wire.
  • Repository abstraction via the inbuilt DBAL or Eloquent repository, or your own implementation.
  • The four tactical-DDD building blocks (aggregates, sagas, projections, domain events) live on one declarative model — same EcotoneLite testing harness across all four, same retry / dedup / PII / observability middleware applied uniformly, no inter-library wiring to reconcile.

Frequently asked questions

Haven’t found what you’re looking for? Contact us

Ecotone's vocabulary IS DDD vocabulary — #[Aggregate] for state-stored aggregates, #[EventSourcingAggregate] for event-sourced ones, aggregate command handlers via #[CommandHandler] directly on aggregate methods, repository abstraction via DBAL or Eloquent or your own implementation, #[Saga] as process managers with #[Identifier] mapping events to instances, bounded-context isolation via the Distributed Bus + Service Map, Domain Events as first-class plain PHP classes (no marker interface). PHP's historical DDD libraries are dormant: Broadway's last functional release was May 2023 (subsequent commits are CI / dependency bumps); Prooph's `event-sourcing` and `service-bus` packages have not received a commit since 2021, and the project website is frozen at 2019. Ecotone is the actively-maintained tactical-DDD framework on Laravel, Symfony, and Ecotone Lite.
Plain final classes. #[Aggregate] marks the type, #[Identifier] declares the identity, command handlers live on the aggregate (a static factory for creation, an instance method for state-changing operations). No base class, no interface, no recordThat() mixin required. Domain events are plain PHP classes the aggregate records; Ecotone publishes them onto the event bus when the aggregate is persisted.
The Distributed Bus and Service Map 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.
A #[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). #[Delayed] handles saga timeouts without cron.

Be part of the change with EcotoneCurve

Unleash the power of Messaging in PHP
and push productivity to the higher level

Get started
Gradient
Shapes 1
Shapes 2
DiscordTwitterSupport and ContactTelegram