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?
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. –
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í. –
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. –