Со всеми этими услугами, как я не могу быть анемичным?

90

Где мы проводим грань между делегированием и инкапсуляцией бизнес-логики? Мне кажется, что чем больше мы делегируем, тем более анемичным мы становимся. Тем не менее, делегирование также способствует повторному использованию и принципу DRY. Так что уместно делегировать и что должно остаться в наших моделях доменов?

Возьмите следующие проблемы в качестве примеров:

Авторизация . Должен ли объект домена отвечать за поддержание своих правил контроля доступа (таких как свойство CanEdit) или он должен быть делегирован другому компоненту / услуге, единолично отвечающему за управление доступом, например, IAuthorizationService.CanEdit (object)? Или это должно быть сочетание двух? Возможно, у объекта домена есть свойство CanEdit, которое делегирует внутреннему IAuthorizationService для выполнения фактической работы?

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

Создание объекта . Фабричный класс в сравнении с фабричными методами против «обновления» экземпляра. Если мы используем отдельный класс фабрики, мы можем изолировать и инкапсулировать логику создания, но за счет открытия состояния нашего объекта для фабрики. Этим можно управлять, если наш уровень домена находится в отдельной сборке, предоставляя внутренний конструктор, используемый фабрикой, но это становится проблемой, если существует несколько шаблонов создания. И если все, что делает фабрика, - это вызывать правильного конструктора, какой смысл иметь фабрику?

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

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

Видишь, куда я иду с этим?

У Рокфорда Лотки есть отличная дискуссия о причинах, по которым он выбрал маршрут Class-in-Charge для своей платформы CSLA, и у меня есть немного истории с этой платформой, и я могу увидеть его идею о том, что бизнес-объекты во многом параллельны объектам домена. Но, пытаясь стать более приверженными хорошим идеалам DDD, мне интересно, когда сотрудничество становится слишком много.

Если я получу сервис IAuthorizationService, IValidator, IFactory и IRepository для моего совокупного корня, что останется? Достаточно ли наличия метода Publish, который изменяет состояние объекта с «Черновик» на «Опубликованный», чтобы считать класс неанемичным доменным объектом?

Твои мысли?

SonOfPirate
источник
Отличный вопрос У меня нет ответа для вас, так как я почти всегда заканчиваю совершенно анемичным в дизайне по той же самой причине - используя / потребляя / выставляя сервисы из / в разных контекстов или разных приложений.
Громанко
Отличный вопрос, хотелось бы увидеть, как дядя Боб, Мартинфоулер, Эричеванс и др. Вмешиваются в это. Теперь для меня, чтобы уйти и долго думать
Мартейн Вербург
Я все время развиваюсь до анемичной модели; и поэтому я борюсь с этим точно так же. Отличный вопрос!
L-Four
Это был мой опыт работы с DDD. Мы делаем это там, где я работаю, и мы всегда заканчиваем анемией. Я ранее (и до сих пор на самом деле) использовать Csla. Нашему архитектору не нравится, что мы страдаем анемией, но он не смог дать мне хорошего ответа на вопрос, что должен делать объект, если все, на что вы указываете, не может быть выполнено внутри объекта. В конце концов, попытка быть пуристом DDD, кажется, создает больше работы, чем стоит. Я лично думаю, что Csla и DDD уживаются (они кажутся идентичными в принципах), если вы готовы оставить догму DDD позади.
Энди
Ниже приведен пример некоторых методов , используемых при моделировании домен из поведенческой (не ориентированные на данные) точки зрения: medium.com/@wrong.about/...
Zapadlo

Ответы:

66

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

  • Постоянство никогда не должно быть в доменной модели. Никогда Вот почему вы полагаетесь на абстрактные типы, например, IRepositoryесли часть модели когда-либо должна что-то делать, например, извлекать другую часть модели и использовать внедрение зависимостей или какой-либо подобный метод, чтобы связать реализацию. Так что вычеркните это из записи.

  • Авторизация, как правило, не является частью модели вашего домена, если только она на самом деле не является частью домена, например, если вы пишете программное обеспечение безопасности. Механизм того, кому разрешено выполнять то, что в приложении, обычно обрабатывается на «краю» бизнес-уровня / домена, общедоступных частей, с которыми на самом деле разрешено общаться частям пользовательского интерфейса и интеграции - Контроллер в MVC, Сервисы или сама система обмена сообщениями в SOA ... вы понимаете.

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

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

Так что, как только вы перебираете все это, остается только проверка. Это единственный хитрый способ.

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

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

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

Как правило, при рассмотрении вопроса о том, относится ли что-либо к модели предметной области, задайте себе следующий вопрос:

"Может ли эта функциональность когда-либо измениться по чисто техническим причинам?" Другими словами, не из-за каких-либо заметных изменений в реальном бизнесе или области?

Если ответ «да», то он не относится к модели предметной области. Это не часть домена.

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

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

Некоторые домены действительно являются анемией. Приложения для социальных закладок на самом деле не имеют особого «домена», о котором можно говорить; все ваши объекты в основном просто данные без функциональности. Система продаж и CRM, с другой стороны, имеет довольно сложную область; когда вы загружаете Rateсущность, то вполне разумно ожидать, что вы действительно сможете делать что-то с этой ставкой, например, применять ее к количеству заказа и просить, чтобы он вычислял скидки по объему, промо-коды и все эти забавные вещи.

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

Aaronaught
источник
2
Кроме того, @SonOfPirate, весьма вероятно, что вы захотите когда-нибудь изменить всю свою модель безопасности; Безопасность на основе ролей часто устаревает в пользу защиты на основе утверждений или прав или, возможно, вам даже нужна защита на уровне поля. Представьте, что вы пытаетесь переделать всю модель вашего домена, если это произошло.
Aaronaught
1
@SonOfPirate: Похоже, вы все еще немного застряли в старой модели «бизнес-уровня», в которой существовал «средний уровень», который представлял собой тонкую оболочку уровня данных, реализующую различные бизнес-правила, а также обычно правила безопасности. , Это не то, что является доменным слоем. Домен - это то, от чего зависит все остальное , он представляет объекты и отношения реального мира, которыми должна управлять система.
Ааронаут
1
@ LaurentBourgault-Roy: Извините, я вам не верю. Каждая компания может сказать это о каждой заявке; В конце концов, изменение базы данных является трудно. Это не делает его частью вашего домена, а бизнес-логика, связанная с постоянным уровнем, означает плохую абстракцию. Модель предметной области ориентирована на поведение, а это именно то, чем не является постоянство . Это не предмет, по которому люди могут придумывать свои собственные определения; это довольно четко прописано в DDD. Вам часто не нужна модель домена для приложений CRUD или отчетов, но вы также не должны утверждать, что у вас есть модель, когда у вас ее нет.
Ааронаут
1
Авторизация абсолютно относится к уровню домена. Кто решает, какие разрешения существуют? Бизнес делает. Кто решает, кто что может сделать? Бизнес делает. У нас только что был запрос на функцию несколько недель назад, чтобы изменить полномочия, необходимые для редактирования определенного объекта в нашей системе. Если шаблон основывался на главном шаблоне, тогда для редактирования (переопределения значений из главного) требовались более высокие привилегии, чем обычно. Куда относится эта логика, если не в домене?
Энди
1
Другим видом авторизации может быть ограничение учетной записи клиента. Обычные специалисты по обслуживанию клиентов могут поднять его до определенного уровня, но, возможно, более высокие ограничения требуют одобрения руководства. Это логика авторизации.
Энди
6

Авторизация. Должен ли объект домена отвечать за соблюдение правил контроля доступа

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

Совместное использование стратегий авторизации между уровнями (в пользовательском интерфейсе и далее в сервисе или в обработчике команд) легче, когда авторизация разделяется отдельно от модели предметной области.

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

Проверка. То же обсуждение, что и выше, относится к валидации.

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

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

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

Создание объекта. Фабричный класс в сравнении с фабричными методами против «обновления» экземпляра.

Я использую один слой косвенности. В своих последних проектах, это команда + обработчик для создания чего - то, то есть CreateNewAccountCommand. Альтернативой может быть всегда использовать фабрику (хотя это может быть неудобно, если остальная часть операции сущностей предоставляется классом обслуживания, который отделен от класса фабрики).

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

Упорство. ... почему бы не иметь метод Save в нашем доменном объекте

Это редко хорошая идея; Я думаю, что есть много общего опыта, чтобы поддержать это.

Если я получу сервис IAuthorizationService, IValidator, IFactory и IRepository для моего совокупного корня, что останется? Достаточно ли наличия метода Publish, который изменяет состояние объекта с «Черновик» на «Опубликованный», чтобы считать класс неанемичным объектом домена ???

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

Квентин-starin
источник
2
«Одна сложная часть, с которой можно столкнуться, - это контекстная авторизация, в которой команда может или не может быть разрешена не только на основе ролей пользователя, но и бизнес-данных / правил». - А как вы к этому подходите? Более чем часто, по крайней мере для меня, наши правила авторизации представляют собой сочетание роли и текущего состояния.
SonOfPirate
@SonOfPirate: обработчики событий, которые прослушивают события домена и обновляют таблицы, которые очень специфичны для запросов, проверяющих авторизацию. Логика, которая просматривает состояние и определяет, авторизована ли роль или отдельное лицо или нет, в обработчике событий (поэтому таблицы почти всегда являются простыми да / нет, что делает проверку подлинности простым поиском). Кроме того, как только любая из этих логик используется более чем в одном месте, она преобразуется из обработчика в общую службу.
Квентин Старин
1
В целом, за последние несколько лет я все дальше и дальше удалялся от попыток объединить все в домен или бизнес-объект. По моему опыту кажется, что сделать вещи более детальными и менее связанными - это долгосрочная победа. Таким образом, хотя с одной точки зрения этот метод размещает бизнес-логику за пределами домена (до некоторой степени), он также поддерживает гибкие модификации позже. Это все о достижении баланса.
Квентин Старин
4
Ссылка на сам ответ здесь: я нашел шаблон состояния очень полезным при работе с проверками, которые зависят как от разрешений, так и от бизнес-правил. Создайте абстрактное состояние, которое внедряется в модель домена и предоставляет методы, которые принимают объект домена в качестве параметра и проверяют действия, специфичные для домена. Таким образом, если ваши правила разрешений меняются (как они почти всегда делают), вам никогда не придется возиться с моделью, чтобы приспособиться к ней, потому что реализации состояний живут в вашем компоненте безопасности.
Aaronaught
1
Оставаясь на тему авторизации, позвольте мне бросить осязаемый пример на стол, чтобы увидеть, как вы (оба) справитесь с этим. У меня есть операция «Публикация» в моем доменном объекте, которая требует, чтобы пользователь был в роли издателя И чтобы объект находился в определенном состоянии. Я хочу скрыть или отключить «кнопку» публикации в пользовательском интерфейсе. Как бы вы достигли этого?
SonOfPirate
4

Хорошо, здесь идет для меня. Я предвосхищу это, сказав, что:

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

  • IANMF (я не Мартин Фаулер);)

  • Грязный маленький секрет в том, что для небольших проектов (даже, возможно, средних) важна последовательность вашего подхода.

авторизация

Для меня аутентификация и авторизация - это сквозная проблема. В моем счастливом маленьком мире Java это делегируется безопасности Spring или фреймворку Apache Shiro.

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

например, объект Car имеет 4 колеса (хорошо, есть некоторые странные исключения, но давайте пока проигнорируем странный 3-колесный автомобиль). Автомобиль просто недействителен, если у него нет 4 (в моем мире), так что проверка является частью того, что является определением автомобиля. Это не значит, что у вас не может быть вспомогательных классов проверки.

В моем счастливом мире Java я использую платформы проверки Bean и использую простые аннотации в большинстве моих полей Bean. Тогда легко проверить ваш объект независимо от того, в каком слое вы находитесь.

Создание объекта

Я смотрю Фабричные классы с осторожностью. Слишком часто я вижу xyxFactoryFactoryкласс;)

Я склонен просто создавать newобъект по мере необходимости, пока не столкнусь со случаем, когда внедрение зависимостей оправдано (и поскольку я пытаюсь следовать подходу TDD, это встречается чаще, чем нет).

В моем счастливом мире Java все больше и больше Guice, но Spring по-прежнему здесь король.

Упорство

Так что это дебаты, которые идут в кругах и каруселях, и я всегда думаю об этом.

Некоторые говорят, что если вы смотрите на объект «чисто», постоянство не является основным свойством, это просто внешняя проблема.

Другие придерживаются мнения, что ваши доменные объекты неявно реализуют интерфейс «сохраняемости» (да, я знаю, что я растягиваюсь здесь). Поэтому хорошо иметь различные saveи deleteт. Д. Методы на них. Это рассматривается как прагматичный подход, и многие технологии ORM (JPA в моем счастливом мире Java) работают с объектами таким образом.

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

НТН!

Мартейн Вербург
источник
2

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

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

Тодд смит
источник
Похоже, что конкретное требование - повторное использование структуры данных (стресс-данные) - приводит к тому, что эта общая часть сводится к простым DTO.
Виктор Сергиенко
Анемичные модели позволяют лучше использовать повторно? Больше похоже на DTO, и мне лично наплевать на повторное использование определений свойств. Я бы предпочел повторно использовать поведение.
Энди
@ Энди - я бы согласился, однако, если ваше поведение находится в доменных службах и они работают с анемичными объектами (хорошо, DTO, если хотите), тогда не увеличивает ли это повторное использование этих поведений? Просто играю адвоката дьявола.
Jpierson
@jpierson Я обнаружил, что поведение, как правило, специфично для конкретного случая использования. Если есть повторное использование, оно будет принадлежать другому классу, но потребитель не будет использовать эти классы, он будет использовать те, которые характерны для варианта использования. Так что любое повторное использование, так сказать, "за кадром". Кроме того, попытка многократного использования моделей обычно затрудняет использование моделей потребителем, поэтому вы в конечном итоге создаете модели просмотра / редактирования на уровне пользовательского интерфейса. Часто вы в конечном итоге нарушаете DRY, чтобы обеспечить более богатый пользовательский опыт (например, DataAnnotations из редактируемых моделей).
Энди
1
Я бы предпочел доменные модели, построенные для варианта использования, повторно используемые там, где это имеет смысл (то есть повторное использование может быть выполнено без значительного или полного изменения поведения). Таким образом, вместо анемичной модели предметной области, класса обслуживания и модели редактирования у вас есть единая интеллектуальная редактируемая модель предметной области. Гораздо проще в использовании и обслуживании, я обнаружил.
Энди
2

Этот вопрос задавался давным-давно, но он помечен как Domain Driven Design. Я думаю, что сам вопрос содержит фундаментальное недопонимание всей практики, и ответы, включая принятый ответ, увековечивают фундаментальное недопонимание.

В архитектуре DDD нет «модели предметной области».

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

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

Другой домен - управление запасами для обувной компании. Система управляет товарно-материальными запасами, начиная с момента их доставки от производителя во Франции, до распределительных центров в континентальной части США, в розничные магазины на местных рынках и, наконец, до покупателя, который покупает обувь в розничной торговле.

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

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

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

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

RibaldEddie
источник