Forma correcta de crear entidades hijas con DDD

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

Vengo demi propia respuesta en la pregunta de similair aquí en stackoverflow. No estoy completamente satisfecho con mi propia visión sobre este problema, por lo que pensé que debía explicar este asunto.

Por ejemplo, necesito crear un modelo simple que represente los nombres de los automóviles: compañía, modelo y modificación (por ejemplo, Nissan Teana 2012, que será la compañía "Nissan", el modelo "Teana" y la modificación "2012").

El boceto del modelo que quiero crear se ve así:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

Entonces, ahora necesito crear código. Usaré C # como lenguaje 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 típica de objetos a través de 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 lo agrega a la colección de modelos padre:

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 ();
}

Una vez que se haya confirmado la transacción y se haya vaciado la sesión, el ORM escribirá todo correctamente en la base de datos y la próxima vez que carguemos esa compañía, su colección de modelos mantendrá correctamente nuestro modelo. Lo mismo pasa con la modificación. Por lo tanto, este enfoque deja a nuestra entidad matriz en un estado inconsistente hasta que se recarga de la base de datos. No vayas.

El segundo enfoque.

Esta vez usaremos la opción específica del idioma para resolver el problema de configurar propiedades protegidas de otras clases, es decir, usaremos el modificador "protegido interno" tanto en los instaladores como en el 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 a la entidad principal y secundaria en un estado coherente. Pero la validación del estado de la entidad secundaria filtrada en la entidad principal (AddModel yAddModification métodos). Como no soy experto en DDD, no estoy seguro de si está bien o no. Podría crear más problemas en el futuro cuando las propiedades de las entidades secundarias no pudieran establecerse simplemente a través de las propiedades y configurar un estado basado en parámetros pasados ​​requeriría un trabajo más complejo que asignar un valor de parámetro a una propiedad. Tenía la impresión de que deberíamos concentrar la lógica sobre la entidad dentro de esa entidad siempre que sea posible. Para mí, este enfoque convierte el objeto principal en algún tipo de híbrido de Entidad y Fábrica.

El tercer enfoque.

Ok, 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, pero mediante la simple creación del objeto con el método de fábrica, lo agregamos implícitamente a la colección de elementos secundarios del objeto principal. 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 escribí un comando "agregar" en mi código. No sé, quizás sea solo yo y mi vasta experiencia fuera del mundo 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?

Respuestas a la pregunta(4)

Su respuesta a la pregunta