В Domain Driven Design, кажется , есть много из соглашения , что Сущности не должен доступ Хранилища непосредственно.
Это из книги Эрика Эванса « Дизайн, управляемый доменом» , или из другого места?
Где есть хорошие объяснения причин этого?
редактировать: уточнить: я не говорю о классической ОО практике разделения доступа к данным на отдельный слой от бизнес-логики - я говорю о конкретной договоренности, согласно которой в DDD сущности не должны общаться с данными уровень доступа вообще (т.е. они не должны содержать ссылки на объекты репозитория)
обновление: я дал награду BacceSR, потому что его ответ казался самым близким, но я все еще довольно в неведении об этом. Если это такой важный принцип, наверняка есть где-то хорошие статьи об этом в Интернете?
обновление: март 2013 года, положительные отзывы по этому вопросу подразумевают, что в этом есть большой интерес, и хотя ответов было много, я все же думаю, что есть еще места, если у людей есть идеи по этому поводу.
Ответы:
Здесь немного путаницы. Хранилища имеют доступ к совокупным корням. Совокупные корни являются сущностями. Причиной этого является разделение проблем и хорошее наслоение. Это не имеет смысла для небольших проектов, но если вы работаете в большой команде, вы хотите сказать: «Вы получаете доступ к продукту через репозиторий продуктов. Продукт - это совокупный корень для коллекции объектов, включая объект ProductCatalog. Если вы хотите обновить ProductCatalog, вы должны пройти через ProductRepository. "
Таким образом, вы получите очень и очень четкое разделение бизнес-логики и того, где все обновляется. У вас нет ребенка, который сам по себе и пишет всю эту программу, которая делает все эти сложные вещи в каталоге продуктов, и когда дело доходит до интеграции его в основной проект, вы сидите и смотрите на это и понимаете, что все должно быть угроблено. Это также означает, что когда люди присоединяются к команде, добавляют новые функции, они знают, куда идти и как структурировать программу.
Но ждать! Репозиторий также относится к постоянному слою, как в паттерне репозитория. В лучшем мире репозиторий Эрика Эванса и паттерн репозитория будут иметь разные имена, потому что они имеют тенденцию частично перекрываться. Чтобы получить шаблон репозитория, у вас есть контраст с другими способами доступа к данным, с сервисной шиной или системой модели событий. Обычно, когда вы добираетесь до этого уровня, определение Эрик Эванса «Репозиторий» уходит на второй план, и вы начинаете говорить о ограниченном контексте. Каждый ограниченный контекст по сути является своим собственным приложением. У вас может быть сложная система одобрения для внесения вещей в каталог продукции. В вашем оригинальном дизайне продукт был центральной частью, но в этом ограниченном контексте каталог продуктов. Вы по-прежнему можете получить доступ к информации о продукте и обновить продукт через служебную шину,
Вернуться к исходному вопросу. Если вы обращаетесь к хранилищу из сущности, это означает, что сущность на самом деле не является бизнес-сущностью, а, вероятно, должна существовать на уровне сервисов. Это связано с тем, что сущности являются бизнес-объектами и должны быть максимально похожими на DSL (предметно-ориентированный язык). Только бизнес-информация в этом слое. Если вы устраняете проблему с производительностью, вам нужно искать в другом месте, поскольку здесь должна быть только деловая информация. Если вдруг у вас возникнут проблемы с приложением, вам будет очень трудно расширять и поддерживать приложение, что действительно является основой DDD: создание поддерживаемого программного обеспечения.
Ответ на комментарий 1 : Хорошо, хороший вопрос. Поэтому не вся проверка происходит на уровне домена. Sharp имеет атрибут «DomainSignature», который делает то, что вы хотите. Он осведомлен о постоянстве, но будучи атрибутом, поддерживает уровень домена в чистоте. Это гарантирует, что у вас нет дублирующегося объекта, в вашем примере с тем же именем.
Но давайте поговорим о более сложных правилах проверки. Допустим, вы Amazon.com. Вы когда-нибудь заказывали что-то с просроченной кредитной картой? У меня есть, где я не обновил карту и что-то купил. Он принимает заказ и пользовательский интерфейс сообщает мне, что все персиковое. Примерно через 15 минут я получу электронное письмо с сообщением о проблеме с моим заказом, моя кредитная карта недействительна. Здесь происходит то, что в идеале существует некоторая проверка регулярных выражений на уровне домена. Это правильный номер кредитной карты? Если да, сохраните порядок. Однако существует дополнительная проверка на уровне задач приложения, где запрашивается внешняя служба, чтобы узнать, можно ли произвести оплату по кредитной карте. Если нет, то на самом деле ничего не отправляйте, приостановите заказ и дождитесь клиента.
Не бойтесь создавать объекты проверки на сервисном уровне, который может обращаться к репозиториям. Просто держите его вне доменного слоя.
источник
Сначала я был убежден, чтобы некоторые из моих сущностей имели доступ к репозиториям (т.е. отложенная загрузка без ORM). Позже я пришел к выводу, что не следует и что я могу найти альтернативные способы:
Вернон Вон в Красной книге «Реализация доменного дизайна» относится к этой проблеме в двух местах, о которых я знаю (примечание: эта книга полностью одобрена Эвансом, как вы можете прочитать в предисловии). В главе 7, посвященной службам, он использует службу домена и спецификацию, чтобы обойти необходимость использования агрегатом репозитория и другого агрегата для определения того, прошел ли пользователь аутентификацию. Его цитируют так:
Вернон, Вон (2013-02-06). Реализация доменно-управляемого дизайна (Kindle Location 6089). Пирсон Образование. Kindle Edition.
А в главе 10 «Агрегаты» в разделе «Навигация по моделям» он говорит (сразу после того, как он рекомендует использовать глобальные уникальные идентификаторы для ссылки на другие корни агрегатов):
Он идет, чтобы показать пример этого в коде:
Далее он также упомянул еще одно решение о том, как доменная служба может использоваться в методе совокупных команд наряду с двойной диспетчеризацией . (Я не могу порекомендовать, насколько полезно читать его книгу. После того, как вы устали от бесконечного рытья в Интернете, раскошелитесь на заслуженные деньги и прочитайте книгу.)
То я имел некоторое обсуждение с всегда милостивым Марко Pivetta @Ocramius , который показал мне немного коды на вытаскивая спецификацию из домена и с помощью этого:
1) Это не рекомендуется:
2) В доменном сервисе это хорошо:
источник
getFriends()
прежде чем делать что-либо еще, он будет пустым или отложенным. Если пусто, то этот объект лежит и находится в недопустимом состоянии. Есть мысли по этому поводу?Это очень хороший вопрос. Я с нетерпением жду некоторого обсуждения по этому поводу. Но я думаю, что это упоминается в нескольких книгах DDD и Джимми Нильссона и Эрика Эванса. Я думаю, это также видно из примеров, как использовать шаблон хранилища.
НО давайте обсудим. Я думаю, что очень правильная мысль, почему сущность должна знать о том, как сохранить другую сущность? Важным для DDD является то, что каждая сущность несет ответственность за управление своей собственной «сферой знаний» и не должна ничего знать о том, как читать или писать другие сущности. Конечно, вы, вероятно, можете просто добавить интерфейс хранилища в Entity A для чтения Entities B. Но есть риск, что вы предоставите знания о том, как сохранить B. Будет ли объект A также выполнять валидацию на B перед сохранением B в db?
Как вы можете видеть, сущность A может более активно участвовать в жизненном цикле сущности B, что может усложнить модель.
Я предполагаю (без какого-либо примера), что юнит-тестирование будет более сложным.
Но я уверен, что всегда будут сценарии, когда вы склонны использовать репозитории через сущности. Вы должны посмотреть на каждый сценарий, чтобы принять правильное решение. Плюсы и минусы. Но решение для репозитория-сущности, на мой взгляд, начинается с множества минусов. Это должно быть особый сценарий с Профи, который уравновешивает Минусы ....
источник
Зачем выделять доступ к данным?
Из книги я думаю, что первые две страницы главы «Модель управляемой конструкции» дают некоторое обоснование того, почему вы хотите абстрагироваться от деталей технической реализации от реализации модели предметной области.
Похоже, что это все для того, чтобы избежать отдельной «модели анализа», которая отделена от фактической реализации системы.
Из того, что я понимаю в книге, говорится, что эта «модель анализа» может быть разработана без учета программной реализации. Как только разработчики пытаются реализовать модель, понятную бизнес-стороне, они по необходимости создают свои собственные абстракции, создавая стенку в общении и понимании.
С другой стороны, разработчики, вносящие слишком много технических проблем в модель предметной области, также могут вызвать это разделение.
Таким образом, вы могли бы подумать, что практика разделения проблем, таких как постоянство, может помочь защитить от этих моделей расхождение моделей анализа. Если необходимо ввести в модель такие вещи, как постоянство, тогда это красный флаг. Возможно модель не практична для реализации.
Цитирование:
«Единая модель уменьшает вероятность ошибки, потому что дизайн в настоящее время является прямым следствием тщательно продуманной модели. Дизайн и даже сам код имеют коммуникативность модели».
Как я понимаю, если у вас появилось больше строк кода, связанных с такими вещами, как доступ к базе данных, вы потеряете эту коммуникативность.
Если вам необходим доступ к базе данных для проверки уникальности, взгляните на:
Уди Дахан: самые большие ошибки команды делают при применении 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
в разделе «Сценарии неиспользования модели предметной области», который затрагивает ту же тему.
Как выделить доступ к данным
Загрузка данных через интерфейс
«Уровень доступа к данным» был абстрагирован через интерфейс, который вы вызываете для получения необходимых данных:
Плюсы: Интерфейс отделяет код доступа к данным, что позволяет вам писать тесты. Доступ к данным может обрабатываться в каждом конкретном случае, обеспечивая лучшую производительность, чем общая стратегия.
Минусы: код вызова должен предполагать, что было загружено, а что нет.
Скажем, GetOrderLines возвращает объекты OrderLine с нулевым свойством ProductInfo по соображениям производительности. Разработчик должен иметь глубокие знания кода за интерфейсом.
Я попробовал этот метод на реальных системах. В конечном итоге вы постоянно меняете объем загружаемого содержимого, пытаясь исправить проблемы с производительностью. В итоге вы заглядываете за интерфейс, чтобы посмотреть код доступа к данным, чтобы увидеть, что загружается, а что нет.
Теперь разделение задач должно позволить разработчику сосредоточиться на одном аспекте кода одновременно, насколько это возможно. Интерфейсная техника удаляет, КАК эти данные загружены, но НЕ КАК МНОГО загружаются данные, КОГДА они загружаются, и ГДЕ они загружаются.
Вывод: довольно низкая сепарация!
Ленивая Загрузка
Данные загружаются по запросу. Вызовы для загрузки данных скрыты внутри самого графа объектов, где доступ к свойству может привести к выполнению запроса sql перед возвратом результата.
Плюсы: «КОГДА, ГДЕ и КАК» доступа к данным скрыты от разработчика, сосредоточенного на предметной логике. В агрегате нет кода, который занимается загрузкой данных. Количество загруженных данных может быть точным количеством, требуемым кодом.
Минусы: когда вы сталкиваетесь с проблемой производительности, ее трудно исправить, если у вас есть универсальное решение "один размер подходит всем". Ленивая загрузка может привести к ухудшению производительности в целом, а реализация отложенной загрузки может быть сложной задачей.
Роль Интерфейс / Eager Fetching
Каждый вариант использования делается явным образом через ролевый интерфейс, реализованный агрегатным классом, что позволяет обрабатывать стратегии загрузки данных для каждого варианта использования.
Стратегия выборки может выглядеть так:
Тогда ваш агрегат может выглядеть так:
BillOrderFetchingStrategy используется для построения агрегата, а затем агрегат выполняет свою работу.
Плюсы: позволяет настраивать код для каждого варианта использования, обеспечивая оптимальную производительность. Инлайн с интерфейсом сегрегация Принцип . Нет сложных требований к коду. Агрегатные модульные тесты не должны имитировать стратегию загрузки. Общая стратегия загрузки может использоваться в большинстве случаев (например, стратегия «загрузить все»), и при необходимости могут быть реализованы специальные стратегии загрузки.
Минусы: Разработчик все еще должен скорректировать / пересмотреть стратегию выборки после изменения кода домена.
При подходе стратегии извлечения вы все равно можете изменить пользовательский код извлечения для изменения бизнес-правил. Это не идеальное разделение проблем, но в итоге будет более ремонтопригодным и лучше, чем первый вариант. Стратегия выборки инкапсулирует данные HOW, WHEN и WHERE. Он лучше разделяет проблемы, не теряя гибкости, так как один размер подходит для всех подходов с отложенной загрузкой.
источник
Я обнаружил, что у этого блога есть довольно хорошие аргументы против инкапсуляции репозиториев внутри сущностей:
http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities
источник
Какой отличный вопрос. Я нахожусь на том же пути открытия, и большинство ответов в Интернете, кажется, приносят столько же проблем, сколько и решений.
Так что (рискуя написать что-то, с чем я не согласен через год), вот мои открытия.
Прежде всего, нам нравится модель с богатым доменом , которая дает нам высокую обнаруживаемость (того, что мы можем сделать с агрегатом) и удобочитаемость (экспрессивные вызовы методов).
Мы хотим достичь этого без внедрения каких-либо сервисов в конструктор объекта, потому что:
Как же тогда мы можем это сделать? Пока я пришел к выводу, что зависимости методов и двойная диспетчеризация обеспечивают достойное решение.
CreateCreditNote()
Теперь требуется служба, которая отвечает за создание кредитных нот. Он использует двойную диспетчеризацию , полностью разгружая работу ответственному сервису, сохраняя возможность обнаружения со стороныInvoice
объекта.SetStatus()
теперь имеет простую зависимость от регистратора, который, очевидно, будет выполнять часть работы .Для последнего, чтобы упростить клиентский код, мы могли бы вместо этого войти через
IInvoiceService
. В конце концов, регистрация счетов кажется довольно неотъемлемой частью счета. Такой единыйIInvoiceService
помогает избежать потребности во всевозможных мини-сервисах для различных операций. Недостатком является то, что становится неясным, что именно будет делать этот сервис . Это может даже начать выглядеть как двойная отправка, хотя большая часть работы все еще выполняетсяSetStatus()
сама по себе.Мы все еще можем назвать параметр «logger» в надежде раскрыть наши намерения. Кажется, слабоват.
Вместо этого я бы предпочел запросить
IInvoiceLogger
(как мы уже делали в примере кода) иIInvoiceService
реализовать этот интерфейс. Клиентский код может просто использовать свой единственныйIInvoiceService
для всехInvoice
методов, которые запрашивают любой такой очень специфический, миниатюрный сервис-инвойс, в то время как сигнатуры методов по-прежнему ясно дают понять, о чем они просят.Я заметил, что я не обращался к хранилищам явно. Ну, регистратор является или использует хранилище, но позвольте мне также привести более явный пример. Мы можем использовать тот же подход, если хранилище необходимо всего одним или двумя методами.
Фактически, это альтернатива постоянно ленивым нагрузкам .
Обновление: я оставил текст ниже для исторических целей, но я рекомендую избегать ленивых нагрузок на 100%.
Для истинных, на основе свойств ленивых нагрузок, я действительно в настоящее время используют инъекции конструктора, но в инерционности-невежественным образом.
С одной стороны, хранилище, которое загружает
Invoice
из базы данных, может иметь свободный доступ к функции, которая будет загружать соответствующие кредитные ноты и вставлять эту функцию вInvoice
.С другой стороны, код, который создает фактическое новое
Invoice
, просто пропустит функцию, которая возвращает пустой список:(Обычай
ILazy<out T>
может избавить нас от уродливого призываIEnumerable
, но это усложнит обсуждение.)Буду рад услышать ваше мнение, предпочтения и улучшения!
источник
Мне кажется, что это общая хорошая практика, связанная с ООД, а не специфическая для DDD.
Причины, которые я могу придумать:
источник
просто Вернон Вон дает решение:
источник
Я научился кодировать объектно-ориентированное программирование до того, как появилось все это отдельное жужжание слоя, и мои первые объекты / классы отображались непосредственно в базе данных.
В конце концов, я добавил промежуточный слой, потому что мне пришлось перейти на другой сервер базы данных. Я видел / слышал об одном и том же сценарии несколько раз.
Я думаю, что отделение доступа к данным (также называемое «Репозиторий») от вашей бизнес-логики - это одна из тех вещей, которые несколько раз были заново изобретены, хотя книга «Управляемый доменом дизайн» делает ее «шумной».
В настоящее время я использую 3 слоя (GUI, Logic, Data Access), как это делают многие разработчики, потому что это хорошая техника.
Разделение данных на
Repository
слой (он жеData Access
слой) может показаться хорошим методом программирования, а не просто правилом, которому нужно следовать.Как и во многих методологиях, вы, возможно, захотите начать, НЕ реализовав, и в конечном итоге обновить вашу программу, как только вы их поймете.
Цитата: Илиада не была полностью изобретена Гомером, Кармина Бурана не была полностью изобретена Карлом Орффом, и в обоих случаях человек, который заставлял других работать, все вместе, получил кредит ;-)
источник
Это старые вещи. Книга Эрика только заставила это гудеть немного больше.
Причина проста - человеческий разум ослабевает, когда сталкивается со смутно связанными множественными контекстами. Они приводят к неоднозначности (Америка в Южной / Северной Америке означает Южную / Северную Америку), неоднозначность ведет к постоянному отображению информации всякий раз, когда разум «дотрагивается до нее», что приводит к плохой производительности и ошибкам.
Деловая логика должна быть отражена как можно более четко. Внешние ключи, нормализация, объектно-реляционное отображение принадлежат совершенно другому домену - эти вещи технические, связанные с компьютером.
По аналогии: если вы изучаете, как писать от руки, вы не должны быть обременены пониманием того, где было сделано перо, почему чернила держатся на бумаге, когда была изобретена бумага и каковы другие известные китайские изобретения.
Причина все та же, что я упоминал выше. Здесь это только на шаг дальше. Почему сущности должны быть частично невежественными, если они могут быть (по крайней мере, близки) полностью? Наша модель содержит меньше вопросов, не связанных с предметной областью, - больше пространства для дыхания, которое наш разум получает, когда ему приходится переосмысливать его.
источник
Чтобы процитировать Каролину Лилиенталь, «Шаблоны должны предотвращать циклы» https://www.youtube.com/watch?v=eJjadzMRQAk , где она ссылается на циклические зависимости между классами. В случае хранилищ внутри агрегатов существует соблазн создать циклические зависимости из-за удобства навигации по объектам в качестве единственной причины. Шаблон, упомянутый выше prograhammer, рекомендованный Верноном Воном, где другие агрегаты ссылаются на идентификаторы вместо корневых экземпляров (есть ли название для этого шаблона?), Предлагает альтернативу, которая может привести к другим решениям.
Пример циклической зависимости между классами (признание):
(Time0): два класса, Sample и Well, ссылаются друг на друга (циклическая зависимость). Well относится к образцу, а Sample относится к лунке, из-за удобства (иногда зацикливание образцов, иногда зацикливание всех лунок в планшете). Я не мог представить случаи, когда Образец не ссылался бы на скважину, где он находится.
(Время 1): Год спустя, многие варианты использования были реализованы .... и теперь есть случаи, когда Образец не должен ссылаться обратно на скважину, в которой он находится. В рабочем шаге есть временные пластины. Здесь скважина относится к образцу, который, в свою очередь, относится к скважине на другой пластине. Из-за этого иногда происходит странное поведение, когда кто-то пытается реализовать новые функции. Требуется время, чтобы проникнуть.
Мне также помогла упомянутая выше статья о негативных аспектах отложенной загрузки.
источник
В идеальном мире DDD предлагает, чтобы сущности не имели ссылки на уровни данных. но мы не живем в идеальном мире. Домены, возможно, должны ссылаться на другие доменные объекты для бизнес-логики, с которой они могут не иметь зависимости. Для объектов логично ссылаться на уровень хранилища только для чтения, чтобы получить значения.
источник