До сих пор не утихают холивары на тему DDD\rich vs anemic. С одной стороны апологеты DDD (domain driven design, дизайн на основе предметной области) твердят о том как это круто, с другой стороны говоря что оно не везде подходит. На вопрос где же оно подходит обычно затрудняются ответить или отвечают “for compex domain”, причем примеров применения такого встретить непросто.

 

Попробуем разобраться. Если отбросить всю философскую шелуху DDD, то придем к очень простой концепции жирной (rich, насыщенной) модели, описанной Фаулером. С одной стороны Фаулер предлагает поместить логику в классы “сущностей”, соответствующие данным предметной области. С другой стороны он прекрасно понимает что логика будет сложна и надо каким-то образом декомпозировать её. Кроме того есть логика, которая оперирует более чем одной сущностью и поместить её в один из классов сущностей не выгодно. Таким образом создаются классы сервисов, стратегий итп. Их всех объединяет свойство, что они не содержат данные предметной области и для работы обращаются к классам сущностей. По сути вся сложная логика располагается в этих самых сервисах.

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

Тут стоит отступить назад и посмотреть на общую картину. Как выглядит код приложения, использующий жирную модель (на основе модели в статье Фаулера):

class ContractsController
{
    public Result CalculateRecognitionsAction(int contractId)
    {
        var contract = repository.GetById(contractId);
        contract.CalculateRecognitions();
        repository.Save();
    }
}

Contract.CalculateRecognitions свою очередь выглядит как-то так:

public void CalculateRecognitions()
{
    this.Product.CalculateRecognitions(this);
}

А Product.CalculateRecognitions выглядит так:

public void CalculateRecognitions(Contract c)
{
    var recognitions = recognitionService.CalculateRecognitions(this);
    c.SetRecognitions(recognitions);
}

Теперь попробуем выполнить простое преобразование: на верхнем уровне будем вызывать сервис, а не методы сущностей.

class ContractsController
{
    public Result CalculateRecognitionsAction(int contractId)
    {
        var p = repository.GetProductByContractId(contractId);
        var recognitions = recognitionsService.CalculateRecognitions(p);
        repository.SaveRecognitionsForContract(contractId, recognitions);
    }
}

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

Сдвиг предмета моделирования

Если посмотреть какие концептуальные изменения произошли в коде в примере выше, то становится понятно что центральным объектом у нас стал RecognitionsService, а не Contract. То есть вместо модели исходных данных (предметной области) задачи мы начали использовать модель решения этой задачи. Модели исходных данных никуда не делась, просто её роль в решении стала гораздо меньше. Такая модель называется стройной (anemic).

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

Таким образом DDD не подходит для сложных задач.

Лирическое отступление

Если вы думаете что вынося всю логику из класса Product в ProductService\ProductHelper\ProductManager вы получаете модель решения, то вы жестоко ошибаетесь. Фактически это будет DDD, только в худшем его проявлении.

Сложные предметные области

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

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

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

И снова такое решение гораздо боле гибкое,чем жирная модель. Эти же валидаторы можно приспособить для валидации ввода пользователя, для возвращения ему осмысленных ошибок, а не исключений. Кроме того можно предусмотреть сохранение “черновиков” во внешнем хранилище, которые не проходят всех правил.

Так где же область применимости DDD?

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

Например взять блог. Обычный блог как например этот. Моделью предметной области являются: блог, посты, комменты, страницы, виджеты в интерфейсе. Решение включает в себя функции: создать пост, изменить пост, удалить пост, получить список, поменять настройки блога. Все эти функции вполне можно поместить в класс Блога, а умный ORM разберется как потом все изменения положить в БД.

Но почему DDD столь популярен?

Именно потому что DDD хорошо работает на простых примерах. Ведь в книгах и презентациях невозможно привести пример на 20kloc. А на примерах в 100 строк DDD выглядит очевидно и очень привлекательно.

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

Заключение

Не стоит слепо следовать DDD. У догматичного DDD очень узкая область применения. Старайтесь моделировать решение задачи, не зацикливайтесь на моделировании исходных данных. Всегда оценивайте как то или иное решение повлияет на качество кода безотносительно DDD, ООП или других баззвордов.

Теги : архитектура, DDD