Maneira correta de criar entidades filhas com DDD

Eu sou bastante novo para o mundo DDD e depois de ler alguns livros sobre isso (Evans DDD entre eles) eu não consegui encontrar a resposta para a minha pergunta na internet: o que a maneira correta de criar entidades filhas com DDD? Você vê, muita informação na internet opera em algum nível simples. Mas diabos nos detalhes e eles são sempre omitidos em dezenas de amostras DDD por uma questão de simplicidade.

Eu estou vindominha própria resposta na pergunta similair aqui no stackoverflow. Eu não estou completamente satisfeito com minha própria visão sobre este problema, então eu pensei que precisava elaborar sobre este assunto.

Por exemplo, eu preciso criar um modelo simples que represente a nomeação de carros: empresa, modelo e modificação (por exemplo, Nissan Teana 2012 - que será a empresa "Nissan", modelo "Teana" e modificação "2012").

O esboço do modelo que quero criar é assim:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

Então, agora preciso criar código. Eu vou usar o C # como linguagem e NHibernate como ORM. Isso é importante e o que normalmente não é mostrado em vastas amostras de DDD na internet.

A primeira abordagem.

Começarei com uma abordagem simples com a criação típica de objetos por meio 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
        };
    }
}

O ruim dessa abordagem é que a criação do modelo não a adiciona à coleção de modelos pai:

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

Depois que a transação é confirmada e a sessão é liberada, o ORM gravará tudo corretamente no banco de dados e, da próxima vez que carregarmos essa empresa, a coleção de modelos armazenará corretamente o nosso modelo. O mesmo acontece com a modificação. Portanto, essa abordagem deixa nossa entidade pai em estado inconsistente até que ela seja recarregada a partir do banco de dados. Não vá.

A segunda abordagem.

Desta vez vamos usar a opção específica de idioma para resolver o problema de configurar propriedades protegidas de outras classes - ou seja, vamos usar o modificador "protected internal" em ambos os setters e 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 ();
}

Desta vez, a criação de cada entidade deixa a entidade pai e filho em um estado consistente. Mas a validação do estado da entidade filho vazou para a entidade pai (AddModel eAddModification métodos). Desde que eu sou especialista em nenhum lugar DDD eu não tenho certeza se está tudo bem ou não. Poderia criar mais problemas no futuro, quando propriedades de entidades filhas não pudessem ser simplesmente definidas por meio de propriedades e a configuração de algum estado com base em parâmetros passados ​​exigiria um trabalho mais complexo que atribuísse valor de parâmetro à propriedade. Fiquei com a impressão de que devemos concentrar a lógica sobre a entidade dentro dessa entidade sempre que possível. Para mim, essa abordagem torna o objeto pai em algum tipo de híbrido Entity & Factory.

A terceira abordagem.

Ok, vamos inverter as responsabilidades de manter relações pai-filho.

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

Essa abordagem tem toda a lógica de validação / criação dentro das entidades correspondentes e eu não sei se é bom ou ruim, mas pela simples criação do objeto com o método factory nós implicitamente o adicionamos à coleção de filhos do objeto pai. Após a transação commit e session flush, haverá 3 inserções no banco de dados, mesmo que eu nunca tenha escrito algum comando "add" no meu código. Eu não sei, talvez seja só eu e minha vasta experiência fora do mundo DDD, mas parece um pouco antinatural por enquanto.

Então, qual é a maneira mais correta de adicionar entidades filhas com DDD?

questionAnswers(4)

yourAnswerToTheQuestion