АрхитектураАрхитектура ПОАрхитектор программного обеспечения

В модульной системе редко меняющийся модуль зависит от часто меняющегося. Какое последствие это создаёт для...

В модульной системе редко меняющийся модуль зависит от часто меняющегося. Какое последствие это создаёт для эволюции системы?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

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

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

Постановка проблемы

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

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

Подробное решение

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

Практический механизм обычно включает следующие шаги:

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

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

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

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

Ситуация из практики

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

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

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

Что кандидаты часто упускают

  1. Достаточно ли направить зависимость на интерфейс, чтобы модуль стал стабильным?

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

  1. Всегда ли стабильный модуль должен зависеть только от ещё более стабильного?

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

  1. Как отличить здоровую зависимость от зависимости, нарушающей направление изменений?

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