Изоляция компонентов нужна для управления сложностью, особенно для больших команд и программ. Но её раннее применение часто противоречит целей по гибкости, особенно в начале, когда ещё не известны или не сложились бизнес процессы, когда программу ещё пишет небольшая команда.
Изменение границ компонент это сложно. В ООП это сложнее чем в ФП из-за более крупной гранулярности, класс сложнее чем отдельная функция. Изменение границ микросервисов сложнее чем в монолите.
Проблема ранней изоляции: -
Необходимость частого рефакторинга на раннем этапе
Тонкая прослойка как компромисс: - Например, обёртки над библиотечными классами добавляют функционал с минимальным вмешательством и без необходимости писать всё с нуля.
Гибридный подход: - Функциональный домен, императивная оболочка.
Домен — функциональное ядро: Бизнес-логика в чистых функциях, независимых от инфраструктуры.
Оболочка — императивная. Необходимость интеграции с ООП библиотеками (io, фреймворки) заставляет писать эту часть в ООП. Но можно использовать тонкие прослойки для интеграции для минимизации boilerplate.
Риски: - Скрытая привязка к библиотеке: Домен косвенно зависит от её типов. - Сложность замены: Если библиотека всё же потребует замены, придётся модифицировать методы расширения.
Итог: Ранняя изоляция через толстые адаптеры часто избыточна.
Методика
Как же тогда должна выглядеть методика обследования и проектирования на основе DDD, но оставляя гибкими границы доменов по bounded context до нужного момента?
Методика пригодна как для проектирования архитектуры доменных компонент, так и более мелкого уровня детализации (структуры данных и функции).
Определения (мои)
Состояние - наблюдаемое явление, фиксированный набор значений параметров системы или её части в определённый момент времени, определяющий её поведение и доступные действия.
Типы → Категории возможных состояний системы и их допустимых комбинаций.
События → Факты о произошедших изменениях (прошлые состояния).
Процессы → Механизмы перехода между состояниями (будущие изменения состояний).
Последовательность проектирования компонент (домена DDD либо более мелких сущностей):
1. Анализ домена:
- Event Storming → выявление ключевых событий и процессов.
- Определение Bounded Context → границы модулей. Домен должен быть такой чтобы помещался а голове одного доменного эксперта. Если говорить про модули кода, то принцип единственной ответственности не позволит вырасти когнитивной сложности компонента, так как не смешивает разные предметные области в одном коде.
2. Моделирование типами:
- Каждое доменное понятие → алгебраический тип данных, определяет набор возможных состояний и их комбинаций.
- Бизнес-правила → валидаторы как функции.
3. Чистые функции для логики:
Мелкая гранулярность в ФП позволит легко переносить функционал через границу компонент
Сервисы → композиция pure-функций
Обработка ошибок → Either/Result типы.
4. Изоляция эффектов и состояний:
IO-операции → отдельный слой (Http, БД).
Использование СУБД выноса состояний наружу или монад для отсутствия скрытых побочных эффектов / состояний. Если это сложно или не эффективно, то для реализации состояний можно использовать состояние объекта в ООП. Также ООП имеет преимущество если ООП библиотеки уже реализуют почти всю логику вашей предметной области и ваши добавления незначительны.
5. Оптимизация скорости (при необходимости)
Когда же тонкая и гибкая прослойка должна стать жёсткой и надёжно изолирующей?
Выбор между тонкой и толстой прослойкой между компонентами зависит от контекста:
1. Тонкая прослойка (быстро/минимализм) или даже вызовы библиотек напрямую, жертвуя гибкостью и принципом единственной ответственности.
- Подходит, если термины библиотеки на 80% совпадают с вашим доменным языком.
- Подходит когда граница компонент ещё не сложилась, она может и должна легко меняться.
- Риск: Привязка к библиотеке, но экономия времени на разработку.
2. Толстая прослойка (надёжность/изоляция):
- Нужны доменные модели, полностью абстрагированные от библиотек.
- Границы компонент не меняются.
- Нужно надёжно изолировать компоненты, чтобы изменения в одних не требовали цепочку изменений других. Особенно, если другими компонентами уже владеет другая группа разработки.
Компромисс:
Даже минимальные адаптеры (интерфейсы + базовые преобразования) лучше, чем прямое использование библиотеки в ядре. Это сохраняет путь к будущим изменениям без переписывания системы.
Правило выбора:
Чем критичнее компонент для бизнеса и чем более стабильны границы между компонентами, тем "толще" должна быть прослойка между компонентами.
