Является ли изоляция модели домена / персистентности такой неловкой?

12

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

  1. Служба на прикладном уровне (предоставляющая набор функций) запрашивает объекты домена из репозитория, в котором она нуждается для выполнения своей функции.
  2. Конкретная реализация этого хранилища извлекает данные из хранилища, для которого оно было реализовано
  3. Служба сообщает объекту домена, который инкапсулирует бизнес-логику, для выполнения определенных задач, которые изменяют его состояние.
  4. Служба сообщает хранилищу, что нужно сохранить измененный объект домена.
  5. Хранилище должно отобразить объект домена обратно в соответствующее представление в хранилище.

Поток Иллюстрация

Теперь, учитывая вышеизложенные предположения, следующее кажется неловким:

Объявление 2 .:

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

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

Объявление 3 .:

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

Объявление 5 .:

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

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

В случае баз данных NoSQL, для которых едва ли существуют какие-либо ORM-подобные концепции, как вы отслеживаете, какие свойства изменились в доменных моделях save()?

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

В основном:

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

Буду признателен за разъяснения по этой теме. Верны ли мои предположения? Если нет, как правильно решить эти проблемы?

Двухместный М
источник
1
Хорошие моменты и вопросы, я тоже интересуюсь ими. Примечание: если вы правильно моделируете агрегат, это означает, что в любой момент времени экземпляр агрегата должен находиться в допустимом состоянии - это основная точка агрегата (а не использование агрегата в качестве контейнера композиции). Это также означает, что для восстановления агрегатной формы данных БД в самом хранилище обычно должен использоваться определенный конструктор и набор операций мутации, и я не вижу, как какой-либо ORM мог бы автоматически знать, как выполнять эти операции ,
Душан
2
Что еще более разочаровывает, так это то, что эти вопросы, подобные вашим, задаются довольно часто, но, насколько мне известно, есть нулевые примеры реализации агрегатов и репозиториев, которые описаны в книге
Dusan

Ответы:

5

Ваше базовое понимание правильное, а архитектура, которую вы набросаете, хороша и работает хорошо.

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

1: Доменные объекты не должны включать весь граф объектов. Например, я мог бы иметь:

public class Customer
{
    public string AddressId {get;set;}
    public string Name {get;set;}
}

public class Address
{
    public string Id {get;set;}
    public string HouseNumber {get;set;
}

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

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

Бизнес-правило не гласит: «Два экземпляра пользователя с одинаковым SocialSecurityNumber не могут существовать одновременно»

Дело в том, что они не могут существовать в одном хранилище.

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

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

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

Основные вопросы

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

  2. Массовые обновления. Да, вы должны индивидуально изменить каждый объект, а затем просто добавить метод SaveMyObjects (список объектов) в свой репозиторий, чтобы выполнить массовое обновление.

    Вы хотите, чтобы объект домена или служба домена содержали логику. Не база данных. Это означает, что вы не можете просто «обновить набор клиентов name = x, где y», потому что для всего, что вы знаете, объект Customer или CustomerUpdateService делают 20 странных других вещей, когда вы меняете имя.

Ewan
источник
Отличный ответ. Вы абсолютно правы, я привык к кодированию в стиле активной записи, поэтому шаблон репозитория кажется на первый взгляд странным. Однако не противоречат ли «худые» доменные объекты ( AddressIdвместо Address) принципам ОО?
Двойной М
Нет, у вас все еще есть объект Address, он просто не является потомком Customer
Ewan
сопоставление рекламных объектов без отслеживания изменений softwareengineering.stackexchange.com/questions/380274/…
Double M
2

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

Пункт 2 .: (загрузка полных графов объектов)

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

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

Что если я просто хочу массово обновить некоторые записи? Зачем мне нужно представление объекта для всех записей? И т.п.

Поэтому решение этой проблемы - просто не использовать ORM для сценариев использования, для которых он не подходит. Реализуйте вариант использования «естественно» как есть, который иногда не требует дополнительной «абстракции» самих данных (объектов данных) или абстракции над «таблицами» (репозиториями).

Как вы уже указали, иметь наполовину заполненные объекты данных или заменять ссылки на объекты на «идентификаторы» - это в лучшем случае обходные пути, а не хорошие.

Пункт 3 .: (проверка ограничений)

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

Пункт 5 .: (ОРМ)

Это, конечно, важная предпосылка для отделения конкретной реализации хранилища от кода домена. Однако действительно ли это так дорого?

Нет, это не так. Есть много других способов иметь постоянство. Проблема в том, что ORM рассматривается как «единственное» решение, которое нужно использовать всегда (по крайней мере, для реляционных баз данных). Попытка предложить не использовать его для некоторых сценариев использования в проекте бесполезна и в зависимости от самого ORM иногда даже невозможна, поскольку эти инструменты иногда используют кеши и позднее выполнение.

Общий вопрос 1 .: (сделки)

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

Любое другое ограничение является полностью искусственным.

Общий вопрос 2 .: (массовые операции)

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

Требование отделить «логику» от SQL исходит от ORM. Они должны это сказать, потому что не могут это поддержать. Это не по своей сути "плохо".

Резюме

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

Точно так же абстракция dataDject-репозитория DDD также не всегда является лучшей. Я бы даже сказал, что они редко бывают оптимальными.

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

Роберт Бройтигам
источник
Там очень интересные моменты, спасибо за подтверждение моих предположений. Вы сказали, что есть много других способов быть настойчивыми. Можете ли вы порекомендовать эффективный шаблон проектирования, который будет использоваться с базами данных графов (без ORM), который все равно предоставит PI?
Двойной М
1
Я бы на самом деле вопрос, нужна ли вам изоляция (и какого рода) в первую очередь. Изоляция по технологии (например, база данных, пользовательский интерфейс и т. Д.) Почти автоматически приносит «неловкость», которую вы пытаетесь избежать, в пользу более легкой замены технологии базы данных. Однако стоимость является более сложным изменением бизнес-логики, поскольку она распространяется по уровням. Или вы можете разделить бизнес-функции, что усложнит изменение баз данных, но упростит изменение логики. Какой из них вы действительно хотите?
Роберт Бройтигам,
1
Вы можете добиться максимальной производительности, если просто смоделируете домен (т. Е. Бизнес-функции) и не абстрагируете базу данных (неважно, реляционная или графическая). Поскольку база данных не является абстрагированной от варианта использования, она может реализовывать самые оптимальные запросы / обновления, которые она хочет, и ей не нужно проходить через какую-то неуклюжую объектную модель, чтобы выполнить то, что она хочет.
Роберт Бройтигам,
Ну, главная цель - не допускать, чтобы проблемы постоянства были в стороне от бизнес-логики, чтобы иметь чистый код, который легко понять, расширить и протестировать. Возможность обмена технологиями БД - это просто бонус. Я вижу, что существует очевидное трение между эффективностью и незнанием, которое кажется более сильным в графических БД из-за мощных запросов, которые вы можете (но не можете) использовать.
Двойной М
1
Как разработчик Java Enterprise, я могу вам сказать, что мы пытались отделить постоянство от логики в течение последних двух десятилетий. Не работает Во-первых, разделение никогда не достигалось. Даже сегодня в якобы «бизнес» объектах есть все виды связанных с базой данных, основным из которых является идентификатор базы данных (и множество аннотаций базы данных). Во-вторых, как вы сказали, иногда бизнес-логика выполняется в базе данных в любом случае. В-третьих, это причина того, что у нас есть конкретные базы данных, чтобы иметь возможность разгрузить некоторую логику, лучше всего выполненную там, где находятся данные.
Роберт Бройтигам