DDD: raíz agregada con gran cantidad de hijos

10

¡Prefacio a esta pregunta diciendo que soy relativamente nuevo en DDD, así que puedo estar cometiendo algunos errores fundamentales aquí!

Estoy trabajando en un proyecto que involucra los conceptos de Cuentas y Transacciones (en el sentido financiero). Una cuenta puede tener muchas transacciones ingresadas contra ella.

Me parece que Cuenta y Transacción son Entidades, y que la Cuenta es una raíz Agregada que contiene Transacciones ya que una Transacción no puede existir sin la Cuenta.

Sin embargo, cuando vengo a aplicar esto en el código, inmediatamente encuentro un problema. En muchas situaciones, no es especialmente útil para mí tener una lista de cada transacción en una cuenta en todo momento. Estoy interesado en poder hacer cosas como calcular el saldo de la Cuenta y hacer cumplir invariantes como un límite de crédito, pero también quiero poder trabajar fácilmente con un subconjunto de Transacciones (por ejemplo, mostrar las que caen dentro de un rango de fechas).

En el último caso, si estuviera usando un TransactionRepository, podría acceder de manera eficiente solo a los objetos que necesito sin cargar la lista completa (potencialmente muy grande). Sin embargo, esto permitiría que otras cosas además de la Cuenta funcionen con Transacciones, lo que significa que he roto el concepto de Cuenta como raíz agregada.

¿Cómo manejan las personas este tipo de situación? ¿Acepta las implicaciones de memoria y rendimiento de cargar un número potencialmente enorme de niños para una raíz agregada?

krixon
fuente

Respuestas:

9

Recomiendo tener cuidado con la regla "no puede existir sin" . Esto habla del concepto de composición en el diseño UML / OO y puede haber sido uno de los enfoques prescritos para el diseño de agregados en el libro azul DDD original (no estoy seguro de eso) pero ha sido revisado en gran medida desde entonces. Puede ser una mejor idea ver sus agregados desde una perspectiva de límite de consistencia transaccional .

La idea es no hacer que sus agregados sean demasiado grandes, donde tendría problemas de rendimiento como el que señala, ni demasiado pequeños, porque algunos invariantes inevitablemente abarcarían múltiples agregados, causando problemas de concurrencia y bloqueo de agregados.

El tamaño agregado correcto idealmente coincidiría con los contornos de lo que modifica en una transacción comercial determinada, ni más ni menos. En su ejemplo, si no hay muchos invariantes de dominio que abarquen varias transacciones financieras, hacer Transactionuna raíz agregada por derecho propio podría ser la mejor solución.

guillaume31
fuente
Gracias, leeré sobre límites de consistencia. Creo que su sugerencia de hacer de Transaction su propia raíz agregada podría ser buena; Como usted dice, no tengo muchos invariantes que abarcan múltiples transacciones.
krixon
7

tl; dr: rompe las reglas si es necesario. DDD no puede resolver todos los problemas; de hecho, las ideas de objetos que ofrece son buenos consejos y un buen comienzo, pero son realmente malas elecciones para algunos problemas comerciales. Considéralo una pista sobre cómo hacer las cosas.


Para el problema de cargar todos los elementos secundarios (transacción) con el elemento primario (cuenta): Parece que te has encontrado con el problema n + 1 (algo en google) que muchos ORM han resuelto.

Puede resolverlo cargando de forma diferida los elementos secundarios (transacción), solo cuando sea necesario.

Pero parece que ya lo sabes al mencionar que puedes usar un TransactionRepository para resolver el problema.

Para 'ocultar' esos datos de modo que solo Account pueda usarlos, ni siquiera necesitaría almacenarlos donde nadie más podría deserializarlos, como una tabla relacional pública. Podría tenerlo almacenado con el 'documento' de la cuenta en una base de datos de documentos. De cualquier manera, si alguien se esforzara lo suficiente, aún podría ver los datos. Y 'trabajar' con eso. Y cuando no estás mirando, ¡lo harán!

Por lo tanto, puede configurar permisos, pero luego, debe ejecutar 'cuenta' como un proceso separado.

De lo que realmente se da cuenta aquí es que DDD y el uso puro del modelo de objetos a veces lo llevarán a una esquina. A decir verdad, no tiene que usar la raíz de 'composición' / agregado para beneficiarse de los principios de diseño de DDD. Es solo una cosa que puede usar cuando tiene una situación que se ajusta a sus limitaciones.

Alguien puede decir 'no optimices temprano'. Sin embargo, aquí, en este caso, conoce la respuesta: habrá suficientes transacciones para atascar un método que los mantendrá a todos para siempre con la cuenta.

La verdadera respuesta es comenzar a ponerse de pie SOA. En mi lugar de trabajo vimos los videos de 'Computación distribuida' de Udi Dahan y compramos nServiceBus (solo nuestra elección). Haga un servicio para cuentas: con su propio proceso, colas de mensajes, acceso a una base de datos de relaciones que solo él puede ver, y ... viola, podría codificar sentencias SQL en el programa e incluso incluir un par de scripts de transacción de Cobol (broma por supuesto), pero en serio tienen más separación de preocupaciones de lo que el snob más inteligente de OO / Java jamás podría soñar.

Recomiendo modelarlo bien de todos modos; Puede obtener los beneficios de la raíz agregada aquí sin los problemas si trata el servicio como un mini-texto delimitado.

Esto tiene un inconveniente, por supuesto. No puede simplemente RPC (servicio web, SOAP o REST) ​​dentro y fuera de los servicios y entre ellos o puede obtener un antipatrón SOA llamado 'el nudo' debido al acoplamiento temporal. Debe usar la inversión del patrón de comunicación, también conocido como 'Pub-Sub', que es similar a los manejadores de eventos y los eventos que generan eventos, pero (1) entre procesos (que puede colocar en máquinas separadas si se sobrecargan en una).

El problema real es que no desea un servicio que necesita obtener datos de otro servicio para 'bloquear' o esperar: debe disparar y olvidar el mensaje y dejar que un controlador en otra parte de su programa lo recoja para completar el procesamiento. Esto significa que tienes que hacer tu lógica de manera diferente. nServicebus automatiza el patrón de 'saga' para ayudar con algo de esto, pero al final, debe desarrollar un estilo de codificación diferente. Todavía puede hacerlo todo, ¡solo tiene que hacerlo de manera diferente!

El libro "Patrones SOA" de Arnon Rotem-Gal-Oz responde muchas preguntas sobre esto. Incluyendo el uso del 'patrón de servicio activo' para replicar periódicamente datos de servicios externos a los suyos cuando surja la necesidad (se necesitarían muchos RPC, o el enlace no es confiable / no está en el ecosistema de publicación / suscripción).

Sólo para previsualización, interfaces de usuario qué tienen que RPC en los servicios. Los informes se generan a partir de una base de datos de informes alimentada por las bases de datos de los servicios. Algunas personas dicen que no se necesitan informes y que el problema debe resolverse de otra manera. Sé escéptico de esa charla.

Al final, sin embargo, no todas las cosas se pueden clasificar adecuadamente en un solo servicio. ¡El mundo no funciona con código de raviolis! Entonces tendrás que romper las reglas. Incluso si nunca tuviera que hacerlo, los nuevos desarrolladores del proyecto lo harán cuando lo deje. Pero no se preocupe, si hace lo que puede, el 85% que sigue las reglas hará que un programa sea mucho más fácil de mantener.

Wow, eso fue largo.

FastAl
fuente
Gracias por la respuesta detallada, definitivamente leeré un poco sobre SOA.
krixon