Правильный способ создания дочерних объектов с DDD

Я довольно новичок в мире DDD, и после прочтения нескольких книг об этом (в том числе Эванса DDD) я не смог найти ответ на свой вопрос в Интернете: каков правильный способ создания дочерних сущностей с DDD? Видите ли, много информации в Интернете работает на каком-то простом уровне. Но дьяволы в деталях, и они всегда опущены в десятках образцов DDD для простоты.

Я прибываю измой собственный ответ на similair вопрос здесь на stackoverflow. Я не полностью удовлетворен своим собственным видением этой проблемы, поэтому я подумал, что мне нужно уточнить этот вопрос.

Например, мне нужно создать простую модель, которая представляет названия автомобилей: компания, модель и модификация (например, Nissan Teana 2012 - это будет «Nissan» компания, «модель Teana» и «2012» модификация).

Эскиз модели, которую я хочу создать, выглядит следующим образом:

CarsCompany
{
    Name
    (child entities) Models
}

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


CarsModification
{
    (parent entity) Model
    Name
}

Итак, теперь мне нужно создать код. Я буду использовать C # в качестве языка и NHibernate в качестве ORM. Это важно и то, что обычно не показывается в обширных образцах DDD в Интернете.

The first approach.

Я начну с простого подхода с типичного создания объекта с помощью заводских методов.

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

Недостаток этого подхода в том, что создание модели не добавляет его в коллекцию родительских моделей:

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

После того, как транзакция зафиксирована и сеанс сброшен, ORM правильно запишет все в базу данных, и в следующий раз, когда мы загрузим эту компанию, ее коллекция моделей будет правильно содержать нашу модель. То же самое касается модификации. Таким образом, этот подход оставляет нашу родительскую сущность в несовместимом состоянии до тех пор, пока она не будет перезагружена из базы данных. Нет идти

The second approach.

На этот раз мы будем использовать специфическую для языка опцию, чтобы решить проблему установки защищенных свойств других классов, а именно мы будем использовать «защищенный внутренний». модификатор как на сеттеры, так и на конструктор.

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

На этот раз каждое создание объекта оставляет и родительский и дочерний объект в согласованном состоянии. Но проверка состояния дочерней сущности просочилась в родительскую сущность (AddModel а такжеAddModification методы). Так как я нигде не эксперт в DDD, я не уверен, все ли в порядке или нет. Это может создать больше проблем в будущем, когда свойства дочерних объектов не могут быть просто установлены через свойства, а для настройки некоторого состояния на основе переданных параметров потребуется более сложная работа, чем присвоение значения параметра свойству. У меня сложилось впечатление, что мы должны сосредоточить логику о сущности внутри этой сущности, где это возможно. Для меня этот подход превращает родительский объект в некий гибрид Entity & Factory.

The third approach.

Хорошо, мы перевернем обязанности по поддержанию отношений между родителями и детьми.

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

Этот подход получил всю логику проверки / создания внутри соответствующих объектов, и я не знаю, хорошо это или плохо, но простым созданием объекта с помощью фабричного метода мы неявно добавили его в коллекцию дочерних объектов родительского объекта. После фиксации транзакции и сброса сеанса в базу данных будет 3 вставки, даже если я никогда не писал «добавить». Команда в моем коде. Я не знаю, может быть, это только я и мой обширный опыт вне мира DDD, но сейчас это выглядит немного неестественно.

So, what's most correct way of adding child entities with DDD?

Ответы на вопрос(4)

Ваш ответ на вопрос