Tactical Design
El diseño táctico en DDD se refiere a la aplicación práctica de los conceptos de dominio en estructuras de código específicas. Estos elementos tácticos son esenciales para implementar el modelo de dominio en software y son expresados habitualmente en forma de Servicios, Entidades, Objetos de Valor, y están organizados dentro de Contextos Delimitados.
Elementos tácticos del DDD
Model-Driven Design:
El diseño impulsado por el modelo es el núcleo del DDD, donde la lógica del negocio y las reglas del dominio son expresadas en código. Este diseño está directamente influenciado por el Lenguaje Ubicuo, que da estructura a los Contextos Delimitados y asegura que tanto los expertos del dominio como los desarrolladores hablen el mismo idioma.
Layered Architecture:
La arquitectura en capas ayuda a organizar el código de manera que cada capa tiene su responsabilidad bien definida. Facilita la separación de la lógica de dominio de la infraestructura y la interfaz de usuario, lo cual es crucial para mantener un diseño limpio y enfocado. La arquitectura en capas más habitual en Domain-Drive Design es la arquitectura hexagonal.
Entidades:
Las Entidades son objetos que tienen una identidad que los distingue de otros objetos, incluso si sus atributos son idénticos. La identidad de una entidad se mantiene constante a lo largo de su ciclo de vida y en diferentes estados del dominio. Para entenderlo de forma rápida, son todas aquellas clases que tienen un identifiacor único (un campo id, uuid, o similar). La clase Customer sería un ejemplo de entidad.
// Ejemplo de la entidad customerfinal class Customer{ public function __construct( private int $id, private string $name, private string $email ) { }
public function getId(): int { return $this->id; }
public function getName(): string { return $this->name; }
public function getEmail(): string { return $this->email; }
public function changeName(string $newName): void { $this->name = $newName; }
// ... more methods}Value Objects:
Los Objetos de Valor son inmutables y se definen por sus atributos. A diferencia de las entidades, no tienen un identificador único (campo id, uuid…) y si los atributos de un objeto de valor cambian, se considera que es un objeto diferente. Los objetos de valor son útiles para representar conceptos que no tienen una identidad propia, sino que son definidos por sus atributos. Por ejemplo, una dirección postal es un Value Object, ya que se define por sus atributos (calle, ciudad, código postal, etc.) y no por una identidad única. En cambio, una persona sería una entidad, ya que tiene un DNI único que la distingue de otras personas. La clase Address sería un ejemplo de Value Object.
// Ejemplo de un Value Object Address#[Immutable]final readonly class Address{ public function __construct( private string $street, private int $number, private string $city, private string $state, private string $country, private int $postalCode ) { $this->validate(); } private function validate(): void { if (empty($this->street) || empty($this->city) || empty($this->state) || empty($this->country)) { throw new InvalidArgumentException('La calle, ciudad, estado y país no pueden estar vacíos.'); } if ($this->number <= 0) { throw new InvalidArgumentException('El número de la dirección debe ser positivo.'); } if ($this->postalCode <= 0) { throw new InvalidArgumentException('El código postal debe ser positivo.'); } } public function change( string $newStreet, int $newNumber, string $newCity, string $newState, string $newCountry, int $newPostalCode ): Address { return new Address($newStreet, $newNumber, $newCity, $newState, $newCountry, $newPostalCode); }}Agregados de dominio:
Un Agregado es una colección de objetos asociados que tratamos como una unidad para el propósito de cambios de datos. Cada agregado tiene una raíz y un límite claro, con reglas de consistencia que se aplican dentro del agregado. La entidad Customer y el Value Object Address formarian un agregado de dominio donde el agregao principal o aggregate root sería Customer
Servicios:
Los Servicios en DDD encapsulan la lógica de dominio que no pertenece naturalmente a una entidad o un objeto de valor. Actúan como operaciones del dominio y pueden ser parte de la capa de dominio o de la capa de aplicación. La clase UpdateCustomerAddress sería un ejemplo de servicio de aplicación.
final readonly class UpdateCustomerAddress{ public function __construct( private CustomerRepository $customerRepository ) { }
public function __invoke(Customer $customer, Address $newAddress): void { // Comprobar que el cliente existe mediante claúsula de guarda $this->assertThatCustomerExists($customer);
// Cambiar la dirección del cliente $updatedAddress = $existingCustomer->getAddress()->change( $newAddress->getStreet(), $newAddress->getNumber(), $newAddress->getCity(), $newAddress->getState(), $newAddress->getCountry(), $newAddress->getPostalCode() );
$existingCustomer->setAddress($updatedAddress);
// Guardar la entidad Customer actualizada $this->customerRepository->save($existingCustomer); }
private funcion assertThatCustomerExists(Customer $customer): void { if (null === $this->customerRepository->findById($customer->getId())) { throw new InvalidArgumentException("El cliente no existe."); } }}Eventos de dominio:
Los Eventos de Dominio son significativos para el dominio y suelen representar algo importante que ocurrió dentro del dominio. Los eventos de dominio son utilizados para modelar efectos secundarios y comunicar cambios dentro del sistema de manera desacoplada. Este elemento es central en el Domain-Drive Design, ya que los diferentes bounded context no se comunican directamente unos con otros (cada uno es independiente) e iteractúan entre sí mediante publicación y consumo de eventos de dominio. Por ejemplo, imaginemos que tenemos un sistema de registro de usuarios, en el que existen dos bounded context bien diferenciados, Users y Notificatios. El departamento de negocio quiere que cuando un usuario se registre, automáticamente se le envíe un email de bienvenida. Por tanto, en nuestro sistema cuando el bounded context Users registra un usuario, publica un evento de dominio UserRegistered que es consumido por el bounded context Notifications para enviar el email de bienvenida.
Factorias:
Las Fábricas son métodos o clases que se encargan de la creación de objetos complejos, asegurando que el objeto creado cumpla con todas las reglas del negocio desde el momento de su creación.
Repositorios:
Los Repositorios se utilizan para encapsular la lógica necesaria para acceder a los agregados o entidades desde la capa de persistencia. Ofrecen una interfaz simple para obtener o persistir datos del dominio, sin exponer los detalles de la infraestructura subyacente.
Cada uno de estos elementos tácticos contribuye al objetivo de crear un sistema que esté profundamente alineado con el modelo de dominio y que pueda evolucionar de manera efectiva a lo largo del tiempo.