Volver a la página principal
viernes 31 enero 2025
6

DDD: Domain Services y Agregados en Diseño de Software con PHP

En el mundo del diseño de software, existen historias que, aunque no son tan conocidas, tienen un impacto profundo en cómo construimos sistemas robustos y mantenibles. Hoy vamos a explorar una de esas historias: la relación entre Domain Services y Agregados, una de las más bonitas pero también más incomprendidas en el diseño de software.

Esta historia no solo nos enseña cómo mejorar la calidad de nuestro código, sino también cómo evitar que nuestros sistemas se vuelvan "anémicos" y pierdan su esencia. ¿Listos para sumergirnos en este fascinante mundo? ¡Vamos allá!

¿Por Qué Domain Services y Agregados Parecen Aceite y Agua?

En el diseño de software, especialmente en el contexto de Domain-Driven Design (DDD), los Agregados y los Domain Services a menudo parecen no llevarse bien. Los Agregados son responsables de encapsular la lógica de negocio y mantener la consistencia de los datos, mientras que los Domain Services se encargan de operaciones que no pertenecen naturalmente a una entidad específica.

En muchos casos, los desarrolladores caen en la trampa de crear Agregados anémicos, donde la lógica de negocio termina en los controladores o comandos, en lugar de estar dentro de los propios Agregados. Esto hace que el código sea menos expresivo y más difícil de mantener.

Pero, ¿y si te dijera que Domain Services y Agregados pueden tener una relación armoniosa? Vamos a ver cómo.

Un Ejemplo Práctico: Procesamiento de Pagos

Imaginemos un escenario común: un sistema de pedidos donde necesitamos procesar pagos. En este caso, tenemos un Command Handler responsable de manejar el comando PayOrderCommand, que procesa el pago de un pedido.

Versión Anémica: Cuando la Lógica de Negocio Vive Fuera del Agregado

En la versión anémica, el Command Handler se encarga de casi todo:

1. Busca el pedido en el repositorio.

2. Invoca un Payment Processor (como PayPal o Stripe) para procesar el pago.

3. Cambia el estado del pedido manualmente.

class PayOrderCommandHandler
{
    private $orderRepository;
    private $paymentProcessor;

    public function __construct(OrderRepository $orderRepository, PaymentProcessor $paymentProcessor)
    {
        $this->orderRepository = $orderRepository;
        $this->paymentProcessor = $paymentProcessor;
    }

    public function handle(PayOrderCommand $command)
    {
        $order = $this->orderRepository->find($command->getOrderId());
        if (!$order) {
            throw new OrderNotFoundException();
        }

        $this->paymentProcessor->pay($order);
        $order->setStatus(OrderStatus::PAID);
    }
}

En este enfoque, el Agregado (en este caso, el pedido) es prácticamente un "objeto tonto" que solo almacena datos. La lógica de negocio está dispersa en el Command Handler, lo que hace que el código sea menos mantenible y más propenso a errores.

Versión No Anémica: Delegando la Lógica al Agregado

En la versión no anémica, el Agregado toma el control. El Command Handler simplemente orquesta las dependencias y delega la lógica de negocio al propio Agregado.

class PayOrderCommandHandler
{
    private $orderRepository;
    private $paymentProcessor;

    public function __construct(OrderRepository $orderRepository, PaymentProcessor $paymentProcessor)
    {
        $this->orderRepository = $orderRepository;
        $this->paymentProcessor = $paymentProcessor;
    }

    public function handle(PayOrderCommand $command)
    {
        $order = $this->orderRepository->find($command->getOrderId());
        if (!$order) {
            throw new OrderNotFoundException();
        }

        $order->pay($this->paymentProcessor);
    }
}

Aquí, el método pay del Agregado Order se encarga de procesar el pago y cambiar el estado del pedido. Esto no solo hace que el código sea más limpio, sino que también encapsula mejor la lógica de negocio.

class Order
{
    private $status;

    public function pay(PaymentProcessor $paymentProcessor)
    {
        $paymentProcessor->pay($this);
        $this->status = OrderStatus::PAID;
    }
}

¿Por Qué Este Enfoque es Mejor?

1. Encapsulación: La lógica de negocio está donde debe estar: dentro del Agregado.

2. Mantenibilidad: Cambiar la lógica de negocio es más fácil, ya que no está dispersa en múltiples lugares.

3. Testabilidad: Es más fácil probar la lógica de negocio, ya que está concentrada en un solo lugar.

4. Expresividad: El código es más claro y expresivo, lo que facilita su comprensión.

Conclusión

La relación entre Domain Services y Agregados puede parecer complicada al principio, pero cuando se entiende y se aplica correctamente, se convierte en una de las historias más bonitas del diseño de software. Al delegar la lógica de negocio a los Agregados y usar Domain Services como herramientas, podemos crear sistemas más robustos, mantenibles y expresivos.

Así que la próxima vez que te encuentres frente a un Command Handler que hace demasiado, recuerda esta historia. Delega la lógica de negocio a los Agregados y deja que los Domain Services hagan su trabajo. ¡Tu código te lo agradecerá! 🚀

Etiquetas:
php ddd
Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer