Как должно быть выражено добавление совокупного корня к репозиторию?

Допустим, у нас есть агрегированная корневая сущность типа Order, которая связывает клиентов и строки заказа. Когда я думаю о сущности порядка, более естественно представить ее как не определенную без идентификатора. Заказ без идентификатора, кажется, лучше представить как запрос заказа, чем заказ.

Чтобы добавить заказ в репозиторий, я обычно вижу, как люди создают экземпляр заказа без идентификатора, а затем репозиторий завершает объект:

class OrderRepository
{
    void Add(Order order)
    {
        // Insert order into db and populate Id of new order
    }
}

Что мне нравится в этом подходе, так это то, что вы добавляете экземпляр Order в OrderRepository. В этом есть большой смысл. Однако у экземпляра заказа нет идентификатора, и с точки зрения потребителя репозитория для меня все еще не имеет смысла, что у заказа нет идентификатора. Я мог бы определить OrderRequest как экземпляр порядка и добавить его в репозиторий, но это похоже на получение яблока из апельсина и последующее добавление его в список апельсинов.

В качестве альтернативы я также видел такой подход:

class OrderRepository
{
    Order AddOrder(Customer customer)
        // It might be better to call this CreateOrder
    {
        // Insert record into db and return a new instance of Order
    }
}

Что мне нравится в этом подходе, так это то, что заказ не определен без идентификатора. Репозиторий может создать запись в базе данных и собрать все необходимые поля перед созданием и возвратом экземпляра заказа. Здесь неприятно то, что вы никогда не добавляете экземпляр заказа в репозиторий.

Любой способ работает, поэтому мой вопрос: должен ли я жить с одной из этих двух интерпретаций, или есть лучшая практика для моделирования вставки?

Я нашел похожий ответ, но для объектов значений: как мне добавить объект в коллекцию, поддерживаемую совокупным корнем. Когда дело доходит до объекта значения, нет никакой путаницы, но мой вопрос касается объекта с идентификатором, полученным из внешнего источника (автоматически созданный идентификатор базы данных).


person Ed I    schedule 19.01.2010    source источник
comment
Я утверждаю, что Орден без идентификатора вообще не является сущностью Ордена, и именно отсюда ваша первая первоначальная мысль. Чтобы бороться с этим, вы должны помнить состояние ваших объектов. Смотрите на «состояние» как на паузу во времени. То есть запрос веб-страницы. Во время создания заказа, да, сначала у него нет идентификатора, но вы еще не закончили с его состоянием - вы добавляете () его в репозиторий, тем самым завершая постоянное состояние Order (), которое сейчас полный.   -  person eduncan911    schedule 20.01.2010
comment
Я пока с вами не возражаю, однако, если вы утверждаете, что заказ без и Id является сущностью на короткую временную паузу, во время этой паузы он не будет иметь различимой идентичности. Я думаю, по крайней мере семантически, что это противоречит определению сущности.   -  person Ed I    schedule 20.01.2010
comment
Идентичность не так проста, как вы думаете. На мой взгляд, наши личности - это наши уникальные души, присутствующие с момента нашего зачатия. Но для правительства наша личность может быть установлена ​​более чем одним способом: свидетельством о рождении, паспортом и так далее. Есть момент, когда ребенок не имеет официальной идентичности для правительства, но правительство по-прежнему рассматривает его как сущность, хотя и нуждающуюся в идентификации. Думайте о заказе как о драгоценном маленьком ребенке, а о хранилище как о правительстве (в любом случае ints более удобны!) :)   -  person tuespetre    schedule 23.05.2014


Ответы (1)


Я хотел бы начать с исключения второго подхода. Это не только кажется нелогичным, но и нарушает несколько хороших принципов проектирования, таких как Разделение команд и запросов и принцип наименьшего удивления.

Остальные параметры зависят от логики домена. Если логика домена диктует, что Заказ без идентификатора бессмысленен, идентификатор является обязательным инвариантом Порядка, и мы должны смоделировать его так:

public class Order
{
    private readonly int id;

    public Order(int id)
    {
        // consider a Guard Clause here if you have constraints on the ID
        this.id = id;
    }
}

Обратите внимание, что, пометив поле id как readonly, мы сделали его инвариантом. Мы не можем изменить его для данного экземпляра Order. Это идеально сочетается с < strong> Entity шаблон.

Вы можете дополнительно усилить логику домена, поместив в конструктор пункт Guard, чтобы ID не был отрицательным или нулевым.

К настоящему времени вы, вероятно, задаетесь вопросом, как это будет работать с автоматически сгенерированными идентификаторами из базы данных. Что ж, это не так.

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

Это оставляет вам два варианта:

  • Измените идентификатор на Guid. Это позволяет любому вызывающему абоненту указать уникальный идентификатор для нового Заказа. Однако для этого необходимо также использовать Guids в качестве ключей базы данных.
  • Измените API, чтобы при создании нового заказа использовался не объект Order, а OrderRequest, как вы предложили - OrderRequest может быть почти идентичен классу Order за вычетом идентификатора.

Во многих случаях создание нового заказа - это бизнес-операция, которая в любом случае требует специального моделирования, поэтому я не вижу проблем в проведении этого различия. Хотя Order и OrderRequest могут быть семантически очень похожими, они даже не должны быть связаны в иерархии типов.

Я могу даже пойти дальше и сказать, что они не должны быть связаны, потому что OrderRequest - это объект значения, тогда как Order - это объект.

Если используется этот подход, метод AddOrder должен возвращать экземпляр Order (или, по крайней мере, идентификатор), потому что в противном случае мы не сможем узнать идентификатор только что созданного заказа. Это возвращает нас к нарушению CQS, поэтому я предпочитаю Guids for Entity IDs.

person Mark Seemann    schedule 19.01.2010
comment
Отличный ответ! Прежде чем задать вопрос, я склонялся к изменению API для принятия OrderRequest, и я согласен с вами, что это должен быть объект значения, не включенный в иерархию Order. Однако теперь мне очень нравится ваше предложение Guid. Я мог бы определить конструктор Order, который знает, как создать уникальный экземпляр, а затем добавить его в репозиторий. Звучит идеально. Однако мне не по себе, потому что это решение выходит за рамки обычной практики. Сталкивались ли вы со многими проектами, в которых вам хотелось бы использовать это решение, но они не были допущены? - person Ed I; 20.01.2010
comment
Вы можете столкнуться с людьми, которые думают, что это плохая идея по нескольким причинам. Некоторые из них в основном связаны с подходами Cargo Cult к архитектуре приложений, но сопротивление все же может быть реальным. В общем, есть (небольшие) накладные расходы на перфоманс при использовании Guids в качестве ключей вместо целых чисел, но пока ничего, что меня не останавливало. Самый важный аргумент против Guids - использование идентификатора вне приложения, потому что его действительно трудно читать вслух по телефону (как один пример). Использование направляющих решает некоторые проблемы, но не все. Я предпочитаю Guids, но открыт и для других альтернатив :) - person Mark Seemann; 20.01.2010
comment
Если разделить Order и OrderRequest, это станет адом для обслуживания, не так ли? Я имею в виду, поскольку они полностью отражают друг друга, за исключением поля Id, им придется все время совместно развиваться. Или я что-то упускаю? - person vorou; 01.03.2013
comment
Эта иллюстрация не столько о сопровождении кода, сколько об исследовании приемлемого способа предоставить бизнес-уровню средство для распознавания идентичности, а не перекладывать деньги на плечи. - person Ed I; 15.10.2013