В недавнем выпуске подкаста DotNet&More мы обсуждали с Максимом Аршиновым его предстоящий доклад на Московский .Next "Блеск и нищета предметной модели". С позицией Максима можно будет легко познакомиться непосредственно на конференции. И, в качестве дополнения, я бы хотел рассмотреть видение великого спора Анемичная VS "Богатая" ("Насыщенная") доменные модели через призму некогда популярных GRASP шаблонов
Дискуссии идут достаточно давно, например:
- Anemic Domain Model [Перевод]
- [Перевод] Анемичная модель предметной области — не анти-шаблон, а архитектура по принципам SOLID
Судя по 190 комментариями к последней статье, данная тема не теряет актуальности.
Прежде чем начать разбор, хотелось бы прояснить предмет спора. Основное отличие анемичной модели от богатой в том, что она не содержит бизнес логику в теле класса. Частным примером анемичной модели может быть всем известный паттерн DTO.
"Богатая" модель, в свою очередь, может содержать логику, описывающую бизнес правила, функции и т.д. Обратите внимание, что мы рассматриваем именно методы, отражающие бизнес логику. ToString, GetHashCode и прочие "технические" части классов не входят в предмет обсуждения, потому и игнорируются.
GRASP
Как и отмечалось в начале статьи, рассматривать данный вопрос мы будем в контексте GRASP паттернов. Данный набор шаблонов был представлен в книге Крэга Лармана «Применение UML и шаблонов проектирования» и сильно повлиял на современное программирование, например, правила Low Coupling/High Cohesion были объявлены именно в GRASP.
Для людей, знакомых с GoF паттернами данный набор шаблонов может показаться слишком размытым. Все дело в том, что GRASP паттерны сфокусированы не на решении прикладных задач, а на распределении ответственности за те или иные действия и операции между объектами:
- Creator отвечает за создание объектов
- Controller отвечает за операции от пользователей
- Indirection отвечает за организацию слабого зацепления между объектами
И так далее.
В контексте данной статьи хотелось бы сфокусироваться на следующих паттернах:
- Information Expert (Информационный эксперт)
- Pure Fabrication (Чистая выдумка)
Information Expert (Информационный эксперт)
Проблема: Каков базовый принцип распределения обязанностей между объектами?
Решение: Назначить эту обязанность тому классу, который обладает достаточной информацией для ее выполнения.
Другими словами:
Ответственность должна быть назначена тому, кто владеет максимумом необходимой информации для исполнения — информационному эксперту.
В контексте C#: метод стоит объявлять в том классе, поля и свойства которого используются в этом методе.
Например, если для расчета суммы долга вся необходимая информация есть у сущности должника (сумма, время выдачи кредита), то именно в этой сущности и стоит объявить соответствующий метод.
Конечно, данное упрощение несколько чрезмерно, но основной смысл должен быть понятен.
Pure Fabrication (Чистая выдумка)
Проблема: Какой класс должен обеспечить реализацию High Cohesion и Low Coupling, если шаблон Information Expert не обеспечивает подходящего решения.
Решение: Присвоить группу обязанностей с высокой степенью зацепления искусственному классу, не представляющему конкретного понятия предметной области.
Другими словами:
Если однозначно выбрать подходящую сущность предметной модели, которой можно было бы назначить ответственность, не удается, необходимо создать синтетический класс, не существующий в предметной области.
В контексте C#: если реализация метода предусматривает равномерное использование нескольких сущностей или внешних зависимостей, то данный метод стоит объявить в отдельном классе. И эти классы называют сервисами.
Например, если для расчета суммы долга необходимо знать не только атрибуты должника, но и параметры банка (процентная ставка, допустимый период просрочки), то стоит создать отдельный сервис, содержащий соответствующий метод.
И что это все значит?
В контексте вышесказанного, можно выделить достаточно простой алгоритм, помогающий принять решение, располагать метод в модели или в сервисе:
- Если метод использует только свойства модели, его стоит объявить в модели
- Если метод использует, по большей части, свойства одной модели и лишь частично другой, его можно объявить в модели
- Если метод равномерно использует свойства нескольких моделей, то стоит вынести его в отдельный или существующий сервис
- Если метод использует внешнюю зависимость, например Репозиторий, его стоит вынести в отдельный или существующий сервис
Резюме
GRASP паттерны нельзя рассматривать как истину в последней инстанции. Но в они могут помочь легко понять, какому объекту назначить то или иное поведение. Соответственно, спор анемичная или "богатая" модель может не иметь смысла, так как решение принимается в каждом конкретном случае, в зависимости от модели предметной области и поведения, связанного с ней. В процессе дизайна системы будут появляться как сущности с поведением, так и без него. И это вполне корректно.
Комментарии (5)
emacsway
30.10.2019 16:02+1Автору спасибо за статью, — интересная точка зрения.
то данный метод стоит объявить в отдельном классе. И эти классы называют сервисами.
Стоит добавить, что Сервисы бывают трех уровней: Application, Domain и Infrastructure. В данном примере Вы говорите именно о Доменном Сервисе. Ограниченный список оснований для создания Доменных Сервисов хорошо выразил Vaughn Vernon в «Implementing Domain-Driven Design».
FanatPHP
Why it is shown in the English language feed?
KAW Автор
Sorry, misclick.