Вопрос по parent-child, nhibernate, domain-driven-design, c# – Правильный способ создания дочерних объектов с DDD

9

Я довольно новичок в мире 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?

Я задаю довольно конкретный вопрос и жду очень конкретного ответа на него. На своих примерах я просто показываю усилия, которые я приложил к этому вопросу. Мне не нужно никаких обсуждений - мне нужен четкий ответ, как сделать это правильно. Тот факт, что вы не знаете ответа на этот вопрос, не означает, что он имеет дискуссионный характер. В мире DDD используется много шаблонов, и я прошу указать мне один, который мне следует применить для решения этой конкретной проблемы. Michael Logutov
Я мог бы удалить все примеры кода и сделать весь этот вопрос намного сложнее для понимания - но я не думаю, что это поможет сообществу. Я не знал, что SO не для сложных вопросов. Michael Logutov
Весь вопрос (с кодом или без него) здесь неуместен. Как я уже сказал, он требует обсуждения. Прочитайте ссылки, которые я разместил. Я могу предоставить еще один:Not a discussion board or forum, Как я уже говорил, этот сайт предназначен дляclear, concise questions that can be answered without discussion, Поскольку весь ваш вопрос касается обсуждения «наиболее правильного способа» и просят «услышать мнения [sp]»; (что также здесь неуместно), здесь не совсем подходит. Ken White

Ваш Ответ

4   ответа
0

Интересно. Свойства навигации DDD и репозитория / ORM. Я думаю, что ответ зависит от того, имеете ли вы дело с одним или двумя агрегатами. Должен ли CarsModel быть частью совокупности CarsCompany или, возможно, ее собственной совокупностью?

Первый подход - устранить проблему. MikeSW намекнул на это. Если CarsCompany и CarsModel не должны быть частью одного и того же агрегата, тогда они должны ссылаться друг на друга только по идентичности, свойства навигации не должны быть видны в Домене.

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

0

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

Третий подход называетсяТесная связь. Company, Car а такжеModification знать почти все друг о друге.

Второй подход широко предлагается в DDD. Доменный объект отвечает за создание вложенного доменного объекта и его регистрацию внутри.

Первый подход - классический стиль ООП. Создание объекта отделено от добавления объекта в какую-либо коллекцию. Таким образом, потребитель кода может заменить объект конкретного класса (например, Car) на объект любого производного класса (например, TrailerCar).

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

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

company.AddModel (model);

Попробуйте применить это изменение бизнес-логики во втором / третьем подходе.

1

Я получил приемлемый ответ здесь:https://groups.yahoo.com/neo/groups/domaindrivendesign/conversations/messages/23187

По существу, это комбинация методов 2 и 3 - поместите метод AddModel в CarsCompany и заставьте его вызывать защищенный внутренний конструктор CarsModel с параметром name, который проверяется внутри конструктора CarsModel.

Интересно, а куда добавлять объекты в родительские и дочерние коллекции?
Спасибо, это имеет смысл. Вы использовали этот шаблон в сочетании с Entity Framework?
В родительском методе .AddChild, который создает дочерний объект защищенным внутренним конструктором (передавая self в качестве параметра для дочернего элемента для установки его родителя в конструкторе), добавляет его в дочернюю коллекцию и возвращает его. Michael Logutov
Описание ошибки: не удалось обработать этот & quot; GET & quot; запрос. Это StackOverflow 101, почему вы должны включить ответ, а не ссылки на другие сайты.
На самом деле я отошел от DDD, потому что понял, что масштабирование и характер CRUD типичных веб-приложений не гарантируют накладных расходов на использование чистого DDD и забывают о том, что вы работаете с реальной БД (которая медленная, а все текущие ORM не столь эффективны). , Michael Logutov
-1

Вот очень конкретный и невероятно честный ответ: все ваши подходы неверны, потому что вы нарушили «первое правило». DDD, то есть БД не существует.

Что вы определяете, так это модель PERSISTENCE для ORM (nhibernate). Для того, чтобы спроектировать доменные объекты, сначала вы должны определитьОграниченный контекстего модель, объекты сущностей и значений для этой модели иСовокупный корень (который будет иметь дело с внутренними правилами детей и бизнеса).

Nhibernate или схема БД здесь не место, вам нужен только чистый код C # и четкое понимание предметной области.

Да, это все мило и звучит так же, как любая книга DDD, но проблема здесь не в разработке домена. Проблема в его реализации. В процессе разработки у нас есть очень специфические шаблоны для решения очень специфической проблемы - проблемы встраивания домена в рабочий код. И я прошу указать мне, как правильно это реализовать. Я мог бы написать это на чисто вымышленном языке, и он мог бы работать в этом вымышленном мире (у меня нет проблем с этим). Michael Logutov
Сложная вещь - это проектирование домена. На самом деле установление Британской Колумбии и Агреагатов - самые сложные вещи. Когда вы знаете их (в псевдо-коде), это просто формальность записать их на C #. Это не теория, самая сложная часть DDD - ПОНИМАНИЕ ДОМЕНА и очень четкое различие между которыми и есть. Также нет никакой серебряной пули или какого-либо другого шаблона для применения. DDD о концепциях и руководящих принципах. Вы хотите реализовать некоторые отношения в Nhibernate. Это НЕ DDD.
Вам нужен ДОМЕН ЭКСПЕРТ, чтобы помочь вам понять проблему и тонкости. Ни я, ни другие люди здесь не являются экспертами в области. Вы должны поговорить с экспертом, чтобы четко понять, что за проблема решить. И, честно говоря, не зная четко BC, вы не можете спроектировать или внедрить сущность или совокупность.
MikeSW, почему вы не тратите время на то, чтобы привести пример, вместо того, чтобы просто говорить. Я поделился с FuriCuri сомнениями в этом вопросе, и все в порядке с DDD, но когда вам приходится вкладывать руки в код, все меняется. И самое важное, очень важно то, как мы ОПИСЫВАЕМ ДОМЕН, чтобы позже проектировать DAO и репозитории. Это то, что спрашивает Фурикури, как правильно создавать POJO для отношений между родителями и детьми.
Хорошо, возможно, вы правы, что этот вопрос касается не только DDD. Но он все еще в значительной степени общий - вы можете поместить практически любой домен с отношениями родитель-потомок, и проблема реализации, которую я описал в моем вопросе, будет по-прежнему актуальна. Я думаю, что реальное значение предметной области здесь не имеет значения, поскольку это просто проблема несоответствия импеданса на уровне реализации, поскольку она все еще связана с DDD, поскольку это DDD, который диктует правила итерации объектов здесь. Michael Logutov

Похожие вопросы