2011-02-07 8 views
5

¡Soy uno de los muchos que intenta entender el concepto de raíces agregadas, y creo que lo tengo! Sin embargo, cuando comencé a modelar este proyecto de muestra, rápidamente me encontré con un dilema.Raíz agregada simple y repositorio

Tengo las dos entidades ProcessType y Process. Un Process no puede existir sin un ProcessType, y un ProcessType tiene muchos Process es. Entonces, un proceso contiene una referencia a un tipo, y no puede existir sin él.

¿Debería ser ProcessType una raíz agregada? Se crearían nuevos procesos llamando al processType.AddProcess(new Process()); Sin embargo, tengo otras entidades que solo tienen una referencia al Process, y accede a su tipo a través del Process.Type. En este caso, no tiene sentido pasar primero por ProcessType.

Pero las entidades AFAIK fuera del agregado solo pueden contener referencias a la raíz del agregado, y no a entidades dentro del agregado. Entonces, ¿tengo dos agregados aquí, cada uno con su propio repositorio?

Respuesta

13

Estoy de acuerdo en gran medida con lo que Sísifo ha dicho, sobre todo lo de la constricción no a sí mismo a las 'reglas 'de DDD que puede conducir a una solución bastante ilógica.

En cuanto a su problema, me he encontrado con la situación muchas veces, y yo llamaría 'ProcessType' como una búsqueda. Las búsquedas son objetos que 'definen', y tienen no referencias a otras entidades; en la terminología DDD, son objetos de valor. Otros ejemplos de lo que yo llamaría una búsqueda podría ser un "RoleType" de un miembro del equipo, que podría ser un probador, desarrollador, gerente de proyecto, por ejemplo. Incluso de una persona 'Título' yo definiría como una búsqueda - Sr., señorita, señora, el Dr.

Me modelar el agregado proceso como:

public class Process 
{ 
    public ProcessType { get; } 
} 

Como usted dice, este tipo de objetos suelen necesitar para poblar los menús desplegables en la UI y, por lo tanto, necesita su propio mecanismo de acceso a los datos. Sin embargo, personalmente NO he creado 'repositorios' como tales para ellos, sino más bien un 'Servicio de búsqueda'. Esto para mí conserva la elegancia de DDD al mantener los "repositorios" estrictamente para raíces agregadas.

Aquí se muestra un ejemplo de un controlador de comandos en mi servidor de aplicaciones y cómo me han puesto en práctica lo siguiente:

equipo agregado de usuario:

public class TeamMember : Person 
{ 
    public Guid TeamMemberID 
    { 
     get { return _teamMemberID; } 
    } 

    public TeamMemberRoleType RoleType 
    { 
     get { return _roleType; } 
    } 

    public IEnumerable<AvailabilityPeriod> Availability 
    { 
     get { return _availability.AsReadOnly(); } 
    } 
} 

controlador de comandos:

public void CreateTeamMember(CreateTeamMemberCommand command) 
{ 
    TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID); 

    TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID, 
                  role, 
                  command.DateOfBirth, 
                  command.FirstName, 
                  command.Surname); 

    using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork()) 
     _teamMemberRepository.Save(member); 
} 

El cliente también puede hacer uso de LookupService para poblar el menú desplegable, etc.:

ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>(); 
+0

Enfoque muy interesante David. ¡Gracias por compartirlo! ¿Cómo implementó LookupService? ¿Como un servicio de infraestructura con un método Get genérico que utiliza el marco ORM subyacente directamente sin repositorios? ¿Alguien más puede comentar sobre este enfoque? ¿Hiciste algo similar, o resolviste este problema de otra manera? – Vern

+2

Tengo ILookupService definido en mi infraestructura, luego una implementación en un proyecto separado que se carga usando DI (unidad). La implementación es en bruto SQL/Sprocs, pero por supuesto puede hacer uso fácilmente de un ORM. Este servicio puede ser utilizado por el dominio y por los servicios de consulta para proporcionar ViewModels a la interfaz de usuario. La razón por la que uso los genéricos es porque todos los 'LookupItems' simplemente tienen una identificación, descripción y lista de atributos. Esto significa que puedo usar un medio genérico para almacenar los datos de todos los elementos de búsqueda/búsqueda usando su nombre de tipo. ES DECIR. No tendría una tabla llamada 'ProcessType'. –

+0

Ya veo. Simplemente intenté implementarlo y parece una solución elegante. Por ahora me atendré a este enfoque a menos que alguien venga y nos diga que tienen una mejor solución :-) Dejaré que la pregunta permanezca abierta durante unos días para obtener más opiniones sobre este tema. – Vern

10

No es tan simple. ProcessType es muy probablemente un objeto de capa de conocimiento: define un determinado proceso. Por otro lado, Process es una instancia de un proceso que es ProcessType. Probablemente no necesites ni quieras la relación bidireccional. El proceso probablemente no sea un elemento lógico de un ProcessType. Por lo general, pertenecen a otra cosa, como un Producto, o Fábrica o Secuencia.

También por definición, cuando elimina una raíz agregada, elimina todos los miembros del agregado. Cuando elimina un Proceso, dudo seriamente que realmente desee eliminar ProcessType. Si eliminó ProcessType, es posible que desee eliminar todos los procesos de ese tipo, pero esa relación ya no es ideal y es probable que no elimine objetos de definición tan pronto como tenga un Process histórico definido por ProcessType.

Quitaría la colección de Procesos de ProcessType y encontraría un padre más adecuado si existe. Mantendría el ProcessType como miembro de Process ya que probablemente define Process. Los objetos capa operativa (proceso) y capa de conocimiento (tipo de proceso) rara vez funcionan como un agregado único, por lo que tendría que procesar una raíz agregada o posiblemente encontrar una raíz agregada que sea un proceso padre. Entonces ProcessType sería una clase externa. Es muy probable que Process.Type sea redundante ya que ya tiene Process.ProcessType. Solo deshazte de eso.

Tengo un modelo similar para el cuidado de la salud. Hay Procedimiento (capa operativa) y ProcedureType (capa de conocimiento). ProcedureType es una clase independiente. El procedimiento es un hijo de un tercer objeto Encuentro. Encounter es la raíz agregada para el Procedimiento. El procedimiento tiene una referencia a ProcedureType pero es de una manera. ProcedureType es un objeto de definición, no contiene una colección de Procedimientos.

EDITAR (ya que los comentarios son tan limitadas)

Una cosa a tener en cuenta por todo esto. Muchos son puristas de DDD e inflexibles sobre las reglas. Sin embargo, si lees detenidamente a Evans, constantemente plantea la posibilidad de que a menudo se requieran sacrificios. También hace todo lo posible para caracterizar las decisiones de diseño lógicas y cuidadosamente pensadas frente a cosas como equipos que no entienden los objetivos o eluden cosas como los agregados por conveniencia.

Lo importante es comprender y aplicar los conceptos en lugar de las reglas.Veo muchos DDD que calzan una aplicación en agregados ilógicos y confusos, etc. solo porque se está aplicando una regla literal sobre repositorios o cruce. Esa no es la intención de DDD, pero a menudo es el producto del enfoque excesivamente dogmático de muchos tomar.

Entonces, ¿qué son los conceptos clave aquí:

agregados proporcionan un medio para hacer un complejo sistema más manejable mediante la reducción de los comportamientos de muchos objetos en comportamientos de alto nivel de los jugadores clave.

Los agregados proporcionan un medio para garantizar que los objetos se creen en una condición lógica y siempre válida que también preserve una unidad lógica de trabajo entre actualizaciones y eliminaciones.

Consideremos el último punto. En muchas aplicaciones convencionales, alguien crea un conjunto de objetos que no están completamente llenos porque solo necesitan actualizar o usar algunas propiedades. El siguiente desarrollador aparece y también necesita estos objetos, y alguien ya ha creado un decorado en algún lugar del vecindario para un propósito diferente. Ahora este desarrollador decide usarlos, pero luego descubre que no tienen todas las propiedades que necesita. Entonces agrega otra consulta y completa algunas propiedades más. Eventualmente porque el equipo no se adhiere a OOP porque toman la actitud común de que OOP es "ineficiente e impráctico para el mundo real y causa problemas de rendimiento como la creación de objetos completos para actualizar una sola propiedad". Con lo que terminan es una aplicación llena de código SQL incorporado y objetos que esencialmente se materializan aleatoriamente en cualquier lugar. Peor aún, estos objetos son proxies inválidos bastardos. Un proceso parece ser un proceso, pero no lo es, está parcialmente poblado de diferentes maneras en un punto determinado, según lo que se necesite. Usted termina con una bola de lodo de numerosas consultas para poblar objetos de forma variable en diversos grados y, a menudo, una gran cantidad de basura como controles nulos que no deberían existir pero son necesarios porque el objeto nunca es realmente válido, etc.

Reglas agregadas evite esto asegurando que los objetos se creen solo en ciertos puntos lógicos y siempre con un conjunto completo de relaciones y condiciones válidas. Entonces, ahora que entendemos por completo para qué son las reglas agregadas y de qué nos protegen, también queremos entender que tampoco queremos hacer un mal uso de estas reglas y crear agregados extraños que no reflejan de qué se trata nuestra aplicación simplemente porque estas reglas agregadas existen y se deben seguir en todo momento.

Así que cuando Evans dice crear repositorios solo para agregados, está diciendo crear agregados en un estado válido y mantenerlos así en lugar de pasar directamente por el agregado de los objetos internos. Tiene un Proceso como un agregado raíz para que pueda crear un repositorio. ProcessType no es parte de ese agregado. ¿Qué haces? Bien, si un objeto es solo y es una entidad, es un agregado de 1. Usted crea un repositorio para él.

Ahora el purista vendrá y dirá que no debería tener ese repositorio porque ProcessType es un objeto de valor, no una entidad. Por lo tanto, ProcessType no es un agregado en absoluto y, por lo tanto, no crea un repositorio para él. Entonces, ¿Qué haces? Lo que no hace es meter a ProcessType en algún tipo de modelo artificial sin otro motivo que el que necesita para obtenerlo, por lo que necesita un repositorio, pero para tener un repositorio, debe tener una entidad como raíz agregada. Lo que haces es considerar cuidadosamente los conceptos. Si alguien le dice que el repositorio es incorrecto, pero usted sabe que lo necesita y lo que sea que digan, su sistema de repositorio es válido y preserva los conceptos clave, mantenga el repositorio como está en lugar de deformar su modelo para satisfacer el dogma.

Ahora, en este caso, suponiendo que estoy en lo cierto sobre qué es ProcessType, ya que el otro comentarista señaló que se trata de un objeto de valor. Usted dice que no puede ser un objeto de valor. Eso podría ser por varias razones.Quizás lo diga porque utiliza NHibernate, por ejemplo, pero el modelo NHibernate para implementar objetos de valor en la misma tabla que otro objeto no funciona. Entonces, su ProcessType requiere una columna de identidad y un campo. A menudo, debido a consideraciones de bases de datos, la única implementación práctica es tener objetos de valor con identificadores en su propia tabla. O tal vez dices eso porque cada Proceso apunta a un único Tipo de Proceso por referencia.

No importa. Es un Objeto de valor debido al concepto. Si tiene 10 objetos de proceso que son del mismo tipo de proceso, tiene 10 miembros y valores Process.ProcessType. Si cada Process.ProcessType apunta a una sola referencia, o si cada uno obtuvo una copia, deberían ser, por definición, exactamente las mismas cosas y todas ser completamente intercambiables con cualquiera de las otras 10. ESTO es lo que lo convierte en un Objeto de valor. La persona que dice "Tiene un Id. Por lo tanto no puede ser un Objeto de valor tiene una entidad" está cometiendo un error dogmático. No cometa el mismo error, si necesita un campo de ID, proporciónele uno, pero no diga "no puede ser un Objeto de valor" cuando, de hecho, es uno que, por algún otro motivo, debe proporcionar un Id. a.

Entonces, ¿cómo entiendes que está bien y mal? ProcessType es un objeto de valor, pero por alguna razón necesita que tenga un Id. El Id per se no viola las reglas. Lo haces bien al tener 10 procesos que tienen un ProcessType que es exactamente el mismo. Tal vez cada uno tenga una copia local profunda, tal vez todos apuntan a un objeto. pero cada uno es idéntico de cualquier manera, ergo cada uno tiene un Id = 2, por ejemplo. Lo que obtienes es incorrecto cuando haces esto: 10 Procesos tienen cada uno un Tipo de Proceso, y este Tipo de Proceso es idéntico y completamente intercambiable, EXCEPTO ahora, cada uno también tiene su propio Id. Único. Ahora tiene 10 instancias de la misma cosa pero varían solo en Id, y siempre variarán solo en Id. Ahora ya no tiene un Objeto de valor, no porque le dio un Id, sino porque le dio un Id con una implementación que refleja la naturaleza de una entidad: cada instancia es única y diferente

¿Tiene sentido?

+0

Muchas gracias, tiene toda la razón. Pero según Evans, en su libro azul, dice que creas repositorios para agregados. Sin embargo, en mi caso, necesito un repositorio para la clase independiente (ProcessType), porque necesito poder crear nuevos tipos de procesos. No es que sucediera con mucha frecuencia, pero es una opción. ¿Cómo manejarías eso? – Vern

+0

Por crear, es decir, persistir el objeto en la base de datos, para lo cual utilizo el patrón de repositorio. El repositorio no es responsable de crear los objetos. – Vern

+0

Al usar un diseño basado en la capa de conocimiento, considere si la creación de conocimiento en sí misma debería o no ser parte de la aplicación principal. Depende En mi caso, hay millones de procedimientos y síntomas. ¿Necesitas uno que no esté en el sistema? Hay una aplicación para eso. Y un médico de cabecera que puede usarlo. Así que tenemos un nuevo paciente que tiene carne comer bayas, 1 de 10 casos conocidos. El hombre en la parte superior va a decir que hay una escritura para eso. Los doctores orinan y se lamentan, pero si se les diera el paso, su propio sistema quedaría inutilizable en un mes. Una preocupación diferente tiene una aplicación diferente que no forma parte de la primaria. – Sisyphus

0

Mira, creo que tienes que reestructurar tu modelo. Use ProcessType como un Value Object y Process Agg Root. De esta manera Cada proceso tiene un processType

Public class Process 
{ 
     Public Process() 
     { 

     } 

     public ProcessType { get; } 

} 

para este u sólo necesita 1 raíz agg no

2.
+0

ProcessType no puede ser un objeto de valor en este escenario. No tiene sentido en el modelo. Entonces me temo que no es una solución. – Vern

+0

:) compruebe las respuestas anteriores: P –

Cuestiones relacionadas