¿Los DDD Aggregates son realmente una buena idea en una aplicación web?

40

Me estoy sumergiendo en el diseño impulsado por dominio y algunos de los conceptos que estoy encontrando tienen mucho sentido en la superficie, pero cuando pienso más en ellos, me pregunto si esa es realmente una buena idea.

El concepto de agregados, por ejemplo, tiene sentido. Crea pequeños dominios de propiedad para no tener que lidiar con todo el modelo de dominio.

Sin embargo, cuando pienso en esto en el contexto de una aplicación web, con frecuencia estamos presionando la base de datos para retirar pequeños subconjuntos de datos. Por ejemplo, una página solo puede enumerar el número de pedidos, con enlaces para hacer clic para abrir el pedido y ver sus ID de pedido.

Si estoy en lo correcto entendimiento agregados, me suelen utilizar el patrón de repositorio para devolver un OrderAggregate que contendría los miembros GetAll, GetByID, Delete, y Save. OK eso suena bien. Pero...

Si llamo a GetAll para enumerar todos mis pedidos, me parecería que este patrón requeriría que se devuelva la lista completa de información agregada, pedidos completos, líneas de pedido, etc. Cuando solo necesito un pequeño subconjunto de esa información (solo información del encabezado).

¿Me estoy perdiendo de algo? ¿O hay algún nivel de optimización que usarías aquí? No puedo imaginar que alguien abogue por devolver agregados enteros de información cuando no la necesite.

Ciertamente, uno podría crear métodos en su repositorio como GetOrderHeaders, pero eso parece anular el propósito de usar un patrón como repositorio en primer lugar.

¿Alguien puede aclarar esto para mí?

EDITAR:

Después de mucha más investigación, creo que la desconexión aquí es que un patrón de Repositorio puro es diferente de lo que la mayoría de la gente piensa que es un Repositorio.

Fowler define un repositorio como un almacén de datos que utiliza semántica de recopilación, y generalmente se mantiene en la memoria. Esto significa crear un gráfico de objeto completo.

Evans altera el repositorio para incluir raíces agregadas y, por lo tanto, el repositorio se amputa para admitir solo los objetos en un agregado.

La mayoría de las personas parecen pensar en los repositorios como objetos de acceso a datos glorificados, donde solo se crean métodos para obtener los datos que se desean. Esa no parece ser la intención descrita en los Patrones de arquitectura de aplicaciones empresariales de Fowler.

Aún otros piensan en un repositorio como una simple abstracción utilizada principalmente para facilitar las pruebas y las burlas, o para desacoplar la persistencia del resto del sistema.

Supongo que la respuesta es que este es un concepto mucho más complejo de lo que pensé al principio.

Erik Funkenbusch
fuente
44
"Creo que la respuesta es que este es un concepto mucho más complejo de lo que pensé en un principio". Esto es muy cierto
quentin-starin
para su situación, puede crear un proxy para el objeto raíz agregado que recupere y almacene datos de forma selectiva solo cuando se solicite
Steven A. Lowe
Mi sugerencia es implementar una carga diferida en las asociaciones de agregación raíz. Para que pueda recuperar una lista de raíces sin cargar demasiados objetos.
Juliano
44
Casi 6 años después, sigue siendo una buena pregunta. Después de leer el capítulo en el libro rojo, diría: no hagas tus agregados demasiado grandes. Es tentador elegir algún concepto de nivel superior de su dominio y declararlo como la raíz para gobernarlos a todos, pero DDD aboga por agregados más pequeños. Y mitiga ineficiencias como las que describió anteriormente.
Cpt. Senkfuss
Sus agregados deben ser lo más pequeños posible a la vez que naturales y efectivos para el dominio (¡un desafío!). Además, está perfectamente bien y es deseable que sus repositorios tengan métodos altamente específicos .
Timo

Respuestas:

30

No use su modelo de dominio y agregados para realizar consultas.

De hecho, lo que está preguntando es una pregunta lo suficientemente común como para que se haya establecido un conjunto de principios y patrones para evitar eso. Se llama CQRS .

quentin-starin
fuente
2
@Mystere Man: No, es para proporcionar los datos mínimos necesarios. Ese es uno de los grandes propósitos de un modelo de lectura separado. También ayuda a abordar algunos problemas de concurrencia por adelantado. CQRS tiene varios beneficios cuando se aplica a DDD.
quentin-starin
2
@Mystere: Estoy bastante sorprendido de que te lo pierdas si lees el artículo al que me vinculé. Consulte la sección titulada "Consultas (informes)": "Cuando una aplicación solicita datos ... esto debe hacerse en una sola llamada a la capa de consulta y, a cambio, obtendrá un único DTO que contiene todos los datos necesarios ... . La razón de esto es que los datos normalmente se consultan muchas veces más de lo que se ejecuta el comportamiento del dominio, por lo que al optimizar esto mejorará el rendimiento percibido del sistema ".
quentin-starin
44
Es por eso que no usaríamos un Repositorio para leer datos en un sistema CQRS. Escribiríamos una clase simple para encapsular una consulta (usando cualquier tecnología que fuera conveniente o necesaria, a menudo directamente ADO.Net o Linq2Sql o SubSonic encaja bien aquí), devolviendo solo (todos) los datos necesarios para la tarea en cuestión, para evitar arrastrando datos a través de todas las capas normales de un repositorio DDD. Solo usaríamos un Repositorio para recuperar un Agregado si quisiéramos enviar un comando al dominio.
quentin-starin
9
"No puedo imaginar que alguien abogue por devolver agregados completos de información cuando no la necesite". Estoy tratando de decir que estás exactamente en lo correcto con esta afirmación. No recupere un conjunto completo de información cuando no la necesite. Este es el núcleo de CQRS aplicado a DDD. No necesita un agregado para consultar. Obtenga los datos a través de un mecanismo diferente y luego hágalo de manera consistente.
quentin-starin
2
@qes De hecho, la mejor solución es no usar DDD para consultas (leer) :) Pero aún usa DDD en la parte de Comando, es decir, para almacenar o actualizar datos. Entonces, tengo una pregunta para usted, ¿siempre usa repositorios con entidades cuando necesita actualizar datos en la base de datos? Digamos que necesita cambiar solo un valor pequeño en la columna (cambio de algún tipo), ¿todavía carga toda la entidad en la capa de aplicación, cambia un valor (propiedad) y luego guarda toda la entidad nuevamente en la base de datos? ¿Un poco de exageración también?
Andrew
8

Luché, y todavía estoy luchando, con la mejor manera de usar el patrón de repositorio en un diseño impulsado por dominio. Después de usarlo ahora por primera vez, se me ocurrieron las siguientes prácticas:

  1. Un repositorio debe ser simple; solo es responsable de almacenar objetos de dominio y recuperarlos. Toda otra lógica debe estar en otros objetos, como fábricas y servicios de dominio.

  2. Un repositorio se comporta como una colección como si fuera una colección en memoria de raíces agregadas.

  3. Un repositorio no es un DAO genérico, cada repositorio tiene su interfaz única y estrecha. Un repositorio a menudo tiene métodos de búsqueda específicos que le permiten buscar en la colección en términos del dominio (por ejemplo: darme todas las órdenes abiertas para el usuario X). El repositorio en sí se puede implementar con la ayuda de un DAO genérico.

  4. Idealmente, los métodos de búsqueda solo devolverán raíces agregadas. Si eso es demasiado ineficiente, también puede devolver objetos de valor de solo lectura que contengan exactamente lo que necesita (aunque es una ventaja si estos objetos de valor también se pueden expresar en términos del dominio). Como último recurso, el repositorio también se puede utilizar para devolver subconjuntos o colecciones de subconjuntos de una raíz agregada.

  5. Las opciones como estas dependen de las tecnologías utilizadas, ya que necesita encontrar una manera de expresar de manera más eficiente su modelo de dominio con las tecnologías utilizadas.

Kdeveloper
fuente
Definitivamente es un tema complejo seguro. Es difícil convertir la teoría en práctica, especialmente cuando combina dos teorías distintas y separadas en una sola práctica.
Sebastian Patten
6

No creo que su método GetOrderHeaders anule el propósito del repositorio en absoluto.

DDD se preocupa (entre otras cosas) de garantizar que obtenga lo que necesita a través de la raíz agregada (por ejemplo, no tendría un OrderDetailsRepository), pero no lo limita en la forma en que lo menciona.

Si un OrderHeader es un concepto de Dominio, entonces debe tenerlo definido como tal y tener los métodos de repositorio apropiados para recuperarlos. Solo asegúrese de pasar por la raíz agregada correcta cuando lo haga.

Eric King
fuente
Tal vez estoy confundiendo conceptos aquí, pero mi comprensión del patrón de repositorio es desacoplar la persistencia del dominio, mediante el uso de una interfaz estándar para la persistencia. Si tiene que agregar métodos personalizados para una característica específica, parece que las cosas se acoplan nuevamente.
Erik Funkenbusch
1
El mecanismo de persistencia se desacopla del dominio, pero no lo que se persiste. Si te encuentras diciendo cosas como "necesitamos enumerar los encabezados de pedidos aquí", entonces debes modelar OrderHeader en tu dominio y proporcionar una forma de recuperarlos de tu repositorio.
Eric King
Además, no se obsesione con la "interfaz estándar para la persistencia". No existe un patrón de repositorio genérico que sea suficiente para todas las aplicaciones posibles. Cada aplicación tendrá muchos métodos de repositorio más allá del estándar "GetById", "Guardar", etc. Esos métodos son el punto de partida, no el punto final.
Eric King
4

Mi uso de DDD puede no considerarse DDD "puro", pero he adaptado las siguientes estrategias del mundo real usando DDD contra un almacén de datos DB.

  • Una raíz agregada tiene un repositorio asociado
  • El repositorio asociado solo lo usa esa raíz agregada (no está disponible públicamente)
  • Un repositorio puede contener llamadas de consulta (por ejemplo, GetAllActiveOrders, GetOrderItemsForOrder)
  • Un servicio expone un subconjunto público del repositorio y otras operaciones no críticas (por ejemplo, transferir dinero de una cuenta bancaria a otra, LoadById, Search / Find, CreateEntity, etc.).
  • Yo uso la pila Root -> Servicio -> Repositorio. Se supone que un servicio DDD solo se usa para cualquier cosa que una entidad no pueda responderse a sí misma (por ejemplo, LoadById, TransferMoneyFromAccountToAccount), pero en el mundo real también tiendo a pegarme en otros servicios relacionados con CRUD (Guardar, Eliminar, Consultar) aunque root debe ser capaz de "responder / realizar" ellos mismos. ¡Tenga en cuenta que no hay nada de malo en dar a una entidad acceso a otro servicio raíz agregado! Sin embargo, recuerde que no incluiría en un servicio (GetOrderItemsForOrder) sino que lo incluiría en el repositorio para que la raíz agregada pueda utilizarlo. Tenga en cuenta que un servicio no debe exponer ninguna consulta abierta como puede hacerlo el repositorio.
  • Normalmente defino un repositorio de forma abstracta en el modelo de dominio (a través de la interfaz) y proporciono una implementación concreta por separado. Defino completamente un servicio en el modelo de dominio inyectando en un repositorio concreto para su uso.

** No tiene que traer de vuelta un agregado completo. Sin embargo, si quieres más, tienes que preguntarle a la raíz, no a algún otro servicio o repositorio. Esta es una carga diferida y se puede hacer manualmente con una carga diferida (inyectando el repositorio / servicio apropiado en la raíz) o usando un ORM que lo admita.

En su ejemplo, probablemente proporcionaría una llamada al repositorio que traería solo los encabezados de los pedidos si quisiera cargar los detalles en una llamada separada. Tenga en cuenta que al tener un "OrderHeader" estamos introduciendo un concepto adicional en el dominio.

Mike Rowley
fuente
3

Su modelo de dominio contiene su lógica empresarial en su forma más pura. Todas las relaciones y operaciones que respaldan las operaciones comerciales. Lo que le falta a su mapa conceptual es la idea de la capa de servicio de aplicación, la capa de servicio se ajusta al modelo de dominio y proporciona una vista simplificada del dominio empresarial (una proyección si lo desea) que permite que el modelo de dominio cambie según sea necesario sin afectar directamente las aplicaciones que utilizan la capa de servicio.

Ir más lejos. La idea del agregado es que hay un objeto, la raíz del agregado, responsable de mantener la consistencia del agregado. En su ejemplo, el pedido sería responsable de manipular sus líneas de pedido.

Para su ejemplo, la capa de servicio expondría una operación como GetOrdersForCustomer que solo devolvería lo que se necesita para ver una lista resumida de los pedidos (como los llama OrderHeaders).

Finalmente, el patrón Repository no es SOLO una colección, sino que también permite consultas declarativas. En C # puede usar LINQ como objeto de consulta , o la mayoría de los otros O / RM también proporcionan una especificación de objeto de consulta.

Un repositorio media entre el dominio y las capas de mapeo de datos, actuando como una colección de objetos de dominio en memoria. Los objetos del cliente construyen especificaciones de consulta de forma declarativa y las envían al Repositorio para su satisfacción. (de la página del Repositorio de Fowler )

Al ver que puede crear consultas en el repositorio, también tiene sentido proporcionar métodos convenientes que manejen consultas comunes. Es decir, si solo desea los encabezados de su pedido, puede crear una consulta que devuelva solo el encabezado y exponerlo desde un método conveniente en sus repositorios.

Espero que esto ayude a aclarar las cosas.

Michael Brown
fuente
0

Sé que esta es una vieja pregunta, pero parece que he llegado a una respuesta diferente.

Cuando hago un repositorio, generalmente se envuelven algunas consultas en caché .

Fowler define un repositorio como un almacén de datos que utiliza semántica de recopilación, y generalmente se mantiene en la memoria. Esto significa crear un gráfico de objeto completo.

Mantenga esos repositorios en sus servidores ram. ¡No solo pasan objetos a la base de datos!

Si estoy en una aplicación web con una página de listado de pedidos, en la que puede hacer clic para ver los detalles, es probable que quiera que mi página de listado de pedidos tenga detalles sobre los pedidos (ID, Nombre, Cantidad, Fecha) para ayudar a un usuario a decidir cuál quiere mirar.

En este punto tienes dos opciones.

  1. Puede consultar la base de datos y retirar exactamente lo que necesita para hacer la lista, luego consultar nuevamente para obtener los detalles individuales que necesitaría ver en la página de detalles.

  2. Puede realizar 1 consulta que retire toda la información y la almacene en caché. En la página siguiente, solicite que lea de la RAM de los servidores en lugar de la base de datos. Si el usuario responde o selecciona la siguiente página, todavía no realiza ningún viaje a la base de datos.

En realidad, cómo lo implementa es solo eso, y detalles de implementación. Si mi mayor usuario tiene 10 pedidos, probablemente quiera ir con la opción 2. Si estoy hablando de 10,000 pedidos, entonces se necesita la opción 1. En los dos casos anteriores y en muchos otros casos, quiero que el repositorio oculte los detalles de la implementación.

En el futuro, si obtengo un ticket para decirle al usuario cuánto han gastado en pedidos ( datos agregados ) en el último mes en la página de listado de pedidos, preferiría escribir la lógica para calcular eso en SQL y hacer otro viaje de ida y vuelta a la base de datos o preferiría calcularlo utilizando los datos que ya están en el servidor ram?

En mi experiencia, los agregados de dominio ofrecen enormes beneficios.

  • Son una gran reutilización de código de pieza que realmente funciona.
  • Simplifican el código manteniendo la lógica de su negocio justo en la capa central en lugar de tener que profundizar en una capa de infraestructura para que el servidor sql lo haga.
  • También pueden acelerar drásticamente sus tiempos de respuesta al reducir la cantidad de consultas que necesita hacer, ya que puede almacenarlas en caché fácilmente.
  • El SQL que estoy escribiendo a menudo es mucho más fácil de mantener, ya que a menudo solo estoy pidiendo todo y calculando el lado del servidor.
WhiteleyJ
fuente