Formato corto de pregunta
¿Está dentro de las mejores prácticas de DDD y OOP inyectar servicios en llamadas de método de entidad?
Ejemplo de formato largo
Supongamos que tenemos el clásico caso Order-LineItems en DDD, donde tenemos una Entidad de dominio llamada Order, que también actúa como Aggregate Root, y esa Entidad está compuesta no solo por sus Value Value, sino también una colección de Line Item Entidades
Supongamos que queremos una sintaxis fluida en nuestra aplicación, de modo que podamos hacer algo como esto (notando la sintaxis en la línea 2, donde llamamos al getLineItems
método):
$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
...
}
No queremos inyectar ningún tipo de LineItemRepository en OrderEntity, ya que es una violación de varios principios en los que puedo pensar. Pero, la fluidez de la sintaxis es algo que realmente queremos, porque es fácil de leer y mantener, además de probar.
Considere el siguiente código, anotando el método getLineItems
en OrderEntity
:
interface IOrderService {
public function getOrderByID($orderID) : OrderEntity;
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}
class OrderService implements IOrderService {
private $orderRepository;
private $lineItemRepository;
public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
$this->orderRepository = $orderRepository;
$this->lineItemRepository = $lineItemRepository;
}
public function getOrderByID($orderID) : OrderEntity {
return $this->orderRepository->getByID($orderID);
}
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
}
}
class OrderEntity {
private $ID;
private $lineItems;
public function getLineItems(IOrderServiceInternal $orderService) {
if(!is_null($this->lineItems)) {
$this->lineItems = $orderService->getLineItems($this);
}
return $this->lineItems;
}
}
¿Es esa la forma aceptada de implementar una sintaxis fluida en las entidades sin violar los principios básicos de DDD y OOP? A mí me parece bien, ya que solo estamos exponiendo la capa de servicio, no la capa de infraestructura (que está anidada dentro del servicio)
No, no debe inyectar nada dentro de su capa de dominio (esto incluye entidades, objetos de valor, fábricas y servicios de dominio). Esta capa debe ser independiente de cualquier marco, bibliotecas o tecnología de terceros y no debe realizar ninguna llamada de E / S.
Esto está mal, ya que el Agregado no debería necesitar nada más que sí mismo para devolver los artículos del pedido. La totalidad de agregado debe estar ya cargado antes de su llamada al método. Si crees que esto debería estar cargado de manera diferida, entonces hay dos posibilidades:
Los límites de tus agregados son incorrectos, son demasiado grandes.
En este caso de uso, usted usa el Agregado solo para leer. La mejor solución es dividir el modelo de escritura del modelo de lectura (es decir, usar CQRS ). En esta arquitectura más limpia no se le permite consultar el Agregado sino un modelo de lectura.
fuente
La idea clave en los patrones tácticos DDD: la aplicación accede a todos los datos en la aplicación actuando en una raíz agregada. Esto implica que las únicas entidades a las que se puede acceder fuera del modelo de dominio son las raíces agregadas.
La raíz agregada Order nunca generará una referencia a su colección de elementos de línea que le permita modificar la colección, ni generará una colección de referencias a ninguna línea de pedido que le permita modificarla. Si desea cambiar el agregado de la Orden, se aplica el principio de Hollywood: "Dígale, no pregunte".
Devolver valores desde dentro del agregado está bien, porque los valores son inherentemente inmutables; No puede cambiar mis datos cambiando su copia.
Usar un servicio de dominio como argumento para ayudar al agregado a proporcionar los valores correctos es una cosa perfectamente razonable.
Normalmente no usaría un servicio de dominio para proporcionar acceso a los datos que están dentro del agregado, porque el agregado ya debería tener acceso a él.
De modo que esa ortografía es extraña si estamos intentando acceder a la colección de valores de líneas de pedido de este pedido. La ortografía más natural sería
Por supuesto, esto supone que las líneas de pedido ya se han cargado.
El patrón habitual es que la carga del agregado incluirá todo el estado requerido para el caso de uso particular. En otras palabras, puede tener varias formas diferentes de cargar el mismo agregado; Sus métodos de repositorio son adecuados para su propósito .
Este enfoque no es algo que encontrará en el Evans original, donde supuso que un agregado tendría un único modelo de datos asociado. Cae más naturalmente fuera de CQRS.
fuente
lineItems()
y la precarga en la primera recuperación de la Raíz Agregada.En términos generales, los objetos de valor que pertenecen al agregado no tienen repositorio por sí mismos. Es responsabilidad de la raíz agregada poblarlos. En su caso, es responsabilidad de su OrderRepository llenar los objetos de valores Order entidad y OrderLine.
En cuanto a la implementación de infraestructura del OrderRepository, en el caso de ORM, es una relación de uno a muchos, y puede elegir cargar con impaciencia o con pereza la OrderLine.
No estoy seguro de qué significan exactamente sus servicios. Está bastante cerca del "Servicio de aplicaciones". Si este es el caso, generalmente no es una buena idea inyectar los servicios a Aggregate root / Entity / Value Object. Application Service debe ser el cliente de Aggregate root / Entity / Value Object and Domain Service. Otra cosa sobre sus servicios es que exponer objetos de valor en Application Service tampoco es una buena idea. Se debe acceder por la raíz agregada.
fuente
La respuesta es: definitivamente NO, evite pasar servicios en métodos de entidad.
La solución es simple: simplemente deje que el repositorio de pedidos devuelva el pedido con todos sus artículos de línea. En su caso, el agregado es Order + LineItems, por lo que si el repositorio no devuelve un agregado completo, entonces no está haciendo su trabajo.
El principio más amplio es: mantener los bits funcionales (por ejemplo, lógica de dominio) separados de los bits no funcionales (por ejemplo, persistencia).
Una cosa más: si puedes, trata de evitar hacer esto:
Haz esto en su lugar
En el diseño orientado a objetos, tratamos de evitar pescar en los datos de un objeto. Preferimos pedirle al objeto que haga lo que queramos.
fuente