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

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

Проблема ранней изоляции: -

Необходимость частого рефакторинга на раннем этапе

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

Гибридный подход: - Функциональный домен, императивная оболочка.

Домен — функциональное ядро: Бизнес-логика в чистых функциях, независимых от инфраструктуры.

Оболочка — императивная. Необходимость интеграции с ООП библиотеками (io, фреймворки) заставляет писать эту часть в ООП. Но можно использовать тонкие прослойки для интеграции для минимизации boilerplate.

Риски: - Скрытая привязка к библиотеке: Домен косвенно зависит от её типов. - Сложность замены: Если библиотека всё же потребует замены, придётся модифицировать методы расширения.

Итог: Ранняя изоляция через толстые адаптеры часто избыточна.

Методика

Как же тогда должна выглядеть методика обследования и проектирования на основе DDD, но оставляя гибкими границы доменов по bounded context до нужного момента?

Методика пригодна как для проектирования архитектуры доменных компонент, так и более мелкого уровня детализации (структуры данных и функции).

Определения (мои)

  1. Состояние - наблюдаемое явление, фиксированный набор значений параметров системы или её части в определённый момент времени, определяющий её поведение и доступные действия.

  2. Типы → Категории возможных состояний системы и их допустимых комбинаций.

  3. События → Факты о произошедших изменениях (прошлые состояния).

  4. Процессы → Механизмы перехода между состояниями (будущие изменения состояний).

Последовательность проектирования компонент (домена DDD либо более мелких сущностей):

1. Анализ домена:

- Event Storming → выявление ключевых событий и процессов.

- Определение Bounded Context → границы модулей. Домен должен быть такой чтобы помещался а голове одного доменного эксперта. Если говорить про модули кода, то принцип единственной ответственности не позволит вырасти когнитивной сложности компонента, так как не смешивает разные предметные области в одном коде.

2. Моделирование типами:

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

- Бизнес-правила → валидаторы как функции.

3. Чистые функции для логики:

  • Мелкая гранулярность в ФП позволит легко переносить функционал через границу компонент

  • Сервисы → композиция pure-функций

  • Обработка ошибок → Either/Result типы.

4. Изоляция эффектов и состояний:

IO-операции → отдельный слой (Http, БД).

Использование СУБД выноса состояний наружу или монад для отсутствия скрытых побочных эффектов / состояний. Если это сложно или не эффективно, то для реализации состояний можно использовать состояние объекта в ООП. Также ООП имеет преимущество если ООП библиотеки уже реализуют почти всю логику вашей предметной области и ваши добавления незначительны.

5. Оптимизация скорости (при необходимости)

Когда же тонкая и гибкая прослойка должна стать жёсткой и надёжно изолирующей?

Выбор между тонкой и толстой прослойкой между компонентами зависит от контекста:

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

- Подходит, если термины библиотеки на 80% совпадают с вашим доменным языком.

- Подходит когда граница компонент ещё не сложилась, она может и должна легко меняться.

- Риск: Привязка к библиотеке, но экономия времени на разработку.

2. Толстая прослойка (надёжность/изоляция):

- Нужны доменные модели, полностью абстрагированные от библиотек.

- Границы компонент не меняются.

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

Компромисс:

Даже минимальные адаптеры (интерфейсы + базовые преобразования) лучше, чем прямое использование библиотеки в ядре. Это сохраняет путь к будущим изменениям без переписывания системы.

Правило выбора:

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

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


  1. shai_hulud
    15.02.2025 16:04

    Какой вывод? Больше связей крепче код?


    1. Dhwtj Автор
      15.02.2025 16:04

      TL;DR

      Чем сильнее изолированы компоненты, тем труднее изменить границы при необходимости.

      На макро уровне необходимость изменения границ возникает когда меняется домен.

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

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


      1. nin-jin
        15.02.2025 16:04

        фабрики фабрик, то есть абстракциям, которых нет в реальной жизни

        А вы думаете станки из воздуха материализуются?


        1. Dhwtj Автор
          15.02.2025 16:04

          В ООП слишком дорогие стенки.

          Не из кирпичей, а из сложных бетонных блоков.


      1. syakimov
        15.02.2025 16:04

        На первых этапах проекта, пока делается прототип, самый верный подход - хуяк-хуяк и в продакшен, время - деньги. А вот потом уже можножно и про высокую архитектуру подумать. Это business-driven development. Так что полностью соглашусь с автором.


        1. Dhwtj Автор
          15.02.2025 16:04

          Главное, чтобы рефакторинг дешёвый был. И только по необходимости.

          Мне кажется, в ФП рефакторинг дешевле, но не важно. Если, конечно, комфортно с ФП писать. Мне комфортно.


          1. syakimov
            15.02.2025 16:04

            Это уже зависит от профессионализма автора, конечно.


  1. syakimov
    15.02.2025 16:04

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


    1. Dhwtj Автор
      15.02.2025 16:04

      Monolith first, это широко известно. Это на уровне архитектуры.

      А на уровне кода та же самая аналогия, может только другой тайминг, когда изоляция и абстракции всё таки становятся нужны.

      Встречал случаи, когда обвязка из этих ваших фабрик увеличивала объём кода в 5 раз. Причём писал один человек, изоляции от чужого кода не требовалось. Ну вот он так видит...


      1. syakimov
        15.02.2025 16:04

        Чем больше кода, тем больше багов. Помогают всякие фреймворки. Если код пишется не для саморазвития, а для работы, и очень хочется в ООП, то это вариант.


        1. Dhwtj Автор
          15.02.2025 16:04

          Для саморазвития проекты дольше одного дня писать немного глупо.

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


  1. Dhwtj Автор
    15.02.2025 16:04

    В общем, тема про эдакий agile в архитектуре и коде.