Domain-Driven Design is an approach that helps build software reflecting real-world business logic. Within DDD, we distinguish building blocks – essential elements of domain modeling. Here’s their breakdown and application:

Core DDD Building Blocks
1. Bounded Context (BC) – Defining Model Boundaries
A clearly defined part of the system where a specific model and business rules make sense.
Example: The “Orders” module in e-commerce has different rules than “Invoices” – they should have separate bounded contexts.
2. Ubiquitous Language – A Shared Domain Language
A unified vocabulary used by both developers and business stakeholders within a specific context.
Example: In a logistics context, “shipment” means something different from “goods” – precise definitions eliminate misunderstandings.
3. Aggregate – Consistency and Integrity
A group of entities and value objects forming a logical whole. The aggregate has a root, which controls access to other objects within it.
Example: In an order system, Order is an aggregate, and its components may include OrderItem or Payment.
4. Entity – Unique Domain Objects
An object identified by a unique identifier that changes its state over time.
Example: A customer with ID 1234 who can change their address and order history but remains the same person.
5. Value Object – Immutable Values
An object that has no unique identity – only its state matters.
Example: A delivery address (city, street, house number).
6. Domain Event – Recording Business Events
Represents something significant that has happened in the domain and has business value.
Example: “Order has been paid” may trigger other processes, such as invoice generation.
7. Repository – Access to Aggregates
An abstraction over the data storage layer.
Example: OrderRepository->findById($orderId);
8. Factory – Creating Domain Objects
Used for constructing complex objects, such as aggregates.
Example: OrderFactory::createNew($customer, $items);
9. Service – Logic That Does Not Belong to Entities
Contains operations that do not fit within a single entity or aggregate.
Example: PaymentService->charge($order);
How to Implement DDD in Symfony? Ecotone as a Solution
Implementing DDD in PHP can be complex, especially with CQRS, Event Sourcing.
This is where Ecotone comes in, simplifying the implementation of these patterns.
Handling Domain Events with Ecotone
Defining a domain event:
class OrderShippedEvent
{
public function __construct(public string $orderId) {}
}
Handling the event:
#[Asynchronous('order_events')]
class OrderShippedHandler
{
#[EventHandler]
public function onOrderShipped(OrderShippedEvent $event): void
{
// Logic after the order has been shipped, e.g., sending an email
}
}
Publishing an event:
$this->eventBus->publish(new OrderShippedEvent($orderId));
CQRS in Ecotone – Separating Reads from Writes
Command:
class CreateOrderCommand
{
public function __construct(
public string $orderId,
public string $customerId
) {}
}
Command handler:
class CreateOrderHandler
{
#[CommandHandler]
public function handle(CreateOrderCommand $command): void
{
// Creating an order in the domain
}
}
Querying data (Query Handler):
class OrderQuery
{
public function __construct(public string $orderId) {}
}
class OrderQueryHandler
{
#[QueryHandler]
public function handle(OrderQuery $query): array
{
// Fetching order data
return ['orderId' => $query->orderId, 'status' => 'shipped'];
}
}
Executing the query:
$result = $queryBus->send(new OrderQuery($orderId));
Event Sourcing in Ecotone
To track domain state changes, you can use Event Sourcing:
class OrderCreatedEvent
{
public function __construct(public string $orderId, public string $customerId) {}
}
#[Aggregate]
class Order
{
#[AggregateIdentifier]
private string $orderId;
private string $customerId;
#[CommandHandler]
public static function create(CreateOrderCommand $command): array
{
return [new OrderCreatedEvent($command->orderId, $command->customerId)];
}
#[EventSourcingHandler]
public function applyOrderCreated(OrderCreatedEvent $event): void
{
$this->orderId = $event->orderId;
$this->customerId = $event->customerId;
}
}
With Ecotone, implementing Event Sourcing becomes straightforward, as it treats events as the source of truth for entities.
Summary – Why Adopt DDD and Ecotone?
- Improved communication between business and IT through Ubiquitous Language
- Consistent models thanks to Bounded Contexts
- Easier system evolution due to well-defined responsibility boundaries
- Increased reliability through Aggregates ensuring consistency
- Ecotone as a simple way to implement Event Sourcing, CQRS, and Domain Events in Symfony
Leave a Reply