Про Anemic Domain Model

Время от времени в кругу моих знакомых регулярно поднимается вопрос о том, что Anemic Domain Model - никакой вовсе и не антипаттерн, и в качестве аргументов приводятся ссылки на статью “The Anaemic Domain Model is no Anti-Pattern, it’s a SOLID design” [1]. После очередного упоминания этой статьи я решил об этом написать.

Я не знаю, читал ли автор той статьи весь список перечисленной внизу нее литературы, потому что в этом списке присутствует книга “Martin, Robert C. Agile software development: principles, patterns, and practices. Prentice Hall PTR, 2003”.

А эта книга дает, на мой взгляд, лучшее представление о том, что делают методы объекта: они Внедряют Зависимости (Dependency Injection), что делает полиморфизм возможным! Я всем советую сперва прочесть эту книгу (именно книгу в оригинале, а не краткие выдержки из Википедии), прежде чем читать указанную статью. Как минимум, просмотрите хотя бы этот видеоролик: “Bob Martin SOLID Principles of Object Oriented and Agile Design”.

Я не думаю, что исключение внедрения зависимостей (Dependency Injection) на уровне объекта будет соответствовать пятому принципу “D” в SOLID, а лишение объекта полиморфизма (да еще и в условиях отсутствия Множественной Диспетчеризации) будет соответствовать третьему принципу “L” в SOLID. В таком случае внедрять зависимости и обеспечивать полиморфизм придется вручную, фактически превращая программу из объектно-ориентированной в процедурную.

Нужно заметить, что на этом месте многие начинают говорить о превосходствах функционального программирования, не понимая отличий межу функциональным программированием и процедурным. Если говорить о превосходствах функционального программирования, то Роберт Мартин уже все сказал в статье “OO vs FP”.

Нужно отличать Anemic Domain Model в объектно-ориентированной парадигме от Date Type в функциональной парадигме. Вот здесь, например, сам Eric Evans говорит о том, что в своей книге “Domain-Driven Design” он не рассматривал глубоко функциональную парадигму, потому что в 2003 она не имела такого применения как сегодня. А сегодня, в контексте Event Sourcing, она имеет уже совсем другое значение.

You know, functional is a big thing. Maybe more than one thing. And so there are people though who have been talking about modeling in the functional realm and very interesting things. The things is models are just systems of abstraction. And so you have a powerful mechanism for abstraction. You should be able to implement, you should be able to express models. Furthermore, if you want to, you know, bring that ubiquitous language to life in the code, well, some of the functional languages, I think, are really nice for making making language in the code. And it might be a good mate with Event Sourcing. I’m just sort of laying out like I’m pointing out that we have so many options. Options that were really not there in 2003.

- Eric Evans, “Tackling Complexity in the Heart of Software”, Domain-Driven Design Europe 2016 - Brussels, January 26-29, 2016

Здесь он возвращается к этому вопросу. А здесь Greg Young рассматривает переход от OOP к Functional Programming в Event Sourcing.

Под Anemic Domain Model же понимается вырождение поведения модели именно в объектно-ориентированной парадигме, т.е. использование объектно-ориентированных языков в процедурном стиле.

Но вернемся к обсуждаемой статье. Я так и не смог найти имя автора той статьи на том сайте. Нечего и говорить, что это - весомый аргумент для авторитета статьи, которая с легкостью берется опровергать статью Мартина Фаулера(!) “Anemic Domain Model”. Создается впечатление, что подобные статьи - просто способ привлечь внимание к ресурсу, используя общественную резонансность скандальных утверждений.

Автор демонстрирует отсутствие четкого понимания различий между:

  1. Логикой уровня приложения
  2. Бизнес-логикой (причем, следует отличать предметно-ориентированную бизнес-логику от бизнес-логики, зависящей от приложения)
  3. Обязанностью доступа к данным (что не является бизнес-логикой), иногда именуемой слоем данных

Указанная статья целиком построена на ошибках проектирования. В своем примере он рассматривает вместо бизнес-логики - обязанность доступа к данным (да еще и в виде Active Record(!)). Жаль, что в его списке литературы нет книги “Clean Code” by Robert C. Martin, в которой рассказывается, как для разделения служебной логики и бизнес-логики вот уже более 10 лет используется Cross-Cutting Concerns.

Уникальная способность автора всунуть в доменную модель обязанности слоя доступа к данным, - это, действительно, достаточно весомый аргумент для того, чтобы автор не наделял доменную модель вообще никакими обязанностями. К тому же Service Layer относится к Application Logic, т.е. имеет политику более низкого уровня, нежели Domain Logic. А у Domain Service есть ограниченный список причин для своего существования.

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

В целом, основной мотив сторонников Anemic Domain Model сводится к тому, что, поскольку они не умеют обеспечить разделение реализаций служебной Логики Доступа к Данным и Бизнес-Логики Доменной Модели, т.е. отделить разного рода (Инфраструктурные) Сервисы от Доменной Модели, то, поэтому, они предлагают вынести всю Бизнес-Логику из Доменной Модели к служебной логике в Сервисы. Ну... хорошо... а что это решает? В Сервисах их разделять не нужно? Это же все-таки политики разного уровня... Получаются те же яйца, только в процедурном стиле. От перестановки мест слагаемых проблема не решается.

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

Все что не относится к логике предметной области, - это новая обязанность, которая должна быть вынесена за пределы Domain Model, или, по крайней мере, не рассматриваться как бизнес-логика, если Domain Model реализована в виде паттерна Active Record (как в той статье).

Очень часто можно наблюдать разбухшие модели, которые выполняют очень много несвойственных ее предметной области обязанностей, в т.ч. и уровня приложения (управление транзакциями, проверка привилегий и т.п.). Domain Model должна моделировать только поведение объекта предметной области (реального мира). Если Domain Model имеет несколько десятков методов, которые не выражают поведение объекта реального мира, не имеют общего применения, а используются только одним клиентом, то мы должны их разместить либо непосредственно внутри клиента, либо в классе, который обслуживает клиента (для обслуживания клиентов уровня приложения существует Sevice Layer, для обслуживания клиентов уровня предметной области и выравнивания интерфейсов существует паттерн Wrapper). Более подробно эта тема уже рассматривалась в статье “Проектирование Сервисного Слоя и Логики Приложения”.

Еще одной частой причиной порождения Anemic Domain Model является недостаточное использование Domain Event, либо некорректная его реализация.

Domain Model может быть представлена в виде агрегата, т.е. композиции связанных объектов, что характерно для DDD и NoSQL. Domain Model может иметь методы, изменяющие ее состояние или композицию, но она не должна заботиться о сохранении этой композиции в базе данных. Предположим, Вы осознали что NoSQL-хранилище Вам подходит лучше, чем RDBMS, и решили заменить реализацию класса ответственного за сохранение объекта. С точки зрения архитектуры, база данных - это IO-устройство, от которого приложение стремится быть независимым. NoSQL хранилища построены вокруг идеи агрегата, что позволяет, в определенной мере, избавиться от реляционных связей и упростить шардинг. Границами транзакции NoSQL-хранилища являются границы агрегата. Если у Вас детали реализации сохранения агрегата скрыты за интерфейсом ответственного за это объекта (обычно это Repository + DataMapper), то такой рефакторинг не затронет реализацию самой Доменной Модели. Если Вы вынуждены изменять реализацию Domain Model, то это значит, что Ваша программа не имеет независимости от IO-устройства, что нарушает Single Responsibility Principle (в виде Code Smell “Shotgun Surgery”).

Иногда случается, что Бизнес-Логика Доменной Модели нуждается в доступе к экземпляру связанной Доменной Модели, или даже в доступе к корню другого Агрегата. Недостаточное понимание способов разделения политики разных уровней (Бизнес-Логики и Логики Доступа к Данным) часто приводит к оправданию Anemic Domain Model. Между тем, существует целый ряд способов решения этой проблемы.

Эта тема уже затрагивалась в статьях:

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

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

Автор просто пишет о том, как писать процедурные программы в Объектно-Ориентированных языках.

Попробуйте реализовать в таком стиле паттерн Class Table Inheritance для коллекции полиморфных объектов с достаточно богатой бизнес-логикой, и вы поймете все недостатки Anemic Domain Model. То же самое справедливо и к случаю использования паттерна Special Case, известного также как метод рефакторинга Introduce Null Object. Смотрите также Replace Conditional with Polymorphism, Replace Type Code With Polymorphism и Replace Type Code with State/Strategy (желательно смотреть информацию в книге, номер страницы указан на страницах онлайн-каталога по ссылкам).

Footnotes

[1]“The Anaemic Domain Model is no Anti-Pattern, it’s a SOLID design” https://blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain-model-is-no-anti-pattern-its-a-solid-design/ (перевод на русский “Анемичная модель предметной области — не анти-шаблон, а архитектура по принципам SOLID” https://habrahabr.ru/post/346016/ )
[2]“Patterns of Enterprise Application Architecture” by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford

Updated on Nov 16, 2019

Comments

comments powered by Disqus