Guión:
Un cliente realiza un pedido y luego, después de recibir el producto, proporciona comentarios sobre el proceso del pedido.
Suponga las siguientes raíces agregadas:
- Cliente
- Orden
- Retroalimentación
Aquí están las reglas de negocio:
- Un cliente solo puede proporcionar comentarios sobre su propio pedido, no el de otra persona.
Un cliente solo puede proporcionar comentarios si el pedido ha sido pagado.
class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Ahora, suponga que el negocio quiere una nueva regla:
Un cliente solo puede proporcionar comentarios si la
Supplier
mercancía de la orden todavía está en funcionamiento.class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, Supplier $supplier, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } // NEW RULE HERE if (!$supplier->isOperating()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Coloqué la implementación de las dos primeras reglas dentro del Feedback
agregado mismo. Me siento cómodo haciendo esto, especialmente dado que el
Feedback
agregado hace referencia a todos los otros agregados por identidad. Por ejemplo, las propiedades del Feedback
componente indican que sabe de la
existencia de los otros agregados, por lo que me siento cómodo al tener que conocer también el estado de solo lectura de estos agregados.
Sin embargo, en función de sus propiedades, el Feedback
agregado no tiene conocimiento de la existencia del
Supplier
agregado, entonces, ¿debería tener conocimiento del estado de solo lectura de este agregado?
La solución alternativa para implementar la regla 3 es mover esta lógica a la apropiada CommandHandler
. Sin embargo, parece que está alejando la lógica del dominio del "centro" de mi arquitectura basada en la cebolla.
Supplier
el estado operativo de un agregado no se consultaría a través de unOrder
repositorio;Supplier
yOrder
son dos agregados separados. En segundo lugar, había una pregunta en la lista de correo DDD / CQRS acerca de pasar raíces y repositorios agregados a otros métodos de raíz agregada (incluido el constructor). Hubo una variedad de opiniones, pero Greg Young mencionó que pasar raíces agregadas como parámetros es común, mientras que otra persona dijo que los repositorios están más estrechamente relacionados con la infraestructura que con el dominio. Por ejemplo, los repositorios "abstractos en colecciones de memoria" y no tienen lógica.Customer
solo puede proporcionar comentarios sobre uno de sus propios pedidos ($order->customerId() == $customer->customerId()
), también tenemos que comparar la ID del proveedor ($order->supplierId() == $supplier->supplierId()
). La primera regla protege contra el usuario que proporciona valores incorrectos. La segunda regla protege contra el programador que proporciona valores incorrectos. Sin embargo, la verificación de si el proveedor está operando debe realizarse en laFeedback
entidad o en el controlador de comandos. ¿Dónde está la pregunta?Respuestas:
Si la corrección transaccional requiere que un agregado conozca el estado actual de otro agregado, entonces su modelo está equivocado.
En la mayoría de los casos, no se requiere corrección transaccional . Las empresas tienden a tolerar la latencia y los datos obsoletos. Esto es especialmente cierto en el caso de inconsistencias que son fáciles de detectar y remediar.
Entonces, el comando será ejecutado por el agregado que cambia de estado. Para realizar la verificación no necesariamente correcta, necesita no necesariamente la última copia del estado del otro agregado.
Para los comandos en un agregado existente, el patrón habitual es pasar un Repositorio al agregado, y el agregado pasará su estado al repositorio, lo que proporciona una consulta que devuelve un estado / proyección inmutable del otro agregado
Pero los patrones de construcción son extraños: cuando está creando el objeto, la persona que llama ya conoce el estado interno, porque lo está proporcionando. El mismo patrón funciona, solo parece inútil
Estamos siguiendo las reglas al mantener toda la lógica del dominio en los objetos del dominio, pero en realidad no estamos protegiendo el negocio invariante de ninguna manera útil al hacerlo (porque toda la misma información está disponible para el componente de la aplicación). Para el patrón de creación, sería igual de bueno escribir
fuente
SupplierOperatingQuery
consulta del modelo de lectura o "Consulta" en el nombre es engañosa? 2. No se requiere consistencia transaccional. No importa si el proveedor detiene las operaciones un segundo antes de que un cliente deje comentarios, pero ¿eso significa que no deberíamos verificarlo de todos modos? 3. En su ejemplo, ¿el suministro de un "servicio de consulta" en lugar del objeto mismo impone consistencia transaccional? ¿Si es así, cómo? 4. ¿Cómo el uso de tales servicios de consulta impacta las pruebas unitarias?Sé que esta es una vieja pregunta, pero me gustaría señalar que el problema se deriva directamente de una premisa incorrecta. Es decir, las raíces agregadas que debemos asumir que existen son simplemente incorrectas.
Solo hay una raíz agregada en el sistema que ha descrito: Cliente. Tanto un Pedido como un Comentario, si bien pueden ser agregados por derecho propio, dependen de la existencia del Cliente, por lo que no son raíces agregadas. La lógica que proporciona en su constructor de comentarios parece indicar que un Pedido DEBE tener un ID de cliente y los Comentarios también DEBEN estar relacionados con un Cliente. Esto tiene sentido. ¿Cómo puede un pedido o comentario no estar relacionado con un cliente? Además, el Proveedor parece estar lógicamente relacionado con el Pedido (por lo que estaría dentro de este agregado).
Con lo anterior en mente, toda la información que desea ya está disponible en la raíz agregada del Cliente y queda claro que está aplicando sus reglas en el lugar equivocado. Los constructores son lugares terribles para hacer cumplir las reglas comerciales y deben evitarse a toda costa. Así es como debería verse (Nota: no voy a incluir constructores para Cliente y Pedido porque probablemente deberían usarse Fábricas. Además, no se muestran todos los métodos de interfaz).
Bueno. Analicemos esto un poco. Lo primero que notará es cuánto más declarativo es este modelo. Todo es una acción, queda claro DÓNDE deben aplicarse las reglas de negocio. El diseño anterior no solo "hace" lo correcto, sino que "dice" lo correcto.
¿Qué llevaría a alguien a asumir que las reglas se están ejecutando en la siguiente línea?
En segundo lugar, puede ver que toda la lógica relacionada con la validación de las reglas de negocios se lleva a cabo de la manera más cercana posible a los modelos a los que pertenecen. En su ejemplo, el constructor (un único método) está realizando múltiples validaciones contra diferentes modelos. Eso rompe el diseño SÓLIDO. ¿Dónde agregaríamos un cheque para asegurarnos de que el contenido de Comentarios no contenga malas palabras? ¿Otro cheque en el constructor? ¿Qué sucede si diferentes tipos de comentarios necesitan diferentes comprobaciones de contenido? Feo.
Tercero, mirando las interfaces, puede ver que hay lugares naturales para extender / modificar las reglas a través de la composición. Por ejemplo, diferentes tipos de pedidos pueden tener diferentes reglas con respecto a cuándo se pueden proporcionar comentarios. El pedido también puede proporcionar diferentes tipos de comentarios, que a su vez pueden tener diferentes reglas para la validación.
También puede ver un montón de interfaces ICustomer *. Estos se utilizan para componer el agregado del Cliente que necesitamos aquí (probablemente no solo llamado Cliente). La razón de esto es simple. Es MUY probable que un Cliente sea una raíz agregada ENORME que se extiende por todo su dominio / DB. Mediante el uso de interfaces, podemos descomponer ese agregado (que probablemente sea demasiado grande para cargar) en múltiples raíces agregadas que solo proporcionan ciertas acciones (como ordenar o proporcionar comentarios). Puede ver que el agregado en mi implementación puede AMBOS hacer pedidos Y proporcionar comentarios, pero no se puede usar para restablecer una contraseña o cambiar un nombre de usuario.
Entonces, la respuesta a su pregunta es que los agregados deben validarse a sí mismos. Si no pueden, es probable que tenga un modelo deficiente.
fuente