Источник: Блог Сергея Теплякова.

Date: 2019-09-08


… и рецензия книги Эрика Эванса “Domain-Driven Design: Tackling Complexity in the Heart of Software”

Хороший дизайн приложения является главной основой любого приложения. Чрезмерная сложность дизайна приводит к увеличению стоимости изменений и некоторой «хаотичности» этого процесса. Чрезмерная простота – оставляет бреши в полученных моделях, в результате чего приложение может не выполнять тех функций, ради которых оно разрабатывается.

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

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

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

ПРИМЕЧАНИЕ Во многих проектах существует папка Domain, в которой расположены объекты-данные, папка DAL с набором репозиториев и папка Services, с набором сервисов. Если вы считаете, что это и есть DDD, то у меня плохие новости, все это, хоть и имеет некоторое отношение к DDD, не гарантирует наличие реальной доменной модели в вашем приложении.

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

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

Если вам знакома такая картина, то поздравляю, вы уже используете две ключевые концепции DDD: Model-Driven Design (Проектирование по модели) и Ubiquitous Language (Единый язык).

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

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

Если и это отвечает вашим подходам к разработке, то вы знаете о другом фундаментальном принципе DDD: Supple Design (Гибкий дизайн), который опирается на трех китов – Intention-Revealing Interfaces (Информативный интерфейс), Assertions (Утверждения a.k.a Контракты) и Side-Effect-Free Functions (Функции без побочных эффектов).

ЦИТАТАДавайте классам и методам имена, описывающие их цель и результат, не заостряя внимание на то, как они выполняют свои обещания. Это освободит разработчиков от необходимости понимать внутренности реализации. Эти имена должны соответствовать ЕДИНОМУ ЯЗЫКУ, что позволит членам команды быстро понять их значение.

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

В этом плане вам может очень помочь такое понятие из словаря DDD, как Bounded Context (Ограниченный контекст), с набором вспомогательных паттернов, которые к этому времени вы уже наверняка изобрели и сами.

В зависимости от задачи вы можете остановиться на общем «ядре», которое затем может использоваться по-разному в клиентском и серверном коде (Core Domain – Смысловое ядро). Если же эти модели находятся под контролем разных команд, то вы можете сделать разные модели и построить между ними простой слой трансформации (Translation Layer – Слой преобразования). Если же разные модели располагаются в разных системах, причем одна из систем была старой или написанной левой ногой, то вы можете построить между ними жесткий «фасадный» слой, придя, таким образом, еще к одному паттерну – Anticorruption Layer (Предохранительный слой).

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