Valores calculados y lecturas simples: ¡un dolor persistente para mis diseños impulsados ​​por dominios!

9

El problema al que me enfrento continuamente es cómo lidiar con los valores calculados impulsados ​​por la lógica de dominio mientras se trabaja de manera eficiente contra el almacén de datos.

Ejemplo:

Estoy devolviendo una lista de Productos de mi repositorio a través de un servicio. Esta lista está limitada por la información de paginación de la solicitud DTO enviada por el cliente. Además, el DTO especifica un parámetro de clasificación (una enumeración amigable para el cliente).

En un escenario simple, todo funciona muy bien: el servicio envía expresiones de paginación y clasificación al repositorio y el repositorio emite una consulta eficiente a la base de datos.

Sin embargo, todo se rompe cuando necesito ordenar los valores generados en la memoria de mi modelo de dominio. Por ejemplo, la clase Product tiene un método IsExpired () que devuelve un bool basado en la lógica empresarial. Ahora no puedo ordenar y buscar en el nivel de repositorio: todo se habría hecho en la memoria (ineficiente) y mi servicio tendría que saber las complejidades de cuándo emitir estos parámetros al repositorio y cuándo realizar la clasificación / paginación sí mismo.

El único patrón que parece tener sentido para mí es almacenar el estado de la entidad en la base de datos (hacer IsExpired () un campo de solo lectura y actualizarlo a través de la lógica de dominio antes de guardar). Si separo esta lógica en un repositorio separado "leer modelo / dto" y "informar", estoy haciendo que mi modelo sea más anémico de lo que me gustaría.

Por cierto, cada ejemplo que he visto para cálculos como este realmente parece apoyarse en el procesamiento en memoria y pasar por alto el hecho de que es mucho menos eficiente a largo plazo. Tal vez estoy optimizando prematuramente, pero eso no me sienta bien.

Me encantaría saber cómo otros han lidiado con esto, ya que estoy seguro de que es común en casi proyectos que involucran DDD.

drogon
fuente

Respuestas:

3

No creo que tener dos modelos de dominio diferentes del mismo modelo de datos haga que su dominio sea anémico. Un modelo de dominio anémico es aquel en el que la lógica empresarial que cambia a menudo se oculta del dominio en una capa de servicio (o, lo que es peor, en la capa de IU).

La separación de los modelos de dominio de comando y consulta se adopta a menudo y tiene un buen acrónimo que puedes buscar en Google en CQRS (segmentación de responsabilidad de consulta de comando).

Empleando el patrón del modelo de dominio, Udi Dahan

Si bien en el pasado tuve "éxito" al crear un único modelo de objeto persistente que manejaba tanto los comandos como las consultas, a menudo era muy difícil escalarlo, ya que cada parte del sistema tiraba del modelo en una dirección diferente.

Resulta que los desarrolladores a menudo asumen requisitos más extenuantes de los que la empresa realmente necesita. La decisión de utilizar las entidades del modelo de dominio para mostrar información al usuario es solo un ejemplo.

[...]

Para aquellos lo suficientemente mayores como para recordar, las mejores prácticas sobre el uso de COM + nos guiaron para crear componentes separados para la lógica de solo lectura y de lectura y escritura. Aquí estamos, una década más tarde, con nuevas tecnologías como Entity Framework, pero esos mismos principios siguen vigentes.

CQRS con actores Akka y modelos de dominio funcional, Debasish Ghosh

Greg Young ha impartido algunas excelentes sesiones sobre DDD y CQRS. En 2008, dijo: "Un modelo único no puede ser apropiado para la presentación de informes, la búsqueda y el comportamiento transaccional". Tenemos al menos dos modelos: uno que procesa los comandos y alimenta los cambios a otro modelo que atiende consultas e informes de los usuarios. El comportamiento transaccional de la aplicación se ejecuta a través del modelo de dominio rico de agregados y repositorios, mientras que las consultas se atienden directamente desde un modelo de datos desnormalizado.

CQRS, Martin Fowler

El cambio que introduce CQRS es dividir ese modelo conceptual en modelos separados para actualización y visualización, a los que se refiere como Comando y Consulta, respectivamente, siguiendo el vocabulario de CommandQuerySeparation. La razón es que para muchos problemas, particularmente en dominios más complicados, tener el mismo modelo conceptual para comandos y consultas conduce a un modelo más complejo que no funciona bien.

En resumen, su idea de que el modelo de comando maneje la caducidad y la pase a la base de datos está absolutamente bien. Lea el primer artículo anterior y verá escenarios similares pero más complejos.

pdr
fuente
2

ESPECIFICACIÓN

Sé que ya has aceptado una respuesta, pero preguntaste sobre DDD, y la coincidencia exacta para esto es lo que Evans llama una 'especificación':
enlace directo de google books.
Si ese enlace no funciona, busca el libro en estos resultados
Es la página 226 si tienes el libro.

En la página 227 hay 3 usos para las especificaciones: Validación, Selección, Creación de un nuevo objeto especial. La suya es 'selección' - IsExpired.

Otra cosa sobre el concepto de 'especificación' es que admite que, por razones de eficiencia, es posible que necesite una versión de código para operar en los objetos en memoria, y otra versión del código para consultar eficientemente el repositorio sin tener que obtener primero Todos los objetos en la memoria.

En un mundo simple, esto significaría poner una versión de SQL en su repositorio y una versión de objetos en su modelo, por supuesto que tiene inconvenientes. La lógica está en 2 lugares (malo, alguien se olvidará de actualizar esos lugares) y hay una lógica de dominio en su repositorio.

Entonces la respuesta es poner ambos conjuntos de lógica en una especificación. La versión en memoria, obviamente, pero también una versión de repositorio. Si está utilizando, por ejemplo, n-hibernate, puede utilizar su lenguaje de consulta incorporado para la versión del repositorio.

De lo contrario, deberá crear un método de repositorio especial para esta especificación que se utilice a partir del objeto de especificación. Las solicitudes de colecciones de objetos que coinciden con la especificación irían a través de la especificación, no del repositorio. Y al menos el código grita 'estoy en 2 lugares, no lo olvides' para futuros mantenedores. Hay un maravilloso ejemplo en la página 231-232 para resolver un problema muy similar.

La especificación es una fuga / deslizamiento 'permitido' de la 'pureza' de DDD. Todavía podría no satisfacer sus necesidades para una variedad de propósitos. Por ejemplo, el ORM puede generar SQL incorrecto; puede haber demasiada codificación adicional. Por lo tanto, es posible que deba llamar a métodos de repositorio de modo que sea casi como poner SQL en la especificación. Una cosa mala por supuesto. Pero no olvide que su programa tiene que funcionar a una velocidad razonable. No tiene que ganar un premio de pureza DDD. Entonces, una realidad de cambiar los almacenes de datos podría significar cirugía anticuada a lo largo del programa. También algo malo. Pero no es tan malo como un programa lento (también conocido como SUCKing). Si salir de la caja en varios DB es una realidad, obviamente, duplicará las reglas de negocios para cada almacén de datos para cada especificación. Al menos tienes el dedo en el asunto y puedes usar el patrón de estrategia cuando intercambias repositorios. Pero si estás usando un DB específico, recuerdaYAGNI

Con respecto a CQRS: la cita de Fowler de pdr anterior aún es cierta aquí: "tener el mismo modelo conceptual para comandos y consultas conduce a un modelo más complejo que no funciona bien" ... y es posible que necesite usar CQRS o similar. Pero es mucho más costoso desde el punto de vista del desarrollo y el mantenimiento. Si usted es un proveedor de paquetes en competencia con otros, podría pagar. Si está escribiendo una aplicación LOB personalizada para un cliente, disparar a la perfección es una mala elección. Debe decidir si el valor de tener un modelo completo o en su mayoría doble vale la pena el esfuerzo adicional. Especificaciónes un buen compromiso porque le permite hacer esta separación en solo una pequeña parte del programa que lo necesita, con la velocidad (de desarrollo) y la simplicidad de un modelo. ¡Buena suerte!

FastAl
fuente
Eso tiene mucho sentido. Creo que necesito morder la bala y leer el libro de Evans :-) ¡Ahora veo que tener una comprensión superficial de estos conceptos realmente puede paralizarlo!
drogon
0

Supongo que cuestionaría cuál es la lógica de negocios que determina si isExpired es verdadero o no. ¿Puede esta lógica ser realizada por una consulta tal como está el modelo de datos? Si es así, ¿puede hacer que su repositorio sea lo suficientemente inteligente como para usar la lógica "isExpired" cuando solicita productos de cierta manera? Si no, tal vez necesite volver a examinar su modelo de datos.

DDD no significa que sus repositorios deben ser estúpidos, solo significa que su dominio necesita saber cómo comunicarse con sus repositorios.

Matthew Flynn
fuente