Encuentre la raíz agregada DDD

10

Juguemos el juego favorito de todos, busquemos la raíz agregada. Usemos el dominio canónico Cliente / Pedido / Pedidos / Líneas de producto. Tradicionalmente, el Cliente, el pedido y el producto son los AR con OrderLines como entidades bajo el Pedido. La lógica detrás de esto es que necesita identificar clientes, pedidos y productos, pero una línea de pedido no existiría sin un pedido. Entonces, en nuestro dominio problemático, tenemos una regla comercial que dice que un Cliente solo puede tener un pedido no entregado a la vez.

¿Eso mueve el pedido bajo la raíz agregada del cliente? Creo que si. Pero al hacerlo, eso hace que el AR del cliente sea bastante grande y esté sujeto a problemas de concurrencia más adelante.

O, ¿qué pasaría si tuviéramos una regla comercial que estableciera que un cliente solo puede pedir un producto en particular una vez en su vida útil? Esta es más evidencia que requiere que el Cliente sea dueño del Pedido.

Pero cuando se trata de envío, realizan todas sus acciones en el Pedido, no el cliente. Es un poco tonto tener que cargar a todo el cliente para marcar un pedido individual como entregado.

Esto es lo que estoy proponiendo:

class Customer
{
    public Guid Id {get;set;}
    public string Name { get; set; }
    public Address Address { get; set; }
    public IEnumerable<Order> Orders { get; set; }
    public void PlaceOrder(ThingsInTheOrder thingsInTheOrder)
    {
        // Make sure there aren't any pending orders already.
        // Make sure they aren't ordering a Widget if they've already ordered a Widget in the past.
        // Create an Order object and add it to the collection.  Raise a domain event to trigger emails and other stuff
    }
}

class Order
{
    public Guid Id { get; set; }
    public IEnumerable<OrderLine> OrderLines { get; set; }
    public ShippingData {get;set;}
    public void Ship(ShippedByPerson shippedByPerson, string trackingCode)
    {
         // Create a new ShippingData object and assign it from the data passed in.  
         // Publish a domain event
    }
}

Mi mayor preocupación es el problema de concurrencia y el hecho de que la Orden en sí misma tiene características de raíz agregada.

Darthg8r
fuente

Respuestas:

12

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 Ordero Customerno 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 EventMediatornotificaciones 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, SalesOrderCheckoutprobablemente 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 SalesOrderCheckoutcumplimiento 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, Customery Productcomo Objetos de dominio; Entre esos problemas están aquellos a los que aludiste en la pregunta:

  • Si un método maneja un Order, a Customery 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, Customery Productcorren el riesgo de convertirse en objetos de "dios"
  • Riesgo de terminar con un solo Managerobjeto 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/ setmé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, OrderRefundProcessortodaví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 SalesOrderCheckouthablar con el PendingOrdersStreamporque el trabajo del primero se completa cuando se ha agregado un nuevo orden a la Base de Datos; Por otro lado, PendingOrdersStreampuede 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 EventMediatory 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.

Ben Cottrell
fuente
3
Voté en contra porque su respuesta combina conceptos de Entity Framework, que es una tecnología específica de Microsoft con Domain Driven Design, que es de un libro escrito por Eric Evans del mismo nombre. Tiene algunas declaraciones en su respuesta que están en contradicción directa con el libro DDD y esta pregunta no hace mención alguna de Entity Framework pero está específicamente etiquetada con DDD. Tampoco se menciona la persistencia en la pregunta, por lo que no veo dónde son relevantes las tablas de la base de datos.
RibaldEddie
@RibaldEddie Gracias por tomarse el tiempo de revisar la respuesta y comentar, estoy de acuerdo en que la mención de datos persistentes realmente no necesita estar en la respuesta, por lo que la he redactado nuevamente para eliminarla. El enfoque principal de la respuesta podría resumirse como "Los sustantivos de entidad a menudo no son muy buenos nombres de clase de objeto de dominio debido a su tendencia a convertirse en objetos de Dios hinchados estrechamente acoplados". ?
Ben Cottrell
El libro DDD no tiene ese concepto IIRC. Tiene entidades, que son simplemente clases que, cuando se instancian, tienen una identidad persistente y única, de modo que dos instancias separadas implican dos cosas únicas y persistentes, lo que contrasta con los objetos de valor que no tienen ninguna identidad y no persisten en el tiempo . En el libro, los objetos Entidades y Valor son objetos de dominio.
RibaldEddie
10

¿Cuál es el criterio para definir un agregado?

Volvamos a lo básico del gran libro azul:

Agregado: un grupo de objetos asociados que se tratan como una unidad a los efectos de los cambios de datos . Las referencias externas están restringidas a un miembro del AGREGADO, designado como la raíz. Se aplica un conjunto de reglas de coherencia dentro de los límites del AGREGADO.

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.

Ordery Order linedefinitivamente pertenecer a tal grupo. Por ejemplo:

  • Eliminar un Order, requerirá la eliminación de todas sus líneas.
  • Eliminar una línea puede requerir renumerar las siguientes líneas
  • Agregar una nueva línea requeriría determinar el número de línea en función de todas las demás líneas del mismo orden.
  • Cambiar cierta información del pedido, como por ejemplo la moneda, podría afectar el significado del precio en las líneas de pedido (o requerir recalcular los precios).

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:

Tenemos una regla comercial que dice que un Cliente solo puede tener un pedido no entregado a la vez.

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.

Christophe
fuente
1

en nuestro dominio problemático, tenemos una regla comercial que dice que un Cliente solo puede tener un pedido no entregado a la vez.

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:

¿Cuál es el impacto comercial de tener un fracaso?

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.

Pero cuando se trata de envío, realizan todas sus acciones en el Pedido, no el cliente.

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.

VoiceOfUnreason
fuente
1

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 CustomerOrderRequesto, PendingCustomerOrdero simplemente CustomerOrder, 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 el canCustomerCompleteOrder()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 PendingOrderpuede ser su propio agregado separado de una UndeliveredOrdery nuevamente separado de una DeliveredOrdero una CancelledOrdero lo que sea.

RibaldEddie
fuente
Aunque su intento de lenguaje neutral en cuanto al género es divertido, me gustaría señalar que las mujeres nunca se paran en los campos para ahuyentar a los cuervos.
Robert Harvey
@RobertHarvey, eso es algo extraño en lo que enfocarme en mi publicación. Los espantapájaros y las efigies han aparecido regularmente en forma femenina a lo largo de la historia.
RibaldEddie
No hubieras hecho la distinción en tu publicación si no lo consideraras importante. Como cuestión de lingüística, el término es "hombre de paja"; casi cualquier duda sobre el sexismo se ve superada por el factor "de qué demonios está hablando" creado al inventar su propio término.
Robert Harvey
55
@RobertHarvey si alguien sabe lo que significa hombre de paja, estoy seguro de que puede descubrir qué significa persona de paja si no ha escuchado ese término. ¿Podemos centrarnos en el contenido de mi publicación, por favor wrt software?
RibaldEddie
1

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.

class CreateOrderService
{
    public Order CreateOrder(Customer customer, ThingsInTheOrder thingsInTheOrder)
    {
        // Get all the orders for the customer
        // Check if any of the things to be ordered exist in previous orders   
        // If none have been previously ordered, create the order and return it
        // Otherwise return null 
    }
}
claudio
fuente
1
¿Puede explicar más cómo el servicio de dominio puede ayudar a abordar la preocupación de concurrencia en la pregunta?
ivenxu