Привет! Меня зовут Станислав, я фронтенд-разработчик компании Тинькофф. Занимаюсь разработкой веб-приложений и написал десятки тысяч строк кода, массу велосипедов и костылей, пока не познакомился с разработкой, основанной на модели предметной области, или Domain-Driven Design.
DDD — это система знаний, приемов и методов, предназначенная для создания приложений высокой сложности. DDD обобщает лучшие практики коммерческой разработки программного обеспечения и постоянно совершенствуется, предоставляя разработчику надежную опору для принятия решений. Лучший способ узнать больше — это книга Эрика Эванса Domain-Driven Design: Tackling Complexity in the Heart of Software.
Размышляя о своих прошлых и текущих проектах, попробую перечислить их проблемы, которые могут быть неочевидны до определенного момента развития приложения, и то, как с ними можно справиться, используя базовые принципы DDD.
Бизнес-логика повсюду
Может показаться, что алгоритм решения предметной проблемы лучше и быстрее имплементировать прямо в месте применения: это может быть UI-компонент, часть инфраструктуры фреймворка, метод обращения к API другой системы.
Представим, что, разрабатывая интернет-магазин, мы реализуем форму заказа обратного звонка по номеру телефона и форму для регистрации пользователя. При этом необходимо соблюсти бизнес-правило о приеме только отечественных телефонных номеров.
Задачи делаем в разное время, в проекте заняты несколько бэкенд- и фронтенд-разработчиков.
Если бизнес-правило будет решено в компонентах форм и отдельно в каждом методе бэкенда, мы получим четыре дубликата имплементации бизнес-правила о допустимых телефонных номерах.
Такой подход приводит к потере целостного представления о бизнес-логике, модели начинают неконтролируемо дублироваться и противоречить друг другу, при чтении кода становится сложно анализировать реализацию, есть риск не учесть какой-нибудь костыль, припрятанный в самом неожиданном месте.
В DDD внимание уделяется модели предметной области как в разрабатываемом коде, так и в представлении участников команды. Модель формирует реализацию приложения, которая является ее неотъемлемой частью. Модель предметной области должна содержать необходимые знания о предмете, быть чистой и согласованной.
Для разделения различных аспектов приложения с целью их тщательной проработки наиболее широко применяется многослойная архитектура: отдельные функции программы располагаются в логических слоях и каждый элемент любого слоя может зависеть только от элементов нижележащих слоев и от элементов своего слоя. Часто выделяют такой набор слоев:
Элементы пользовательского интерфейса могут зависеть от элементов трех нижних слоев и от прочих элементов UI, а вот программные компоненты инфраструктурного слоя могут зависеть только от других инфраструктурных компонентов. Состав слоев может отличаться, но для DDD критически важно обособление слоя бизнес-логики.
План такой:
Разделить приложение на логические слои.
Спроектировать слои так, чтобы каждый верхний слой мог зависеть только от нижележащих.
Сосредоточьте весь код бизнес-логики в одном слое.
Изолируйте слой бизнес-логики от остальных слоев.
Это позволит свободно разрабатывать чистую модель предметной области — достаточно большую, чтобы имплементировать необходимую для работы приложения бизнес-логику.
Теперь, практикуя DDD и заботясь об изоляции предметной области, разработчики имплементировали метод валидации телефонных номеров в специальном объекте в доменном слое и обращаются к этому методу в случаях использования из операционного слоя.
Теперь, если интернет-магазин расширит страны присутствия и потребуется расширить правило валидации, нужно будет изменить всего один класс.
Нет разделения на контексты
Проектирование универсальных моделей, которые пытаются решать сразу все задачи, для всех пользователей системы, ведет к тому, что слишком сложная модель становится внутренне противоречивой и хрупкой. Это происходит из-за того, что нет ограничивающих контекстов. Например, изменения отдела снабжения могут задеть функциональность промостраницы или личного кабинета пользователя.
Модель, созданная для первого контакта с пользователем «Клиент», кроме телефонного номера, ФИО, адреса и даты создания оказалась очень удобной для агрегации и других данных для работы с этим пользователем. Разработчики снабдили ее списком рекомендаций, историей покупок, управлением бонусами и историей просмотров.
По ходу усложнения модели пользователя требовалась не только все большая производительность серверов, но и масса труда разработчиков для разрешения конфликтов слияния, борьбы с циклическими зависимостями и чтения огромного количества кода, относящегося почти ко всем сферам деятельности предприятия.
Ограниченный контекст — принцип DDD, согласно которому следует логически разделять приложение на контексты, каждый из которых должен решать задачи соответствующего поддомена предметной области.
Предметно-ориентированное проектирование предлагает ряд методов для разделения сложной предметной области на поддомены.
Разделение может проходить по границам деятельности различных ролей пользователей. Также полезным ориентиром будут отдельные бизнес-процессы, для которых разрабатывается ПО или организационная структура предприятия. Наглядный способ выявления разных контекстов — проверка изменения определения одного и того же слова в разных контекстах. Например, договор в контексте торгового отдела имеет совершенно иное определение, чем в контексте отдела кадров.
Выбирайте способ разделения или комбинируйте их с целью получения внутренне связанных контекстов предметной области с малой их взаимной зависимостью между собой. Проектируйте модели каждого из контекстов отдельно: это поможет избежать появления чрезмерно усложненных моделей и лишних связей между контекстами.
Например, после рефакторинга приложение разделили на несколько контекстов: онбординг, магазин, лояльность и доставка. Основной сущностью стал клиент онбординга, в остальных контекстах были созданы модели клиент, содержащие только необходимое им, и дополнительно — идентификатор для связывания с основной сущностью.
Не поддерживается единый язык
В работе случаются проекты с многозначными терминами, жаргоном, неологизмами и массой синонимов при описании модели. Это не только усложняет онбординг новых разработчиков, но и крадет время членов команды на уточнение верного смысла. Приходится постоянно конкретизировать значения, выяснять и запоминать отношения между синонимами.
Например, бизнес-аналитик обсуждает реализацию задачи с новым разработчиком:
— А здесь мы выводим список торговых точек.
— Но у нас нет таких данных.
— Посмотри Shops.
— Нет.
— Markets?
— Нет, вот только какие-то Opportunity Points.
— Да, это то, что нужно!
— ?!
В DDD единый язык — основа для построения моделей в ограниченном контексте, средство коммуникации и трансфера знаний. Он может состоять из наименований классов и операций, включать в себя терминологию для обсуждения правил, которые должны быть явно реализованы в модели. И, конечно, этот язык дополняется наименованиями паттернов, применяемых командой в этой доменной модели.
Единый язык требует осознанного поддержания участниками разработки, актуализации и рефакторинга.
Используйте модель в качестве основы языка. Помогайте другим членам команды использовать этот язык в коммуникациях и коде. Когда находятся лучшие слова и выражения для описания модели, проводите рефакторинг кода, переименуйте классы и методы для соответствия. Устраняйте неоднозначность терминов в обсуждениях. Помните, что изменение в едином языке — это и изменение в модели.
Похоже, что герои примера уже нашли способ повысить эффективность своей работы. Будем надеяться, что они найдут единый язык и смогут решить все стоящие перед ними задачи.
Заключение
Для меня DDD определенно работает, и я рекомендую использовать этот подход. Но опыт любого разработчика уникален, и мне хотелось бы знать, есть ли подобные проблемы у ваших проектов? Работает ли DDD в вашем случае? Напишите, пожалуйста, в комментарии.
dexie
Ой-ёй. Диаграмма направления зависимостей неверная. Бизнес логика и UI компоненты не могут зависеть от инфраструктуры. Слой инфраструктуры должен распологаться на одном уровне с UI компонентами.
По теме - единый доменный язык это, наверное, главное что нужно брать из DDD. Остальное - детали имплементации.
dream_designer
Еще поддомены и ограниченные контексты. А остальное, да, детали.
QhighTower Автор
Инфраструктурный слой предоставляет общие технические возможности, поддерживающие более высокие уровни, у Эванса это может быть например обмен сообщениями в приложении, хранение данных для домена, утилиты UI.
Хороший пример библиотеки инфраструктурного уровня на мой взгляд, это различные реактивные расширения, например RxJS
dexie
Если ваша бизнес-логика зависит от RxJS (или, например, MediatR, Rx.NET), то у вас, скорее всего, проблемы с архитектурой приложения. Простыми словами - ваших БА интересует какая там версия у Rx.NET новая вышла и надо ли ее обновить, или стори они пишут под RxJS “BehaviorSubject ‘user’ should return current user”? Зависимости на инфраструктуру для бизнес-слоя нужно разворачивать через интерфейсы, чтобы инфраструктура имплементила domain интерфейсы.
onets
Я конечно понимаю, что это отсылка к луковой архитектуре, но всегда есть хотя бы одно но.
Так-то если развить эту идею - у всех бизнес логика зависит от такой инфраструктуры как .net framework или Java.core или что там из модного и современного.
dopusteam
Вообще нет, если упороться, то бизнес логика вообще не зависит от фреймворка)
onets
Ну так то-то да, но мы же ее перекладываем в виде классов, методов и функций.
dopusteam
Этого я уже не понял :think:
dream_designer
Так как раз-таки сама суть луковой/гексагональной/чистой (выбрать нужное) архитектуры состоит в том, чтобы бизнес-логика не зависела от инфраструктурного слоя. Без всяких но.
QhighTower Автор
Согласен, даже просто язык программирования может рассматриваться как один из инфраструктурных элементов в разработке программного обеспечения.