В модульной системе редко меняющийся модуль зависит от часто меняющегося. Какое последствие это создаёт для эволюции системы?
Редко меняющийся модуль начинает наследовать нестабильность своей зависимости: изменения в часто меняющемся модуле могут требовать его адаптации, повторного тестирования и совместного выпуска. В результате фактическая граница изменений проходит не по модулям, а через них, поэтому независимая эволюция нарушается.
Принцип направления зависимостей сформировался как ответ на рост стоимости изменений в крупных программных системах. Когда компоненты связаны без учёта частоты их изменений, небольшое изменение в одном месте распространяется на стабильные части системы.
Архитектурная модульность нужна не только для разделения кода. Её цель — локализовать изменения, чтобы разные части системы могли развиваться с минимальной координацией.
Предположим, модуль расчёта отчётов редко меняется и используется многими компонентами. Он напрямую зависит от часто изменяющегося модуля правил скидок, чьи типы, форматы или сценарии регулярно пересматриваются.
Каждое изменение правил может затронуть отчёты. Потребуется менять стабильный модуль, запускать его проверки, пересматривать интеграцию и иногда синхронизировать выпуск нескольких компонентов. Формально модули разделены, но организационно и технически они эволюционируют как единое целое.
Зависимости должны быть направлены в сторону более стабильных абстракций и решений. Стабильный модуль не должен зависеть от деталей нестабильного модуля; вместо этого изменчивая реализация должна подстраиваться под контракт, которым владеет более стабильная часть системы.
Практический механизм обычно включает следующие шаги:
Важно различать частоту изменений и стабильность интерфейса. Часто меняющийся модуль может оставаться безопасной зависимостью, если его публичный контракт устойчив и изменения изолированы внутри реализации. Напротив, редко меняющийся модуль с широким публичным контрактом может создавать сильную связанность для большого числа потребителей.
Инверсия зависимости не означает, что вся система должна зависеть от абстракций. Избыточные интерфейсы усложняют код и могут скрыть реальные связи. Абстракция оправдана там, где есть граница изменения, разные варианты реализации или необходимость независимого жизненного цикла.
Компромисс состоит в том, что дополнительный контракт и адаптер увеличивают объём проектирования. Кроме того, плохо выбранная абстракция может стать столь же нестабильной, как и прежняя конкретная зависимость. Поэтому границу следует строить вокруг устойчивой бизнес-потребности, а не вокруг каждого класса или технического вызова.
В платформе расчёта комиссий отчётный модуль использовал внутренние объекты модуля тарифов. Тарифы часто менялись: появлялись новые основания для расчёта, менялась структура правил, а отчётам требовалось только несколько устойчивых фактов — операция, ставка, сумма и результат.
Рассматривались два варианта. Первый — оставить прямую зависимость и поддерживать совместимость внутренних объектов. Это требовало бы дисциплины, но сохраняло бы утечку модели тарифов и постепенно увеличивало бы стоимость изменений. Второй — передавать отчётному модулю отдельный устойчивый контракт результата расчёта, а преобразование выполнять внутри модуля тарифов или его адаптера.
Выбрали второй вариант. Отчёты перестали зависеть от внутренней структуры правил, а изменения алгоритма тарифов стали локальными, пока сохранялась семантика результата. Цена решения — дополнительное преобразование данных и необходимость явно поддерживать контракт, но она оказалась ниже стоимости постоянной синхронизации модулей.
Нет. Интерфейс может быть формально абстрактным, но при этом содержать изменчивые детали, чрезмерно широкий набор операций или типы реализации. Стабильность определяется не ключевым словом и не формой объявления, а тем, насколько редко контракт меняется и насколько хорошо он изолирует потребителя от деталей.
Это полезное направление, но не абсолютное правило без контекста. Иногда стабильный модуль должен использовать изменчивую возможность, например подключаемую политику или внешний провайдер. Тогда зависимость следует направлять через принадлежащий стабильному модулю контракт, а изменчивую часть помещать за адаптером.
Нужно посмотреть на реакцию системы на изменение: какие модули требуют правок, совместного тестирования и синхронного выпуска. Если изменение детали одного модуля регулярно затрагивает редко меняющийся потребитель, зависимость, вероятно, пересекает границу в неправильном направлении. Если же меняется только реализация, а контракт и потребитель остаются неизменными, граница выполняет свою задачу.