2012-06-13 8 views
9

Soy bastante nuevo en el mundo de DDD y después de leer un par de libros sobre el tema (Evans DDD entre ellos) no pude encontrar la respuesta a mi pregunta en Internet: ¿cuál es la forma correcta? de crear entidades secundarias con DDD? Usted ve, mucha información en internet opera en un nivel simple. Pero los demonios en los detalles y siempre se omiten en docenas de muestras de DDD en aras de la simplicidad.Forma correcta de crear entidades secundarias con DDD

Vengo de my own answer en la pregunta similar aquí en stackoverflow. No estoy completamente satisfecho con mi propia visión sobre este problema, así que pensé que tenía que dar más detalles sobre este asunto.

Por ejemplo, necesito crear un modelo simple que represente autos nombrando: compañía, modelo y modificación (por ejemplo, Nissan Teana 2012 - que será la compañía "Nissan", modelo "Teana" y modificación "2012").

El boceto del modelo Quiero crear el aspecto siguiente:

CarsCompany 
{ 
    Name 
    (child entities) Models 
} 

CarsModel 
{ 
    (parent entity) Company 
    Name 
    (child entities) Modifications 
} 


CarsModification 
{ 
    (parent entity) Model 
    Name 
} 

lo tanto, ahora tengo que crear código. Usaré C# como idioma y NHibernate como ORM. Esto es importante y lo que normalmente no se muestra en vastas muestras de DDD en Internet.

El primer acercamiento.

Comenzaré con un enfoque simple con la creación de objetos típica a través de los métodos de fábrica.

public class CarsCompany 
{ 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } } 


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel>(); 


    protected CarsCompany() 
    { 
    } 


    public static CarsCompany Create (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsCompany 
     { 
      Name = name 
     }; 
    } 


    public void AddModel (CarsModel model) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     this._models.Add (model); 
    } 
} 


public class CarsModel 
{ 
    public virtual CarsCompany Company { get; protected set; } 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } } 


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification>(); 


    protected CarsModel() 
    { 
    } 


    public static CarsModel Create (CarsCompany company, string name) 
    { 
     if (company == null) 
      throw new ArgumentException ("Company is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsModel 
     { 
      Company = company, 
      Name = name 
     }; 
    } 


    public void AddModification (CarsModification modification) 
    { 
     if (modification == null) 
      throw new ArgumentException ("Modification is not specified."); 

     this._modifications.Add (modification); 
    } 
} 


public class CarsModification 
{ 
    public virtual CarsModel Model { get; protected set; } 
    public virtual string Name { get; protected set; } 


    protected CarsModification() 
    { 
    } 


    public static CarsModification Create (CarsModel model, string name) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsModification 
     { 
      Model = model, 
      Name = name 
     }; 
    } 
} 

Lo malo de este enfoque es que la creación del modelo no añadirlo a la colección de modelos de padres:

using (var tx = session.BeginTransaction()) 
{ 
    var company = CarsCompany.Create ("Nissan"); 

    var model = CarsModel.Create (company, "Tiana"); 
    company.AddModel (model); 
    // (model.Company == company) is true 
    // but (company.Models.Contains (model)) is false 

    var modification = CarsModification.Create (model, "2012"); 
    model.AddModification (modification); 
    // (modification.Model == model) is true 
    // but (model.Modifications.Contains (modification)) is false 

    session.Persist (company); 
    tx.Commit(); 
} 

Después de la transacción se confirma y la sesión se vacía, el ORM escriba correctamente todo en la base de datos y la próxima vez que carguemos esa compañía, su colección de modelos contendrá nuestro modelo correctamente. Lo mismo ocurre con la modificación. Por lo tanto, este enfoque deja a nuestra entidad padre en estado inconsistente hasta que se haya vuelto a cargar desde la base de datos. No vayas.

El segundo enfoque.

Esta vez usaremos una opción específica de idioma para resolver el problema de configuración de propiedades protegidas de otras clases, es decir, vamos a usar el modificador "protected internal" tanto en setters como en constructor.

public class CarsCompany 
{ 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } } 


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel>(); 


    protected CarsCompany() 
    { 
    } 


    public static CarsCompany Create (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsCompany 
     { 
      Name = name 
     }; 
    } 


    public CarsModel AddModel (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var model = new CarsModel 
     { 
      Company = this, 
      Name = name 
     }; 

     this._models.Add (model); 

     return model; 
    } 
} 


public class CarsModel 
{ 
    public virtual CarsCompany Company { get; protected internal set; } 
    public virtual string Name { get; protected internal set; } 
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } } 


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification>(); 


    protected internal CarsModel() 
    { 
    } 


    public CarsModification AddModification (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var modification = new CarsModification 
     { 
      Model = this, 
      Name = name 
     }; 

     this._modifications.Add (modification); 

     return modification; 
    } 
} 


public class CarsModification 
{ 
    public virtual CarsModel Model { get; protected internal set; } 
    public virtual string Name { get; protected internal set; } 


    protected internal CarsModification() 
    { 
    } 
} 

... 

using (var tx = session.BeginTransaction()) 
{ 
    var company = CarsCompany.Create ("Nissan"); 
    var model = company.AddModel ("Tiana"); 
    var modification = model.AddModification ("2011"); 

    session.Persist (company); 
    tx.Commit(); 
} 

Esta vez, cada creación de entidad deja la entidad principal y secundaria en estado consistente. Pero la validación del estado de entidad hijo se filtró a la entidad principal (métodos AddModel y AddModification). Como no soy un experto en DDD, no estoy seguro si está bien o no. Podría crear más problemas en el futuro cuando las propiedades de las entidades secundarias no puedan establecerse simplemente a través de propiedades y la configuración de un estado basado en parámetros pasados ​​requeriría un trabajo más complejo que asignara valor de parámetro a la propiedad. Estaba bajo la impresión de que deberíamos concentrar la lógica sobre la entidad dentro de esa entidad donde sea que sea posible. Para mí, este enfoque convierte el objeto principal en algún tipo de Entity & Factory hybrid.

El tercer enfoque.

Bien, vamos a invertir las responsabilidades de mantener las relaciones entre padres e hijos.

public class CarsCompany 
{ 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModel> Models { get { return new ImmutableSet<CarsModel> (this._models); } } 


    private readonly ISet<CarsModel> _models = new HashedSet<CarsModel>(); 


    protected CarsCompany() 
    { 
    } 


    public static CarsCompany Create (string name) 
    { 
     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     return new CarsCompany 
     { 
      Name = name 
     }; 
    } 


    protected internal void AddModel (CarsModel model) 
    { 
     this._models.Add (model); 
    } 
} 


public class CarsModel 
{ 
    public virtual CarsCompany Company { get; protected set; } 
    public virtual string Name { get; protected set; } 
    public virtual IEnumerable<CarsModification> Modifications { get { return new ImmutableSet<CarsModification> (this._modifications); } } 


    private readonly ISet<CarsModification> _modifications = new HashedSet<CarsModification>(); 


    protected CarsModel() 
    { 
    } 


    public static CarsModel Create (CarsCompany company, string name) 
    { 
     if (company == null) 
      throw new ArgumentException ("Company is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var model = new CarsModel 
     { 
      Company = company, 
      Name = name 
     }; 

     model.Company.AddModel (model); 

     return model; 
    } 


    protected internal void AddModification (CarsModification modification) 
    { 
     this._modifications.Add (modification); 
    } 
} 


public class CarsModification 
{ 
    public virtual CarsModel Model { get; protected set; } 
    public virtual string Name { get; protected set; } 


    protected CarsModification() 
    { 
    } 


    public static CarsModification Create (CarsModel model, string name) 
    { 
     if (model == null) 
      throw new ArgumentException ("Model is not specified."); 

     if (string.IsNullOrEmpty (name)) 
      throw new ArgumentException ("Invalid name specified."); 

     var modification = new CarsModification 
     { 
      Model = model, 
      Name = name 
     }; 

     modification.Model.AddModification (modification); 

     return modification; 
    } 
} 

... 

using (var tx = session.BeginTransaction()) 
{ 
    var company = CarsCompany.Create ("Nissan"); 
    var model = CarsModel.Create (company, "Tiana"); 
    var modification = CarsModification.Create (model, "2011"); 

    session.Persist (company); 
    tx.Commit(); 
} 

Este enfoque tiene toda la lógica de validación/creación dentro de las entidades correspondientes y no sé si es bueno o malo, sino por simple creación del objeto con el método de fábrica implícitamente añadiendo a la colección de objetos de los padres los niños . Después de la confirmación de la transacción y la descarga de la sesión, habrá 3 inserciones en la base de datos aunque nunca haya escrito algún comando "agregar" en mi código. No sé si tal vez solo somos yo y mi vasta experiencia fuera del mundo de DDD, pero por el momento se siente un poco antinatural.

Entonces, ¿cuál es la forma más correcta de agregar entidades secundarias con DDD?

+0

Podría eliminar todos los ejemplos de código y hacer toda esta pregunta mucho más difícil de entender, pero no creo que ayude a la comunidad. No sabía que SO no es para preguntas complejas. –

+0

Toda la pregunta (con o sin código) es inapropiada aquí. Como dije, pide una discusión. Lee los enlaces que publiqué. Puedo proporcionarle otro: [No es un foro de discusión ni foro] (http://meta.stackexchange.com/a/128550/172661). Como dije antes, este sitio es para ** preguntas claras y concisas que pueden responderse sin discusión **. Debido a que toda su pregunta se trata de discutir "la forma más correcta" y de "escuchar las opiniones [sp]" (que tampoco es apropiada aquí), no encaja aquí. –

+3

Estoy haciendo una pregunta bastante concreta y esperando una respuesta muy particular. Con mis ejemplos, simplemente muestro los esfuerzos de investigación que pongo en este asunto. No necesito ninguna discusión. Necesito una respuesta clara sobre cómo hacer esto bien. El hecho de que no sepa la respuesta a esta pregunta no significa que tenga naturaleza de discusión. El mundo de DDD usa muchos patrones y estoy pidiendo que me señale uno que deba aplicar para resolver este problema en particular. –

Respuesta

1

Tengo respuesta aceptable aquí: https://groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/23187

Básicamente, es una combinación de método 2 y 3 - poner método AddModel en el CarsCompany y que sea llamada al constructor interior, protegido de la CarsModel con el parámetro nombre que se validó dentro del constructor de CarsModel.

+0

Interesante, pero, ¿dónde se agregan objetos a colecciones primarias y secundarias? –

+0

En el método padre .AddChild, que crea el elemento secundario por el constructor interno protegido (pasando self como parámetro para que el elemento secundario sea su elemento primario en el constructor), lo agrega a la colección secundaria y lo devuelve. –

+0

Gracias, eso tiene sentido. ¿Has usado este patrón junto con Entity Framework? –

0

Aquí hay una respuesta muy concreta y honesta: todos sus enfoques son incorrectos, porque rompió la 'primera regla' de DDD, es decir, la base de datos no existe.

Lo que está definiendo es el modelo PERSISTENCE para un ORM (nhibernate). Para diseñar los objetos del dominio, primero debe identificar el Bounded Context, su Modelo, las Entidades y los objetos de Valor para ese modelo y el Aggregate Root (que se ocupará internamente de las reglas secundarias y busines).

El esquema de nhibernate o db no tiene cabida aquí, solo necesita el código de C# puro y una comprensión clara del dominio.

+4

Sí, eso es todo bien y suena como cualquier libro de DDD pero el problema aquí no es sobre el diseño del dominio. El problema en implementarlo. En desarrollo, tenemos patrones muy específicos para resolver un problema muy específico, un problema de forjar el dominio en el código de trabajo. Y estoy pidiendo que me señale cuál es la forma correcta de implementarlo. Podría escribirlo con lenguaje ficticio puro y podría funcionar en ese mundo ficticio (no tengo ningún problema con eso). –

+2

Lo difícil es diseñar el dominio. De hecho, establecer el BC y los Aggreagates son las cosas más truculentas. Una vez que los conoces (en código pseduo) es solo una formalidad escribirlos en C#. Esto no es teoría, la parte más difícil de DDD es ENTENDER el DOMINIO y tener una distinción muy clara de cuál es cuál. Además, no hay una bala de plata o un patrón para aplicar. DDD se trata de conceptos y pautas. Desea implementar algunas relaciones en Nhibernate. Eso NO es DDD. – MikeSW

+1

Necesita el EXPERTO DE DOMINIO para ayudarlo a comprender el problema y las sutilezas. Ni yo ni otras personas de por aquí somos expertos en el dominio. Debe hablar con el experto para comprender claramente cuál es el problema que debe resolver. Y honestamente, sin conocer claramente el BC, no puede diseñar o implementar una Entidad o un Agregado. – MikeSW

0

Entonces, ¿cuál es la forma más correcta de agregar entidades secundarias con DDD?

El tercer enfoque se llama Tight Coupling. Company, Car y Modification conocen casi todo el uno del otro.

El segundo enfoque es ampliamente propuesto en DDD. Un objeto de dominio es responsable de crear un objeto de dominio anidado Y de registrarlo dentro.

El primer enfoque es el clásico estilo OOP. La creación de un objeto está separada de agregar un objeto a una colección. De esta forma, el consumidor del código puede sustituir un objeto de una clase concreta (por ejemplo, Auto) por un objeto de cualquier clase derivada (por ejemplo, TrailerCar).

// var model = CarsModel.Create (company, "Tiana"); 

var model = TrailerCarsModel.Create (
    company, "Tiana", SimpleTrailer.Create(company)); 

company.AddModel (model); 

Intente adoptar este cambio de lógica de negocios en el 2º/3er enfoque.

0

Interesante. DDD vs repositorio/propiedades de navegación ORM. Creo que la respuesta depende de si se trata de un agregado o dos. ¿Debería CarsModel ser parte del agregado de CarsCompany, o quizás su propio agregado?

El primer paso es hacer desaparecer el problema. MikeSW insinuó esto. Si CarsCompany y CarsModel no necesitan formar parte del mismo agregado, solo deben referirse entre ellos por identidad, las propiedades de navegación no deberían estar visibles en el Dominio.

El segundo enfoque consiste en tratar de agregar una relación de la misma manera que tratamos de obtener un agregado: haga que Application Services llame a un método del repositorio, que es el lugar correcto para abordar sus inquietudes específicas de ORM. Tal método podría poblar ambos extremos de la relación.

Cuestiones relacionadas