Version corta
La razón fundamental para DDD es que los Objetos de dominio son abstracciones que deben cumplir con los requisitos de su dominio funcional; si los Objetos de dominio no pueden cumplir fácilmente esos requisitos, sugiere que podría estar utilizando la abstracción incorrecta.
Nombrar objetos de dominio usando sustantivos de entidad puede conducir a que esos objetos se unan estrechamente entre sí y se conviertan en objetos hinchados de "dios", y pueden arrojar problemas como el de esta pregunta, como "¿Dónde está el lugar correcto para colocar el Método CreateOrder? ".
Para facilitar la identificación de la raíz agregada 'correcta', considere un enfoque diferente donde los objetos de dominio se basen en los requisitos comerciales funcionales de alto nivel, es decir, elija sustantivos que aluden a los requisitos funcionales y / o comportamientos que los usuarios del sistema necesitan realizar.
Versión larga
DDD es un enfoque para el diseño OO que tiene como objetivo dar como resultado un gráfico de objetos de dominio en la capa empresarial de su sistema: los objetos de dominio son responsables de satisfacer sus requisitos comerciales de alto nivel, e idealmente deberían poder confiar en la capa de datos para cosas como el rendimiento y la integridad del almacén de datos persistente subyacente.
Otra forma de verlo podría ser las viñetas en esta lista
- Los sustantivos de entidad suelen sugerir atributos de datos.
- Los nombres de dominio deben sugerir comportamiento
- DDD y OO Modeling se preocupan por las abstracciones basadas en requisitos funcionales y el dominio principal / lógica empresarial.
- Business Logic Layer es responsable de satisfacer los requisitos de dominio de alto nivel
Una de las ideas falsas comunes con respecto a DDD es que los Objetos de dominio deben basarse en alguna "cosa" física del mundo real (es decir, algún sustantivo al que pueda señalar en el mundo real, atribuido con todo tipo de datos / propiedades), sin embargo, los datos / atributos de esas cosas del mundo real no necesariamente hacen un buen punto de partida cuando se trata de concretar los requisitos funcionales.
Por supuesto, Business Logic debería usar estos datos, pero los objetos de dominio en sí mismos deberían ser, en última instancia, abstracciones que representen los requisitos y comportamientos funcionales del dominio.
Por ejemplo; sustantivos tales como Order
o Customer
no implican ningún comportamiento, y por lo tanto son generalmente abstracciones inútiles para representar la lógica de negocios y los objetos de dominio.
Cuando busque los tipos de abstracciones que podrían ser útiles para representar Business Logic, considere los requisitos típicos que puede esperar que un sistema cumpla:
- Como vendedor, quiero crear un pedido para un nuevo cliente para poder generar una factura de los productos que se venderán con sus precios y cantidad.
- Como asesor de servicio al cliente, quiero cancelar un pedido pendiente para que un operador de almacén no cumpla el pedido.
- Como asesor de servicio al cliente, quiero devolver una línea de pedido para que el producto se pueda ajustar al inventario y el pago se reembolse a través del método de pago original del cliente.
- Como operador de almacén, quiero ver todos los productos en un pedido pendiente y la información de envío para poder elegir los productos y enviarlos a través del servicio de mensajería.
- etc.
Requisitos de dominio de modelado con un enfoque DDD
Según la lista anterior, considere algunos Objetos de dominio potenciales para dicho sistema de Órdenes:
SalesOrderCheckout
PendingOrdersStream
WarehouseOrderDespatcher
OrderRefundProcessor
Como objetos de dominio, estos representan abstracciones que toman posesión de varios requisitos de dominio de comportamiento; de hecho, sus sustantivos apuntan fuertemente a los requisitos funcionales específicos que cumplen.
(También puede haber infraestructura adicional allí, como EventMediator
notificaciones para pasar a los observadores que desean saber cuándo se ha creado un nuevo pedido, o cuándo se ha enviado un pedido, etc.).
Por ejemplo, SalesOrderCheckout
probablemente necesite manejar datos sobre Clientes, Envíos y Productos, sin embargo, no tiene nada que ver con el comportamiento de los pedidos de envío, la clasificación de pedidos pendientes o la emisión de reembolsos.
El SalesOrderCheckout
cumplimiento de los requisitos de su dominio incluye hacer cumplir esas reglas comerciales, como evitar que los clientes ordenen demasiados artículos, posiblemente ejecutar alguna validación y tal vez generar notificaciones para otras partes del sistema; puede hacer todas esas cosas sin necesidad de depender de ninguna de los otros objetos.
DDD usando sustantivos de entidad para representar objetos de dominio
Existen varios peligros potenciales cuando se tratan sustantivos simples como Order
, Customer
y Product
como Objetos de dominio; Entre esos problemas están aquellos a los que aludiste en la pregunta:
- Si un método maneja un
Order
, a Customer
y a Product
, ¿a qué Objeto de Dominio pertenece?
- ¿Dónde está la raíz agregada para esos 3 objetos?
Si elige sustantivos de entidad para representar objetos de dominio, pueden suceder varias cosas:
Order
, Customer
y Product
corren el riesgo de convertirse en objetos de "dios"
- Riesgo de terminar con un solo
Manager
objeto divino para unir todo.
- Esos objetos corren el riesgo de estar estrechamente unidos entre sí; puede ser difícil cumplir los requisitos del dominio sin pasar
this
(o self
)
- Existe el riesgo de desarrollar abstracciones "con fugas", es decir, se espera que los objetos de dominio expongan docenas de
get
/ set
métodos que debilitan la encapsulación (o, si no lo hace, entonces algún otro programador probablemente lo hará más adelante ...).
- Existe el riesgo de que los objetos de dominio se hinchen con una mezcla compleja de datos comerciales (por ejemplo, entrada de datos de usuario a través de una interfaz de usuario) y estado transitorio (por ejemplo, un "historial" de acciones del usuario cuando se ha modificado el pedido).
DDD, diseño OO y modelos simples
Un error común con respecto al diseño DDD y OO es que los modelos "simples" son de alguna manera "malos" o "antipatrones". Martin Fowler escribió un artículo que describe el modelo de dominio anémico , pero como lo deja claro en el artículo, el DDD en sí mismo no debe 'contradecir' el enfoque de separación limpia entre capas
"También vale la pena enfatizar que poner el comportamiento en los objetos del dominio no debería contradecir el enfoque sólido de usar capas para separar la lógica del dominio de cosas tales como la persistencia y las responsabilidades de presentación. La lógica que debería estar en un objeto del dominio es la lógica del dominio - validaciones, cálculos , reglas de negocio, como quiera llamarlo ".
En otras palabras, el uso de modelos simples para mantener los datos comerciales transferidos entre otras capas (por ejemplo, un modelo de pedido pasado por una aplicación de usuario cuando el usuario desea crear un nuevo pedido) no es lo mismo que un "modelo de dominio anémico". Los modelos de datos 'simples' son a menudo la mejor manera de rastrear datos y transferir datos entre capas (como un servicio web REST, un almacén de persistencia, una aplicación o interfaz de usuario, etc.).
La lógica empresarial puede procesar los datos en esos modelos y puede rastrearlos como parte del estado comercial, pero no necesariamente tomará posesión de esos modelos.
La raíz agregada
Mirando de nuevo el ejemplo objetos de dominio - SalesOrderCheckout
, PendingOrdersStream
, WarehouseOrderDespatcher
, OrderRefundProcessor
todavía no hay una obvia agregado Raíz; pero eso en realidad no importa porque estos Objetos de dominio tienen responsabilidades completamente separadas que no parecen superponerse.
Funcionalmente, no hay necesidad de SalesOrderCheckout
hablar con el PendingOrdersStream
porque el trabajo del primero se completa cuando se ha agregado un nuevo orden a la Base de Datos; Por otro lado, PendingOrdersStream
puede recuperar nuevos pedidos de la base de datos. Estos objetos en realidad no necesitan interactuar entre sí directamente (tal vez un Mediador de eventos podría proporcionar notificaciones entre los dos, pero esperaría que cualquier acoplamiento entre estos objetos sea muy flojo)
Quizás la raíz agregada sea un contenedor de IoC que inyecte uno o más de esos objetos de dominio en un controlador de interfaz de usuario, y que también proporcione otra infraestructura como EventMediator
y Repository
. O tal vez sea algún tipo de servicio ligero de orquestador que se encuentre en la parte superior de la capa empresarial.
La raíz agregada no necesariamente debe ser un objeto de dominio. En aras de mantener la separación de preocupaciones entre los objetos de dominio, generalmente es bueno cuando la raíz agregada es un objeto separado sin lógica de negocios.
¿Cuál es el criterio para definir un agregado?
Volvamos a lo básico del gran libro azul:
El objetivo es mantener a los invariantes. Pero también es para gestionar adecuadamente la identidad local, es decir, la identificación de objetos que no tienen un significado solo.
Order
yOrder line
definitivamente pertenecer a tal grupo. Por ejemplo:Order
, requerirá la eliminación de todas sus líneas.Por lo tanto, aquí se requiere el agregado completo para garantizar reglas de coherencia e invariantes.
¿Cuándo parar?
Ahora, describe algunas reglas comerciales y argumenta que para garantizarlas, debe considerar al cliente como parte del agregado:
Por supuesto, por qué no. Veamos las implicaciones: siempre se accede al pedido a través del cliente. Es ésto la vida real ? Cuando los trabajadores llenen las cajas para entregar el pedido, ¿deberán leer el código de barras del cliente y el código de barras del pedido para acceder al pedido? De hecho, en general, la identidad de un Pedido es global, no local para un cliente, y esta relativa independencia sugiere mantenerlo fuera del conjunto.
Además, estas reglas comerciales se ven más como políticas: es una decisión arbitraria de la compañía ejecutar su proceso con estas reglas. Si no se respetan las reglas, el jefe puede ser infeliz, pero los datos no son realmente inconsistentes. Y además, durante la noche "por cliente, un pedido no entregado a la vez" podría convertirse en "diez pedidos no entregados por cliente" o incluso "independientemente del cliente, cien pedidos no entregados por almacén", de modo que el agregado ya no podría estar justificado.
fuente
Antes de profundizar demasiado en ese agujero de conejo, debe revisar la discusión de Greg Young sobre la consistencia del conjunto y, en particular:
Porque en muchos casos, la respuesta correcta no es tratar de evitar que suceda algo incorrecto, sino generar informes de excepción cuando pueda haber un problema.
Pero, suponiendo que múltiples pedidos no entregados son una responsabilidad significativa para su negocio ...
Sí, si desea asegurarse de que solo haya un pedido no entregado, entonces debe haber algún agregado que pueda ver todos los pedidos de un cliente.
Ese agregado no es necesariamente el agregado del cliente .
Puede ser algo así como una cola de pedidos, o un historial de pedidos, donde todos los pedidos de un cliente específico entran en la misma cola. Por lo que ha dicho, no necesita todos los datos del perfil del cliente, por lo que no debería ser parte de este agregado.
Sí, cuando realmente está trabajando con cumplimiento y extracción de hojas, la vista del historial no es particularmente relevante.
La vista del historial, para imponer su invariante, solo necesita la identificación del pedido y su estado de procesamiento actual. Eso no necesariamente tiene que ser parte del mismo agregado que el orden; recuerde, los límites agregados se tratan de gestionar el cambio, no de estructurar vistas.
Por lo tanto, podría ser que maneje el pedido como un agregado, y el historial de pedidos como un agregado separado, y coordine la actividad entre los dos.
fuente
Has establecido un ejemplo de persona de paja. Es demasiado simplista y dudo que refleje un sistema del mundo real. No modelaría esas Entidades y su comportamiento relacionado de la manera que usted especificó debido a eso.
Sus clases necesitan modelar el estado de un pedido de manera que se refleje en múltiples agregados. Por ejemplo, cuando el cliente coloca el sistema en el estado en el que se debe procesar la solicitud de pedido del cliente, podría crear un agregado de objeto de entidad de dominio llamado
CustomerOrderRequest
o,PendingCustomerOrder
o simplementeCustomerOrder
, o cualquier idioma que utilice la empresa, y podría contener un puntero a ambos el cliente y las líneas de pedido y luego tienen un método como elcanCustomerCompleteOrder()
que se llama desde la capa de servicio.Este objeto de dominio contendría la lógica de negocios para determinar si el pedido era válido o no.
Si el pedido fuera válido y se procesara, tendría alguna forma de hacer la transición de este objeto a otro objeto que representara el pedido procesado.
Creo que el problema con su comprensión es que está utilizando un ejemplo artificialmente simplificado de agregados. A
PendingOrder
puede ser su propio agregado separado de unaUndeliveredOrder
y nuevamente separado de unaDeliveredOrder
o unaCancelledOrder
o lo que sea.fuente
Vaughn Vernon menciona esto en su libro "Implementando diseño impulsado por dominio" al comienzo del Capítulo 7 (Servicios):
"A menudo, la mejor indicación de que debe crear un Servicio en el modelo de dominio es cuando la operación que necesita realizar se siente fuera de lugar como un método en un Agregado o un Objeto de Valor".
Entonces, en este caso podría haber un servicio de dominio llamado "CreateOrderService" que toma una instancia del Cliente y la lista de artículos para el pedido.
fuente