Constructores de argumento cero y entidades siempre válidas

8

Recientemente he leído mucho sobre las entidades de dominio Siempre válidas. He llegado a creer que para garantizar que las entidades sean siempre válidas, necesito:

1) Elimine la obsesión primitiva y coloque las reglas de validación / dominio en los constructores de objetos de valor como se explica aquí: https://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/ . 2) Coloque las reglas de validación / dominio en el constructor de entidades o los establecedores de propiedades como se explica aquí: http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/ .

Sin embargo, luego miro algunos proyectos de código abierto como este: https://github.com/gregoryyoung/mr . Por lo que entiendo, el autor de este proyecto es un defensor del modelo de dominio siempre válido y, sin embargo, miro aquí en la clase InventoryItem: https://github.com/gregoryyoung/mr/blob/master/SimpleCQRS/Domain.cs . Noto que puedo hacer esto:

InventoryItem inventoryItem = new InventoryItem();

o esto:

InventoryItem inventoryItem2 = new InventoryItem(Guid.Empty,null);

En mi opinión, esto significa que la entidad se inicializa en un estado no válido. Este parece ser el caso en todos los otros proyectos de código abierto que he visto recientemente, por ejemplo, este: https://github.com/dcomartin/DDD-CQRS-ES-Example/blob/master/src/Domain /Customer.cs .

Me doy cuenta de que hay una validación contextual en estos proyectos de código abierto ( https://martinfowler.com/bliki/ContextualValidation.html ). También me doy cuenta de que los ORM necesitan un constructor vacío predeterminado si se asignan al modelo de dominio.

¿Se encuentra un objeto de dominio en un estado válido si se inicializa con valores predeterminados utilizando un constructor de argumento cero / inicializado con valores vacíos / nulos?

w0051977
fuente
2
Realmente no puedes generalizar. Puede ser. Puede que no lo sea. ¿Importa? En mi humilde opinión, especialmente las primeras conversaciones DDD olvidaron pensar que la realidad es diferente y, por ejemplo, los ORM / idiomas / marcos dictan algunas reglas (y el precio para evitarlas puede ser demasiado alto o no valioso). Ok, también el proyecto que enumeró no parece ser el mejor código .NET posible ...
Adriano Repetti
@Adriano Repetti, ¿conoces algún buen proyecto de código abierto de cqrs? ¿Pones validación en tus comandos?
w0051977
2
Distinguiría la validación de parámetros de la validación de dominio (cuando corresponda). Por ejemplo, un parámetro nulo no tiene nada que ver con el dominio, proviene de un detalle de implementación (lenguaje que está utilizando). Lo primero siempre lo hago, no importa qué y dónde. La validación de dominio es más complicada, pero si realmente te preocupas por los invariantes rotos, en mi opinión, debes considerar los objetos inmutables . Son (una vez más, en mi opinión) una compensación, a menudo, mejor que una guerra santa contra los idiomas que realmente usamos (y eso fue una crítica a muchas versiones antiguas de DDD)
Adriano Repetti

Respuestas:

7

Antes de abordar los tipos de consideraciones que intervienen en la creación de objetos, abordemos primero la motivación detrás de su pregunta: la frase "Entidades de dominio siempre válidas". Esta frase es, en el mejor de los casos, engañosa y tiene poco sentido en el contexto de DDD. Esto debería ser evidente por dos razones relacionadas:

La primera es que, implícitamente, aleja su enfoque del comportamiento del sistema y le pide que considere la validación solo en términos de estado. Si bien esto puede parecer lógico (¡por supuesto, la validación es en términos de estado!), Debe recordar que el principio fundamental de DDD es que un sistema se modela de acuerdo con el comportamiento . La motivación para esto es simplemente que el contexto, o el proceso comercial en sí mismo, a menudo es una consideración importante al determinar si algún estado es válido o no. Modelar un sistema de esta manera puede reducir en gran medida su complejidad.

Esto nos lleva a la segunda razón, que se refiere a los requisitos prácticos que tal sistema implicaría. Para crear un sistema de "Entidades de dominio siempre válidas", sería necesario modelar cada permutación de estado de acuerdo con los procesos comerciales en los que se utiliza el estado. Un ejemplo simple puede ilustrar las limitaciones de esto:

Reglas:

  • Customer debe ser mayor de 18 años para registrarse
  • Customer debe ser menor de 25 años para calificar para el descuento en el registro
  • Customer debe tener más de 25 años para hacer la reserva

Lo primero que debe notar es que todas estas reglas (como casi todas las reglas) se aplican a algún proceso comercial. No existen en el vacío. Estas reglas serían validadas en customer.Register()y customer.Reserve(). Esto da como resultado un paradigma mucho más descriptivo y declarativo porque está claro dónde se ejecutan las reglas.

Si quisiéramos modelar estas reglas tal que resulte en el sistema de las "entidades de dominio siempre es válido" tendríamos que particionar nuestro Customeren Registrary Reserverentidades. Y si bien eso puede no parecer tan malo para este ejemplo, a medida que las reglas se vuelven más complejas y abundantes, terminará con una explosión de clases como esta que representan un estado "dentro" de algún contexto o proceso. Esto es simplemente innecesario y inevitablemente creará problemas cuando dos de estos objetos dependan de la misma porción de estado.

Además, algo así como Customer c = new Customer()un mal lugar para lanzar una excepción porque no está claro qué reglas comerciales podrían aplicarse. Lo que nos lleva a nuestra discusión final.

Solo voy a salir y ir tan lejos como para decir que no debería haber validación cero de las reglas comerciales que suceden en los constructores. La construcción de objetos no tiene nada que ver con su dominio comercial, y por esa razón, además de las razones anteriores relacionadas con el contexto y la coherencia, todas las reglas comerciales deben aplicarse dentro de los cuerpos de métodos de una entidad (es probable que los métodos se denominen según los procesos comerciales correctos ?)

Además, "renovar" un objeto no es lo mismo que crear una nueva entidad en su dominio. Los objetos no salen de la nada. Si existen reglas comerciales sobre cómo una nueva entidad puede ingresar a su sistema, entonces debe modelarse en su dominio . Aquí hay más discusión sobre el tema por un verdadero maestro http://udidahan.com/2009/06/29/dont-create-aggregate-roots/

tobogán lateral
fuente
3

¿Se encuentra un objeto de dominio en un estado válido si se inicializa con valores predeterminados utilizando un constructor de argumento cero / inicializado con valores vacíos / nulos?

Puede ser.

No hay nada en contra de las reglas sobre la creación de un objeto de dominio válido utilizando un constructor predeterminado.

No hay nada en contra de las reglas sobre la creación de un objeto de dominio válido con valores vacíos.

No hay nada en contra de las reglas sobre la creación de un objeto de dominio válido que carece de elementos opcionales.

No hay nada en contra de las reglas sobre la creación de un objeto de dominio válido utilizando valores nulos.

Donde se producen problemas: crear objetos de dominio que no obedecen a su propio álgebra semántica.

La implementación correcta depende de una interpretación correcta de la semántica.

Es normal tomar una representación indulgente de un concepto de dominio y, por pasos, aplicar restricciones adicionales. Esta es una de las áreas donde los lenguajes de implementación que hacen que sea fácil agregar tipos (ej .: F #) tienen ventajas sobre los lenguajes más torpes como Java.

Por ejemplo, si tenemos un dominio que se preocupa por los números, y dentro de ese dominio hay algunas capacidades que son específicas de los números primos, o primos de Mersenne, entonces un mecanismo natural para usar es crear tres tipos diferentes; haciendo explícita la noción de que hay diferentes conjuntos de restricciones que se aplicarán a las entradas en diferentes partes de su solución.

Number n = new Number(...)
Optional<Prime> p = n.prime()
if (p.isPresent()) {
    Optional<MersennePrime> mp = p.mersennePrime()
    // ...
} 

En este tipo de diseño, la "validación" de que el número realmente es un Prime existiría dentro del propio constructor Prime. En un contexto donde necesitamos la restricción adicional de que el parámetro es un primo de Mersenne , usamos una conversión Prime-> MersennePrime, asegurando que el conocimiento de la restricción de Mersenne tenga una autoridad ("no se repita").

"Hacer lo implícito, explícito"

VoiceOfUnreason
fuente
Gracias. ¿Podría explicar qué es el álgebra semántica con quizás un ejemplo? ¿Podría explicar también cómo el ejemplo de código es relevante? +1 para "no hay nada en contra ...".
w0051977
¿Seguramente una entidad sin una ID vacía es algo malo? A menos que haya escenarios en los que inicialice la clase de dominio con valores predeterminados y luego la construya de forma incremental.
w0051977