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

185

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

Это из книги Эрика Эванса « Дизайн, управляемый доменом» , или из другого места?

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

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

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

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

codeulike
источник
Взгляните на мой вопрос stackoverflow.com/q/8269784/235715 , он показывает ситуацию, когда трудно перехватить логику без доступа Entity к репозиторию. Хотя я думаю, что сущности не должны иметь доступа к репозиториям, и в моей ситуации есть решение, когда код можно переписать без ссылки на репозиторий, но в настоящее время я не могу думать ни о каком.
Алексей Бурцев
Не знаю, откуда это. Мои мысли: я думаю, что это недоразумение исходит от людей, которые не понимают, что такое DDD. Этот подход не для реализации программного обеспечения, а для его разработки (домен .. дизайн). Когда-то у нас были архитекторы и разработчики, но теперь есть только разработчики программного обеспечения. DDD предназначен для архитекторов. И когда архитектор разрабатывает программное обеспечение, ему нужен какой-то инструмент или шаблон для представления памяти или базы данных для разработчиков, которые будут реализовывать подготовленный проект. Но сам дизайн (с точки зрения бизнеса) не имеет или не нуждается в хранилище.
берхалак

Ответы:

47

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

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

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

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

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

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

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

kertosis
источник
15
Спасибо. Но я должен стремиться донести как можно больше бизнес-логики до сущностей (и связанных с ними фабрик, спецификаций и т. Д.), Верно? Но если никому из них не разрешено получать данные через репозитории, как я должен писать какую-либо (достаточно сложную) бизнес-логику? Например: пользователю чата не разрешается менять свое имя на имя, которое уже использовалось кем-то другим. Я бы хотел, чтобы это правило было встроено в сущность ChatUser, но это не очень легко сделать, если вы не можете открыть хранилище оттуда. Так что я должен делать?
codeulike
Мой ответ был больше, чем поле для комментариев, см. Редактирование.
кертоз
6
Вы должны знать, как защитить себя от вреда. Это включает в себя проверку того, что он не может войти в недопустимое состояние. То, что вы описываете с помощью пользователя Чата, - это бизнес-логика, которая находится ДОПОЛНИТЕЛЬНО к логике, которую сущность должна поддерживать в себе. Бизнес-логика, например, то, что вам нужно, принадлежит сервису Chatroom, а не сущности ChatUser.
Alec
9
Спасибо Алек. Это четкий способ выразить это. Но мне кажется, что ориентированное на домен золотое правило Эванса «вся бизнес-логика должна идти в доменном слое» противоречит правилу «сущности не должны обращаться к репозиториям». Я могу с этим смириться, если пойму, почему это так, но я не могу найти в Интернете хорошего объяснения того, почему сущности не должны получать доступ к репозиториям. Эванс, кажется, не упоминает об этом явно. Откуда это? Если вы можете опубликовать ответ, указывающий на какую-нибудь хорошую литературу, вы можете получить награду в 50pt:)
codeulike
4
«Его не имеет смысла в малом». Это большая ошибка, которую делают команды ... это маленький проект, поэтому я могу делать то и это ... перестать так думать. Многие небольшие проекты, с которыми мы работаем, в конечном итоге становятся большими из-за требований бизнеса. Если вы делаете что-то с малым или большим, делайте это правильно.
MeTitus
35

Сначала я был убежден, чтобы некоторые из моих сущностей имели доступ к репозиториям (т.е. отложенная загрузка без 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));
   } 
   ...
}     

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

То я имел некоторое обсуждение с всегда милостивым Марко 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); 
}
prograhammer
источник
1
Вопрос: Нас всегда учат не создавать объект в недопустимом или противоречивом состоянии. Когда вы загружаете пользователей из репозитория, а затем звоните, getFriends()прежде чем делать что-либо еще, он будет пустым или отложенным. Если пусто, то этот объект лежит и находится в недопустимом состоянии. Есть мысли по этому поводу?
Джимбо
Репозиторий вызывает Домен, чтобы создать новый экземпляр. Вы не получите экземпляр пользователя, не пройдя через домен. Проблема, к которой относится этот ответ, - обратная. Где домен ссылается на репозиторий, и этого следует избегать.
программер
28

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

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

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

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

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

Магнус Бакеус
источник
1
Хорошая точка зрения. Я полагаю, что в старой модели предметной области сущность B была бы ответственна за валидацию, прежде чем она дала бы себя сохранить. Вы уверены, что Эванс упоминает организации, которые не используют репозитории? Я на полпути к книге, и она еще не упоминала ...
codeulike
Ну, я прочитал книгу несколько лет назад (хорошо 3 ...), и моя память подводит меня. Я не могу вспомнить, если он точно сформулировал это, НО, однако я считаю, что он проиллюстрировал это на примерах. Вы также можете найти толкование сообщества его примера Cargo (из его книги) на dddsamplenet.codeplex.com . Загрузите код проекта (посмотрите на проект Vanilla - это пример из книги). Вы обнаружите, что репозитории используются только на прикладном уровне для доступа к объектам домена.
Магнус Бакеус
1
Загрузив пример DDD SmartCA из книги p2p.wrox.com/… вы увидите другой подход (хотя это и есть клиент RIA windows), где репозитории используются в сервисах (ничего странного здесь), но сервисы используются внутри энтитес. Это то, что я бы не стал делать, НО я парень из веб-приложения. Учитывая сценарий для приложения SmartCA, где вы должны иметь возможность работать в автономном режиме, возможно, дизайн DDD будет выглядеть по-другому.
Магнус Бакеус
Пример SmartCA звучит интересно, в какой главе он находится? (загрузка кода организована по главам)
codeulike
1
@codeulike В настоящее время я разрабатываю и внедряю фреймворк, используя концепции DDD. Иногда для выполнения проверки требуется доступ к базе данных и запрос к ней (пример: запрос для проверки уникального индекса по нескольким столбцам). В связи с этим и тем фактом, что запросы должны быть записаны на уровне хранилища, выясняется, что сущности домена должны иметь ссылки на их репозиторий взаимодействует на уровне модели домена, чтобы полностью поставить проверку на уровне модели домена. Так нормально ли для доменных сущностей иметь доступ к репозиториям?
Карамафруз
13

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

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

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

Похоже, что это все для того, чтобы избежать отдельной «модели анализа», которая отделена от фактической реализации системы.

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

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

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

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

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

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

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

Уди Дахан: самые большие ошибки команды делают при применении 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 по соображениям производительности. Разработчик должен иметь глубокие знания кода за интерфейсом.

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

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

Вывод: довольно низкая сепарация!

Ленивая Загрузка

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

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

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

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

Роль Интерфейс / Eager Fetching

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

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

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. Он лучше разделяет проблемы, не теряя гибкости, так как один размер подходит для всех подходов с отложенной загрузкой.

ТГП
источник
Спасибо, проверю ссылки. Но в своем ответе вы путаете «разделение интересов» с «отсутствием доступа к нему вообще»? Конечно, большинство людей согласятся с тем, что уровень персистентности должен храниться отдельно от уровня, в котором находятся сущности. Но это отличается от того, что «сущности не должны иметь возможности даже видеть уровень персистентности даже через очень общую независимость от реализации. интерфейс'.
codeulike
Загружаете данные через интерфейс или нет, вы все еще озабочены загрузкой данных при реализации бизнес-правил. Я согласен с тем, что многие люди все еще называют это разделение интересов, хотя, возможно, лучше использовать один принцип ответственности.
ttg
1
Не совсем уверен, как проанализировать ваш последний комментарий, но я думаю, что вы предлагаете не загружать данные при обработке бизнес-правил? Я вижу, что это сделало бы правила "чище". Но многим типам бизнес-правил потребуется ссылаться на другие данные - вы предлагаете, чтобы они были предварительно загружены отдельным объектом?
Codeulike
@codeulike: я обновил свой ответ. Вы по-прежнему можете загружать данные во время бизнес-правил, если считаете, что это абсолютно необходимо, но для этого не требуется добавлять строки кода доступа к данным в модель вашего домена (например, ленивая загрузка). В моделях доменов, которые я разработал, данные обычно загружаются заранее, как вы сказали. Я обнаружил, что выполнение бизнес-правил обычно не требует чрезмерного количества данных.
ТТГ
12

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

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

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

// 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>может избавить нас от уродливого призыва IEnumerable, но это усложнит обсуждение.)

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

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

Timo
источник
3

Мне кажется, что это общая хорошая практика, связанная с ООД, а не специфическая для DDD.

Причины, которые я могу придумать:

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

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

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

Алиреза Рахмани Халили
источник
Но не от сущности.
ссмит
От источника Вернон Вон IDDD: класс Календарь общественности распространяется EventSourcedRootEntity {... общественного CalendarEntry scheduleCalendarEntry (CalendarIdentityService aCalendarIdentityService,
Теймураза
проверить его работу @Teimuraz
Алиреза Рахмани Халили
1

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

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

Я думаю, что отделение доступа к данным (также называемое «Репозиторий») от вашей бизнес-логики - это одна из тех вещей, которые несколько раз были заново изобретены, хотя книга «Управляемый доменом дизайн» делает ее «шумной».

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

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

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

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

umlcat
источник
1
Спасибо, но я не спрашиваю о том, чтобы отделить доступ к данным от бизнес-логики - это совершенно ясно, что существует очень широкое согласие. Я спрашиваю, почему в архитектурах DDD, таких как S # arp, сущностям не разрешается даже «общаться» со слоем доступа к данным. Это интересная договоренность, о которой я не смог найти много дискуссий.
codeulike
0

Это из книги Эрика Эванса «Дизайн, управляемый доменом», или из другого места?

Это старые вещи. Книга Эрика только заставила это гудеть немного больше.

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

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

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

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

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

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

Арнис Лапса
источник
Правильно. Итак, как же полностью невежественная сущность Entity реализует Business Logic, если ей даже не разрешено общаться со слоем постоянства? Что он делает, когда ему нужно посмотреть на значения в произвольных других объектах?
Codeulike
Если вашей сущности нужно посмотреть на значения в произвольных других сущностях, у вас, вероятно, есть некоторые проблемы с дизайном. Возможно, рассмотрите возможность разделения классов, чтобы они были более сплоченными.
cdaq
0

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

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

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

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

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

Эдвард Энглунд
источник
-1

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

vsingh
источник
Нет, это приводит к ненужной связи с объектами, нарушает SRP и разделение проблем и затрудняет десериализацию объекта из постоянства (поскольку процесс десериализации теперь должен также вводить службы / репозитории, которые требует этот объект).
ссмит