¿Cómo trabajar con grandes raíces agregadas?

11

Estoy aprendiendo DDD y, sin embargo, tengo más preguntas que respuestas.

Consideremos un modelo de un directorio que contiene una enorme cantidad de archivos.
Así es como lo veo:

El directorio es una raíz agregada.
Esta entidad debe tener la lógica de validación de verificar la unicidad del nombre del archivo cuando se agrega o se renombra. Y la entidad File contiene la lógica 'SetName', que notifica al Directorio a través del Evento de Dominio sobre los cambios de nombre.
Pero, ¿cómo debería funcionar el directorio?
No siempre es posible cargar todos los archivos en la memoria. ¿Debería en este caso el repositorio de archivos tener lógica adhoc para verificar la unicidad del nombre? Supongo que es una decisión viable.
Sin embargo, ¿qué sucede si algunos archivos ya se han agregado o renombrado dentro de una transacción actual aún no confirmada? (nada lo prohíbe. Los límites de las transacciones se establecen externamente en relación con la lógica empresarial). Probablemente el repositorio debe tener en cuenta tanto los estados en memoria como los persistentes (fusionar estos estados puede ser una tarea no trivial).

Entonces, cuando la raíz agregada con todos sus elementos secundarios cabe en la memoria, todo está bien. Y tan pronto como no puedas materializar todas las entidades, hay problemas.

Me gustaría saber cuáles son los enfoques para tales situaciones. Puede que no haya ningún problema en absoluto y es solo debido a mi malentendido sobre el tema.

Pavel Voronin
fuente
1
¿Qué te hace pensar que tienes que cargar todos los archivos y su contenido y no solo "FileInfo"?
Eufórico
@Eufórico. Bueno, a veces incluso eso no es posible. De todos modos hay otro problema. ¿Cómo proporcionar coherencia de FileInfo y las entidades de archivo correspondientes modificadas dentro de la transacción actual? Probablemente CQRS aborde esta pregunta ... aún no la he mirado.
Pavel Voronin
1
Es útil comprender que DDD no es una técnica de programación, sino más bien una técnica de diseño. Demasiadas personas lo tratan como una metodología de codificación. Términos como "raíz agregada" agravan el problema, porque dan la impresión de peso técnico, cuando en realidad no hablan mucho de las técnicas de programación. Las técnicas de programación no cambian mucho en DDD; Si bien DDD informa su código y arquitectura, aún tiene código y arquitectura separados de él.
Robert Harvey
@RobertHarvey Me parece que DDD requiere técnicas de programación más complejas. Al menos cuando se trata de casos de esquina. Trato DDD principalmente como una forma de separar (y localizar) la lógica empresarial del inevitable código de infraestructura que funciona implícitamente detrás de escena. Para mí DDD = buena OOD + infraestructura implícita. La mayoría de las preguntas que tengo sobre DDD se refieren a la última parte.
Pavel Voronin
2
¿Por qué dice: "Los límites de transacción se establecen externamente en relación con la lógica empresarial"? El deber de la raíz agregada es mantener un límite transaccional. Además, no tiene que cargar el contenido de los archivos. Simplemente puede cargar metadatos.
Esben Skov Pedersen

Respuestas:

20

Mi respuesta está sesgada con el gran libro de Vaughn Vernon Implementing Domain Driven Design (una lectura obligada)

1. Favorecer los agregados pequeños.

Si voy a modelar su dominio, modelaría un Directorycomo agregado y Filecomo otro agregado.

2. Agregados de referencia por id.

Por Directorylo tanto tendrá una colección de FileIdobjetos de valor.

3. Use fábricas para crear agregados.

Para un caso simple, un método de fábrica puede ser suficiente Directory.addFile(FileName fileName). Sin embargo, para casos más complejos, usaría una fábrica de dominios.
La fábrica de dominios podría validar que fileNamees única utilizando ay FileRepositoryun UniquefileNameValidatorservicio de infraestructura.

¿Por qué modelar Filecomo un agregado separado?

Porque Directoriesno están hechos de Files. a Fileestá asociado con un cierto Directory. Además, piense en un directorio que tenga miles de archivos. Cargar todos estos objetos en la memoria cada vez que se recupera un directorio es un factor decisivo para el rendimiento.

Modele sus agregados de acuerdo con sus casos de uso. Si sabe que nunca habrá más de 2-3 archivos en un directorio, puede modelarlos como un agregado único, pero en mi experiencia las reglas de negocio cambian todo el tiempo y paga si su modelo era lo suficientemente flexible como para acomodar cambios

Obligatorio leer Diseño Agregado Efectivo por Vaughn Vernon

Songo
fuente
2
Tenga en cuenta que, de hecho, las implementaciones de directorios reales generalmente se implementan de esta manera (agregados separados, referenciados por sus identificadores). Por ejemplo, los "archivos" son simplemente referenciados desde su directorio por sus inodesistemas Unix.
Alexander Langer
1
Finalmente he leído el libro recomendado. Gran libro, gracias. Sin embargo, todavía no está completamente claro. Solo Filepuede existir uno con Nombre dado Directory, es decir, Directorytiene la invariante: Unicidad de nombre. Al menos es importante para el Directory, pero no para el File. En realidad, @AlexanderLanger tiene razón: muchas carpetas pueden hacer referencia a 'Archivo'. Y el Nombre es probablemente la propiedad de esta referencia en lugar de la Filepropia. Okay. Entonces, la funcionalidad de cambio de nombre pertenece al Directory, pero de nuevo, no es una buena idea almacenar miles de identidades referenciadas.
Pavel Voronin
E incluso si lo hiciéramos, tendríamos que verificar los nombres. Desde aquí parece razonable para crear un método en el repositorio de carpetas: bool ContainsFileOfName(int folderId, string fileName). Después de eso, la firma del método 'Renombrar' puede ser la siguiente: void Rename(int fileId, string newName)donde dentro de algunos IFolderService(que envuelve el repositorio) se resuelve y se le pregunta si existe ese nombre.
Pavel Voronin
1

Esta no es una pregunta DDD per se. La pregunta principal aquí es sobre el contexto de sincronización (que aquí es una raíz agregada).

Volviendo al tema: el directorio bloqueará en algún objeto de sincronización de nombres de archivos y realizará una verificación si el nombre de archivo dado está permitido, que es O (n) en el peor de los casos.

Mare Infinitus
fuente
1

Aunque algunos pueden decir que intente cambiar su diseño, siempre es necesario que un AR tenga una gran lista de objetos simples. Y almacenarlos en la memoria no es lo mejor desde una perspectiva de rendimiento, sin embargo, todo lo que necesita hacer en tales casos es preservar los límites de la transacción. Una solución simple es la siguiente:

  1. Mantenga el AR de la carpeta simple y coloque una columna de versión.
  2. Haga que cada archivo (digamos fila) haga referencia a la raíz agregada, en otras palabras, para mantener la identificación de la carpeta.
  3. Siempre realice los cambios de los archivos, como cambiar el nombre, agregar, eliminar, a través del AR. En otras palabras, si desea agregar un archivo, cargue el AR y use un método en AR para hacer el cambio. Suponiendo que está utilizando un patrón de repositorio, addFile () creará un nuevo archivo, cambiará la versión de la carpeta. Guárdelos como UoW. Si alguien más ha cambiado el AR, obtendrá un error debido a la columna de versión (la versión AR).
  4. Cualquier cambio en el archivo en sí, como editar el archivo o renombrarlo, debe hacerse a través del AR. Entonces la versión se mantiene en el AR. Esto significa esencialmente que no hay otras rutas de ejecución en su código que cambien el archivo, excepto el AR propietario.

Algunas restricciones:

  1. El archivo debe pertenecer a un solo AR. Si este no es el caso, entonces modele la relación de Carpeta -> Archivo como contención y no el archivo en sí.
  2. No puede mover un archivo de una carpeta a otra, a menos que haga este cambio en una UoW, en otras palabras, en la misma transacción. Este es un caso especial, ya que debe enviar una solicitud que terminará en dos comandos, por lo que se cambiarán ambos AR, por lo tanto, dos nuevas versiones para cada uno y probablemente dos eventos (archivo eliminado, archivo agregado) y esto es un poco complicado porque debes mantener los pedidos de eventos para los dos AR, lo que a veces no es fácil.
ligu
fuente