DDD - правило, что объекты не могут напрямую обращаться к репозиториям

В Domain Driven Design, похоже, lots соглашение, что Сущности не должны напрямую обращаться к репозиториям.

Это произошло от книги Эрика Эванса Domain Driven Design, или она исходила из других источников?

Где есть хорошие объяснения причин этого?

edit: Чтобы уточнить: я не говорю о классической практике OO отделяя доступ к данным от отдельного уровня от бизнес-логики - я говорю о конкретной договоренности, в которой в DDD сущности не должны говорить к уровню доступа к данным вообще (т.е. они не должны содержать ссылки на объекты репозитория)

update: я дал Bountce BacceSR, потому что его ответ казался самым близким, но я все еще в темноте об этом. Если это такой важный принцип, должны быть какие-то хорошие статьи об этом в Интернете, конечно?

update: March 2013, upvotes на вопрос подразумевают там большой интерес в этом, и, несмотря на то, что было много ответов, я все еще думаю, что есть место для большего, если у людей есть идеи об этом.

Ответ 1

Здесь немного путаницы. Репозитории доступа к совокупным корням. Совокупные корни - это сущности. Причиной этого является разделение проблем и хорошее расслоение. Это маловероятно для небольших проектов, но если вы работаете в большой команде, вы хотите сказать: "Вы получаете доступ к продукту через репозиторий продуктов. Продукт представляет собой совокупный корень для коллекции объектов, включая объект ProductCatalog. Если вы хотите обновить ProductCatalog, вы должны пройти ProductRepository."

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

Но подождите! Репозиторий также ссылается на уровень персистентности, как в шаблоне репозитория. В лучшем мире репозиторий Эрика Эванса и шаблон хранилища будут иметь отдельные имена, потому что они имеют тенденцию пересекаться совсем немного. Чтобы получить шаблон репозитория, вы контрастируете с другими способами доступа к данным, с служебной шиной или системой модели событий. Обычно, когда вы достигаете этого уровня, определение репозитория Эрика Эванса идет по пути, и вы начинаете говорить о ограниченном контексте. Каждый ограниченный контекст, по сути, является его собственным приложением. У вас может быть сложная система одобрения для получения информации в каталоге продуктов. В вашем оригинальном дизайне продукт был центральным элементом, но в этом ограниченном контексте каталог продукции. Вы по-прежнему можете получать информацию о продукте и обновлять продукт через служебную шину, но вы должны понимать, что каталог продуктов за пределами ограниченного контекста может означать нечто совершенно иное.

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

Ответ на комментарий 1: Правильно, хороший вопрос. Поэтому не все проверки выполняются в доменном слое. У Sharp есть атрибут "DomainSignature", который делает то, что вы хотите. Это постоянство, но атрибут сохраняет уровень домена чистым. Это гарантирует, что у вас нет дублированного объекта, в вашем примере с тем же именем.

Но расскажем о более сложных правилах проверки. Скажем, вы Amazon.com. Вы когда-нибудь заказывали что-то с истекшей кредитной картой? У меня есть, где я не обновил карту и что-то купил. Он принимает заказ, и пользовательский интерфейс сообщает мне, что все персиковое. Примерно через 15 минут я получу электронное письмо с сообщением о проблеме с моим заказом, моя кредитная карта недействительна. Что здесь происходит, в идеале, существует некоторая проверка регулярного выражения в доменном слое. Это правильный номер кредитной карты? Если да, сохраните заказ. Однако есть дополнительная проверка на уровне задач приложений, где запрашивается внешняя служба, чтобы узнать, можно ли произвести платеж на кредитной карте. Если нет, не отправляйте ничего, приостанавливайте заказ и ждите клиента. Все это должно происходить на сервисном уровне.

Не бойтесь создавать объекты проверки на уровне службы, которые могут обращаться к репозиториям. Просто держите его вне домена.

Ответ 2

Сначала я был убежден, что некоторые из моих сущностей могут получить доступ к репозиториям (т.е. отложенная загрузка без ORM). Позже я пришел к выводу, что не следует и что я могу найти альтернативные способы:

  1. Мы должны знать наши намерения в запросе и то, что мы хотим от домена, поэтому мы можем делать вызовы репозитория перед построением или вызовом Агрегированного поведения. Это также помогает избежать проблемы несовместимого состояния в памяти и необходимости отложенной загрузки (см. Эту статью). Запах в том, что вы больше не можете создать экземпляр вашей сущности в памяти, не беспокоясь о доступе к данным.
  2. CQS (разделение командных запросов) может помочь уменьшить необходимость вызова хранилища для объектов в наших объектах.
  3. Мы можем использовать спецификацию для инкапсуляции и передачи потребностей логики домена и передать ее в хранилище (сервис может организовать эти вещи для нас). Спецификация может исходить от объекта, который отвечает за поддержание этого инварианта. Репозиторий будет интерпретировать части спецификации в свою собственную реализацию запроса и применять правила из спецификации к результатам запроса. Это стремится сохранить доменную логику на уровне домена. Это также служит вездесущему языку и общению лучше. Представьте себе, что нужно сказать "просроченная спецификация заказа" вместо "фильтровать заказ из tbl_order, где place_at меньше, чем за 30 минут до sysdate" (см. Этот ответ).
  4. Это усложняет рассуждения о поведении объектов, поскольку принцип единственной ответственности нарушается. Если вам нужно решить проблемы с хранением/постоянством, вы знаете, куда идти и куда не идти.
  5. Это позволяет избежать опасности предоставления объекту двунаправленного доступа к глобальному состоянию (через хранилище и доменные службы). Вы также не хотите нарушать границы транзакции.

Вернон Вон в Красной книге "Реализация доменного дизайна" относится к этой проблеме в двух местах, о которых я знаю (примечание: эта книга полностью одобрена Эвансом, как вы можете прочитать в предисловии). В главе 7 "Службы" он использует доменную службу и спецификацию, чтобы обойти необходимость в агрегате для использования репозитория и другого агрегата для определения того, прошел ли пользователь аутентификацию. Он цитирует слова:

Как правило, мы должны стараться избегать использования репозиториев (12) из агрегатов, если это вообще возможно.

Вернон, Вон (2013-02-06). Реализация доменно-управляемого дизайна (Kindle Location 6089). Пирсон Образование. Kindle Edition.

А в главе 10 "Агрегаты" в разделе "Навигация по моделям" он говорит (сразу после того, как он рекомендует использовать глобальные уникальные идентификаторы для ссылки на другие корни агрегатов):

Ссылка по идентичности не полностью предотвращает навигацию по модели. Некоторые будут использовать репозиторий (12) из Агрегата для поиска. Эта техника называется моделью отсоединенного домена, и на самом деле она является формой отложенной загрузки. Однако существует другой рекомендуемый подход: используйте службу репозитория или службы домена (7) для поиска зависимых объектов, прежде чем вызывать поведение агрегирования. Клиентская служба приложений может контролировать это, а затем отправлять в агрегат:

Он идет, чтобы показать пример этого в коде:

public class ProductBacklogItemService ... { 

   ... 
   @Transactional 
   public void assignTeamMemberToTask( 
        String aTenantId, 
        String aBacklogItemId, 
        String aTaskId, 
        String aTeamMemberId) { 

        BacklogItem backlogItem = backlogItemRepository.backlogItemOfId( 
                                        new TenantId( aTenantId), 
                                        new BacklogItemId( aBacklogItemId)); 

        Team ofTeam = teamRepository.teamOfId( 
                                  backlogItem.tenantId(), 
                                  backlogItem.teamId());

        backlogItem.assignTeamMemberToTask( 
                  new TeamMemberId( aTeamMemberId), 
                  ofTeam,
                  new TaskId( aTaskId));
   } 
   ...
}     

Далее он также упомянул еще одно решение о том, как можно использовать доменную службу в методе Aggregate command вместе с двойной диспетчеризацией. (Я не могу порекомендовать, насколько полезно читать его книгу. После того, как вы устали от бесконечного рытья в Интернете, раскошелитесь на заслуженные деньги и прочитайте книгу.)

То я имел некоторое обсуждение с всегда милостивым Марко Pivetta @Ocramius, который показал мне немного коды на вытаскивая спецификацию из домена и с помощью этого:

1) Это не рекомендуется:

$user->mountFriends(); // <-- has a repository call inside that loads friends? 

2) В доменном сервисе это хорошо:

public function mountYourFriends(MountFriendsCommand $mount) { /* see http://store.steampowered.com/app/296470/ */ 
    $user = $this->users->get($mount->userId()); 
    $friends = $this->users->findBySpecification($user->getFriendsSpecification()); 
    array_map([$user, 'mount'], $friends); 
}

Ответ 3

Это очень хороший вопрос. Я буду с нетерпением ждать обсуждения этого вопроса. Но я думаю, что он упоминал в несколько книг DDD и Jimmy nilssons и Eric Evans. Я думаю, это также видно на примерах, как использовать шаблон репозитория.

НО давайте обсудим. Я думаю, что очень важная мысль заключается в том, почему организация должна знать, как сохранить другую сущность? Важно с DDD, что каждый субъект несет ответственность за управление собственной "областью знаний" и не должен знать ничего о том, как читать или писать другие объекты. Конечно, вы можете просто добавить интерфейс репозитория к Entity A для чтения сущностей B. Но риск в том, что вы раскрываете знания о том, как сохранить B. Будет ли сущность A также выполнять валидацию на B, прежде чем продолжить B в db?

Как вы можете видеть, объект A может более активно участвовать в жизненном цикле объекта B и может добавить к нему сложность.

Я предполагаю (без какого-либо примера), что модульное тестирование будет более сложным.

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

Ответ 4

Зачем выделять доступ к данным?

Из книги, я думаю, что первые две страницы главы Model Driven Design дают некоторое обоснование тому, почему вы хотите абстрагировать детали технической реализации от реализации модели домена.

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

Это похоже на все, чтобы избежать отдельной "модели анализа", которая оторвалась от фактической реализации системы.

Из того, что я понимаю в книге, говорится, что эта "модель анализа" может быть разработана без учета внедрения программного обеспечения. Когда разработчики пытаются внедрить модель, понятную бизнес-стороне, они создают свои собственные абстракции из-за необходимости, вызывая стену в общении и понимании.

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

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

Цитирование:

"Единственная модель уменьшает вероятность ошибки, потому что дизайн теперь является прямым результатом тщательно продуманной модели. Дизайн и даже сам код имеют коммуникативность модели".

Как я это интерпретирую, если у вас больше строк кода, связанных с вещами, такими как доступ к базе данных, вы теряете эту коммуникативность.

Если необходимость доступа к базе данных для таких вещей, как проверка уникальности, взгляните на:

Уди Дахан: самые большие ошибки команды при применении DDD

http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/

в разделе "Все правила не созданы равными"

и

Использование шаблона модели домена

http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119

в разделе "Сценарии не использования модели домена", в котором затрагивается один и тот же вопрос.

Как разделить доступ к данным

Загрузка данных через интерфейс

"Уровень доступа к данным" был абстрагирован через интерфейс, который вы вызываете, чтобы получить требуемые данные:

var orderLines = OrderRepository.GetOrderLines(orderId);

foreach (var line in orderLines)
{
     total += line.Price;
}

Плюсы: интерфейс отделяет "сантехнический" код доступа к данным, позволяя вам писать тесты. Доступ к данным можно обрабатывать в каждом конкретном случае, обеспечивая лучшую производительность, чем общая стратегия.

Минусы: вызывающий код должен предполагать, что было загружено, а что нет.

Скажите GetOrderLines возвращает объекты OrderLine с нулевым свойством ProductInfo по соображениям производительности. Разработчик должен иметь глубокое знание кода, лежащего за интерфейсом.

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

Теперь разделение проблем должно позволить разработчику сосредоточиться на одном аспекте кода за один раз, насколько это возможно. Методика интерфейса удаляет HOW, загружаются ли эти данные, но не КАК MUCH данные загружаются, КОГДА загружается и WHERE загружается.

Заключение: Довольно слабое разделение!

Lazy Loading

Данные загружаются по требованию. Вызовы для загрузки данных скрыты внутри самого графика объекта, когда доступ к свойству может привести к выполнению SQL-запроса перед возвратом результата.

foreach (var line in order.OrderLines)
{
    total += line.Price;
}

Плюсы: "КОГДА, ГДЕ И КАК" доступа к данным скрывается от разработчика, ориентированного на логику домена. В агрегате нет кода, связанного с загрузкой данных. Количество загружаемых данных может быть точной величиной, требуемой кодом.

Минусы: когда вы попадаете в проблему с производительностью, ее трудно исправить, если у вас есть общее решение "один размер подходит всем". Ленивая загрузка может привести к ухудшению производительности в целом, и осуществление ленивой загрузки может быть сложным.

Интерфейс роли/нетерпеливое извлечение

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

Стратегия выбора может выглядеть так:

public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
    Order Load(string aggregateId)
    {
        var order = new Order();

        order.Data = GetOrderLinesWithPrice(aggregateId);

        return order;
    }

}

Тогда ваш агрегат может выглядеть так:

public class Order : IBillOrder
{
    void BillOrder(BillOrderCommand command)
    {
        foreach (var line in this.Data.OrderLines)
        {
            total += line.Price;
        }

        etc...
    }
}

BillOrderFetchingStrategy используется для создания агрегата, а затем агрегат выполняет свою работу.

Плюсы. Позволяет настраивать код для одного варианта использования, обеспечивая оптимальную производительность. Является встроенным с Принципом разделения интерфейса. Нет сложных требований к коду. Агрегаты единичных тестов не должны имитировать стратегию загрузки. Общая стратегия загрузки может использоваться в большинстве случаев (например, стратегия "загрузить все" ), и при необходимости могут быть реализованы специальные стратегии загрузки.

Минусы: разработчику по-прежнему приходится корректировать/просматривать стратегию выбора после изменения кода домена.

С помощью подхода стратегии выбора вы можете по-прежнему обнаруживать изменение пользовательского кода для изменения бизнес-правил. Это не идеальное разделение проблем, но в конечном итоге будет более ремонтопригодным и лучше, чем первый вариант. Стратегия выборки инкапсулирует данные HOW, WHEN и WHERE. Он имеет лучшее разделение проблем, не теряя гибкости, как один размер, подходит для всего ленивого подхода к загрузке.

Ответ 6

Какой отличный вопрос. Я нахожусь на том же пути открытия, и большинство ответов в Интернете, кажется, приносят столько же проблем, сколько и решений.

Так что (рискуя написать что-то, с чем я не согласен через год), вот мои открытия.

Прежде всего, нам нравится модель с богатым доменом, которая дает нам высокую степень обнаружения (того, что мы можем сделать с помощью агрегата) и удобочитаемость (экспрессивные вызовы методов).

// Entity
public class Invoice
{
    ...
    public void SetStatus(StatusCode statusCode, DateTime dateTime) { ... }
    public void CreateCreditNote(decimal amount) { ... }
    ...
}

Мы хотим достичь этого без внедрения каких-либо сервисов в конструктор сущностей, потому что:

  • Введение нового поведения (которое использует новый сервис) может привести к изменению конструктора, то есть изменение влияет на каждую строку, которая создает экземпляр сущности !
  • Эти сервисы не являются частью модели, но конструктор-инъекция предполагает, что они были.
  • Часто сервис (даже его интерфейс) является деталью реализации, а не частью домена. Модель предметной области будет иметь внешнюю зависимость.
  • Может быть непонятно, почему сущность не может существовать без этих зависимостей. (Вы говорите, служба кредитных нот? Я даже не собираюсь ничего делать с кредитными нотами...)
  • Это сделало бы трудным создание экземпляра, таким образом, трудным для тестирования.
  • Проблема распространяется легко, потому что другие сущности, содержащие эту, получат такие же зависимости, которые могут выглядеть очень неестественными зависимостями.

Как же тогда мы можем это сделать? Пока я пришел к выводу, что зависимости методов и двойная диспетчеризация обеспечивают достойное решение.

public class Invoice
{
    ...

    // Simple method injection
    public void SetStatus(IInvoiceLogger logger, StatusCode statusCode, DateTime dateTime)
    { ... }

    // Double dispatch
    public void CreateCreditNote(ICreditNoteService creditNoteService, decimal amount)
    {
        creditNoteService.CreateCreditNote(this, amount);
    }

    ...
}

CreateCreditNote() теперь требует службы, которая отвечает за создание кредитных нот. Он использует двойную диспетчеризацию, полностью разгружая работу ответственному сервису, сохраняя возможность обнаружения из сущности Invoice.

SetStatus() теперь имеет простую зависимость от регистратора, который, очевидно, будет выполнять часть работы.

Для последнего, чтобы упростить клиентский код, мы могли бы вместо этого войти через IInvoiceService. В конце концов, регистрация счетов кажется довольно неотъемлемой частью счета. Такой единый IInvoiceService помогает избежать необходимости использования всевозможных мини-сервисов для различных операций. Недостатком является то, что становится неясным, что именно будет делать этот сервис. Это может даже начать выглядеть как двойная отправка, хотя большая часть работы все еще выполняется в SetStatus().

Мы все еще можем назвать параметр "logger" в надежде раскрыть наши намерения. Кажется, немного слабым, хотя.

Вместо этого я бы предпочел запросить IInvoiceLogger (как мы уже делали в примере кода), и IInvoiceService реализовал этот интерфейс. Клиентский код может просто использовать свой единственный IInvoiceService для всех методов Invoice которые запрашивают любой такой очень специфический, встроенный в счет-фактуру "мини-сервис", в то время как сигнатуры методов по-прежнему ясно дают понять, о чем они просят.

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

public class Invoice
{
    public IEnumerable<CreditNote> GetCreditNotes(ICreditNoteRepository repository)
    { ... }
}

Фактически, это альтернатива постоянно ленивым нагрузкам.

Обновление: я оставил текст ниже для исторических целей, но я рекомендую избегать ленивых нагрузок на 100%.

Для истинных, ленивых нагрузок на основе свойств в настоящее время я использую инъекцию конструктора, но неосведомленным образом.

public class Invoice
{
    // Lazy could use an interface (for contravariance if nothing else), but I digress
    public Lazy<IEnumerable<CreditNote>> CreditNotes { get; }

    // Give me something that will provide my credit notes
    public Invoice(Func<Invoice, IEnumerable<CreditNote>> lazyCreditNotes)
    {
        this.CreditNotes = new Lazy<IEnumerable<CreditNotes>>() => lazyCreditNotes(this));
    }
}

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

С другой стороны, код, который создает фактический новый Invoice, просто передаст функцию, которая возвращает пустой список:

new Invoice(inv => new List<CreditNote>() as IEnumerable<CreditNote>)

(Пользовательский ILazy<out T> может избавить нас от уродливого ILazy<out T> в IEnumerable, но это усложнит обсуждение.)

// Or just an empty IEnumerable
new Invoice(inv => IEnumerable.Empty<CreditNote>())

Буду рад услышать ваше мнение, предпочтения и улучшения!

Ответ 7

Для меня это, как представляется, является общей хорошей практикой, связанной с OOD, а не спецификой DDD.

Причины, о которых я могу думать, следующие:

  • Разделение проблем (сущности должны быть отделены от того, как они сохраняются, поскольку могут существовать несколько стратегий, в которых один и тот же объект будет сохраняться в зависимости от сценария использования)
  • Логически, сущности можно увидеть на уровне ниже уровня, в котором работают репозитории. Компоненты нижнего уровня не должны иметь знаний о компонентах более высокого уровня. Поэтому записи не должны иметь знаний о репозиториях.

Ответ 8

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

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

Я думаю, что разделение доступа к данным (aka "Репозиторий" ) из вашей бизнес-логики является одной из тех вещей, которые были заново изобрещены несколько раз, задумались над книгой Design Driven Design, сделавшей ее "шумной".

В настоящее время я использую 3 уровня (GUI, Logic, Data Access), как это делает многие разработчики, потому что это хорошая техника.

Разделение данных на слой Repository (слой a.k.a. Data Access) можно рассматривать как хорошую технику программирования, а не только правило, следовать.

Как и многие методологии, вы можете начать, НЕ реализовав и, в конечном итоге, обновить свою программу, как только вы их поймете.

Цитата: "Илиада" не была полностью изобретена Гомером, Кармина Бурана не была полностью изобретена Карлом Орфом, и в обоих случаях человек, который заставлял других работать, все заработал, получил кредит; -)

Ответ 9

просто Вернон Вон дает решение:

Использовать репозиторий или службу домена для поиска зависимых объектов впереди вызова общего поведения. Служба клиентских приложений может контролируйте это.

Ответ 10

Это исходило из книги дизайна Eric Evans Domain Driven Design, или она исходила из других источников?

Это старый материал. Книга Эрика просто заставляла его гудеть еще немного.

Где есть хорошие объяснения причин этого?

Причина проста: человеческий ум становится слабым, когда он сталкивается с неопределенно связанными множественными контекстами. Они приводят к двусмысленности (Америка в Южной/Северной Америке означает Юг/Северная Америка), двусмысленность приводит к постоянному отображению информации всякий раз, когда разум "затрагивает ее" и подводит итог как плохую производительность и ошибки.

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

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

edit: Чтобы уточнить: я не говорю о классической практике OO отделяя доступ к данным от отдельного уровня от бизнес-логики - я говорю о конкретной договоренности, в которой в DDD сущности не должны говорить к уровню доступа к данным вообще (т.е. они не должны содержать ссылки на объекты репозитория)

Причина все тот же, о чем я говорил выше. Здесь он еще на один шаг. Почему объекты должны быть частично персидскими, если они могут быть (по крайней мере, близки) полностью? Меньше проблем, связанных с доменом, относится к нашей модели - больше дышащей комнаты, к которой приходит наш ум, когда ей приходится повторно интерпретировать ее.

Ответ 11

Причина, по которой сущность не может иметь хранилище сама по себе, заключается в том, что она называется согласованностью транзакций, и только агрегаты должны отвечать за постоянство, совокупность == согласованность транзакций.

Агрегат должен быть ВСЕГДА в согласованном состоянии, действительность агрегата определяется самим агрегатом, а не отдельными его составляющими.

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

Так что это главная причина, по которой никакая другая Доменная Модель, кроме Агрегата, не должна иметь прикрепленный к ней Репозиторий.

Ответ 12

Чтобы процитировать Каролину Лилиенталь, "шаблоны должны предотвращать циклы" https://www.youtube.com/watch?v=eJjadzMRQAk, где она ссылается на циклические зависимости между классами. В случае хранилищ внутри агрегатов существует соблазн создать циклические зависимости из-за удобства навигации по объектам в качестве единственной причины. Шаблон, упомянутый выше prograhammer, рекомендованный Верноном Воном, где другие агрегаты ссылаются на идентификаторы, а не на корневые экземпляры (есть ли название для этого шаблона?), Предлагает альтернативу, которая может привести к другим решениям.

Пример циклической зависимости между классами (признание):

(Time0): два класса, Sample и Well, ссылаются друг на друга (циклическая зависимость). Well относится к образцу, а Sample относится к лунке, из-за удобства (иногда зацикливание образцов, иногда зацикливание всех лунок в планшете). Я не мог представить случаи, когда Образец не ссылался бы на Колодец, где он находился.

(Время 1): Год спустя, многие варианты использования были реализованы.... и теперь есть случаи, когда Образец не должен ссылаться обратно на Скважину, в которую он помещен. В рабочем шаге есть временные пластины. Здесь скважина относится к образцу, который, в свою очередь, относится к скважине на другой пластине. Из-за этого странное поведение иногда возникает, когда кто-то пытается реализовать новые функции. Требуется время, чтобы проникнуть.

Мне также помогла упомянутая выше статья о негативных аспектах отложенной загрузки.

Ответ 13

Каждый объект должен иметь доступ к своему хранилищу. если объект использует другой репозиторий, то это, кажется, трансграничные границы, которые не являются допустимым подходом DDD.

Ответ 14

В идеальном мире DDD предлагает, чтобы сущности не имели ссылки на слои данных. но мы не живем в идеальном мире. Домены могут потребоваться ссылаться на другие объекты домена для бизнес-логики, с которыми у них может не быть зависимости. Логически для объектов ссылаться на уровень репозитория для цели только для чтения, чтобы получить значения.