Привет! Меня зовут Станислав, я фронтенд-разработчик компании Тинькофф. Занимаюсь разработкой веб-приложений и написал десятки тысяч строк кода, массу велосипедов и костылей, пока не познакомился с разработкой, основанной на модели предметной области, или Domain-Driven Design.

DDD — это система знаний, приемов и методов, предназначенная для создания приложений высокой сложности. DDD обобщает лучшие практики коммерческой разработки программного обеспечения и постоянно совершенствуется, предоставляя разработчику надежную опору для принятия решений. Лучший способ узнать больше — это книга Эрика Эванса Domain-Driven Design: Tackling Complexity in the Heart of Software.

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

Бизнес-логика повсюду

Может показаться, что алгоритм решения предметной проблемы лучше и быстрее имплементировать прямо в месте применения: это может быть UI-компонент, часть инфраструктуры фреймворка, метод обращения к API другой системы.

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

Задачи делаем в разное время, в проекте заняты несколько бэкенд- и фронтенд-разработчиков. 

Если бизнес-правило будет решено в компонентах форм и отдельно в каждом методе бэкенда, мы получим четыре дубликата имплементации бизнес-правила о допустимых телефонных номерах.

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

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

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

 

 

Элементы пользовательского интерфейса могут зависеть от элементов трех нижних слоев и от прочих элементов UI, а вот программные компоненты инфраструктурного слоя могут зависеть только от других инфраструктурных компонентов. Состав слоев может отличаться, но для DDD критически важно обособление слоя бизнес-логики.

План такой:

  1. Разделить приложение на логические слои.

  2. Спроектировать слои так, чтобы каждый верхний слой мог зависеть только от нижележащих.

  3. Сосредоточьте весь код бизнес-логики в одном слое.

  4. Изолируйте слой бизнес-логики от остальных слоев.

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

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

Теперь, если интернет-магазин расширит страны присутствия и потребуется расширить правило валидации, нужно будет изменить всего один класс. 

Нет разделения на контексты

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

Модель, созданная для первого контакта с пользователем «Клиент», кроме телефонного номера, ФИО, адреса и даты создания оказалась очень удобной для агрегации и других данных для работы с этим пользователем. Разработчики снабдили ее списком рекомендаций, историей покупок, управлением бонусами и историей просмотров. 

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

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

Предметно-ориентированное проектирование предлагает ряд методов для разделения сложной предметной области на поддомены.

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

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

Например, после рефакторинга приложение разделили на несколько контекстов: онбординг, магазин, лояльность и доставка. Основной сущностью стал клиент онбординга, в остальных контекстах были созданы модели клиент, содержащие только необходимое им, и дополнительно — идентификатор для связывания с основной сущностью.

Не поддерживается единый язык

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

Например, бизнес-аналитик обсуждает реализацию задачи с новым разработчиком:

— А здесь мы выводим список торговых точек.

— Но у нас нет таких данных.

— Посмотри Shops.

— Нет.

— Markets?

— Нет, вот только какие-то Opportunity Points.

— Да, это то, что нужно!

— ?!

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

Единый язык требует осознанного поддержания участниками разработки, актуализации и рефакторинга. 

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

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

Заключение

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

Комментарии (10)


  1. dexie
    14.08.2023 09:59
    +1

    Ой-ёй. Диаграмма направления зависимостей неверная. Бизнес логика и UI компоненты не могут зависеть от инфраструктуры. Слой инфраструктуры должен распологаться на одном уровне с UI компонентами.

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


    1. dream_designer
      14.08.2023 09:59

      Еще поддомены и ограниченные контексты. А остальное, да, детали.


    1. QhighTower Автор
      14.08.2023 09:59
      -2

      Инфраструктурный слой предоставляет общие технические возможности, поддерживающие более высокие уровни, у Эванса это может быть например обмен сообщениями в приложении, хранение данных для домена, утилиты UI.
      Хороший пример библиотеки инфраструктурного уровня на мой взгляд, это различные реактивные расширения, например RxJS


      1. dexie
        14.08.2023 09:59
        +3

        Если ваша бизнес-логика зависит от RxJS (или, например, MediatR, Rx.NET), то у вас, скорее всего, проблемы с архитектурой приложения. Простыми словами - ваших БА интересует какая там версия у Rx.NET новая вышла и надо ли ее обновить, или стори они пишут под RxJS “BehaviorSubject ‘user’ should return current user”? Зависимости на инфраструктуру для бизнес-слоя нужно разворачивать через интерфейсы, чтобы инфраструктура имплементила domain интерфейсы.


        1. onets
          14.08.2023 09:59
          +1

          Я конечно понимаю, что это отсылка к луковой архитектуре, но всегда есть хотя бы одно но.

          Так-то если развить эту идею - у всех бизнес логика зависит от такой инфраструктуры как .net framework или Java.core или что там из модного и современного.


          1. dopusteam
            14.08.2023 09:59
            +1

            Вообще нет, если упороться, то бизнес логика вообще не зависит от фреймворка)


            1. onets
              14.08.2023 09:59

              Ну так то-то да, но мы же ее перекладываем в виде классов, методов и функций.


              1. dopusteam
                14.08.2023 09:59

                Этого я уже не понял :think:


          1. dream_designer
            14.08.2023 09:59

            Так как раз-таки сама суть луковой/гексагональной/чистой (выбрать нужное) архитектуры состоит в том, чтобы бизнес-логика не зависела от инфраструктурного слоя. Без всяких но.


          1. QhighTower Автор
            14.08.2023 09:59

            Согласен, даже просто язык программирования может рассматриваться как один из инфраструктурных элементов в разработке программного обеспечения.