Strategic DDD – Key Building Blocks and Their Role in Architecture

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

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *