Концентрическое правило зависимостей
Clean и Onion Architecture располагают слои концентрическими кольцами. Единственное жёсткое правило: source-code зависимости указывают только внутрь — к более высокоуровневой политике.
На B2B-платформе заказов вырос четырёхслойный стек: presentation, application, domain, infrastructure. Команда изучила hexagonal architecture и тщательно расставила интерфейсы на каждой границе. Но спустя шесть месяцев обнаружилась проблема: domain layer импортировал из infrastructure layer утилиту форматирования денежных значений. Утилита жила в infrastructure, потому что изначально была помещена туда рядом с кодом конвертации валют для базы данных. Границы интерфейсов были целы — domain вызывал интерфейс, а не конкретный класс, — но сам интерфейс был определён внутри пакета infrastructure. Это значило, что domain зависел от пакета infrastructure ради определения интерфейса. Инверсия зависимостей была неполной. Когда в 2017 году вышла книга Clean Architecture, Robert Martin предлагал не что-то совершенно новое. Он брал ключевое наблюдение hexagonal architecture — порты, принадлежащие приложению — и систематизировал его в правило, применимое к каждой границе между слоями. Формулировка была точной: source-code зависимости могут указывать только внутрь — к более высокоуровневой политике.
Модель концентрических колец
Как Clean Architecture (Robert Martin, 2012 blog post, 2017 книга), так и Onion Architecture (Jeffrey Palermo, 2008) организуют систему в виде набора концентрических окружностей — колец, вложенных одно в другое. В центре — наиболее важный и стабильный код, на внешнем крае — наиболее изменчивый, специфичный для инфраструктуры.
Кольца немного различаются между двумя моделями, но логика одна. Канонические четыре кольца Мартина:
- Entities (самое внутреннее) — общеприкладные бизнес-правила; наиболее стабильный код системы.
- Use Cases — бизнес-правила конкретного приложения; оркестрируют entities для достижения целей.
- Interface Adapters — переводят между форматом use case и внешним миром (controllers, presenters, gateways).
- Frameworks and Drivers (самое внешнее) — веб-фреймворки, базы данных, внешние библиотеки; наиболее изменчивый код.
В Onion Палермо используются немного другие названия, но та же логика: Domain Model в центре, затем Domain Services, затем Application Services, затем внешнее кольцо Infrastructure. Обе модели приходят к одному структурному ограничению.
Правило зависимостей — почему это правило, а не рекомендация
Единственное принудительно соблюдаемое ограничение: source-code зависимость (import, reference, compilation dependency) может указывать только внутрь. Ничто во внутреннем кольце не может ссылаться на что-либо во внешнем. Entity не может импортировать use case. Use case не может импортировать controller. Interface adapter не может импортировать веб-фреймворк — веб-фреймворк импортирует adapter.
Это не принцип стиля. Это структурное ограничение с конкретной отдачей: если ничто во внутренних кольцах не знает о внешних, внутренние кольца можно компилировать, тестировать и анализировать в полной изоляции от инфраструктуры. Все тесты entity и use case можно запустить без базы данных, веб-сервера или ORM.
Нарушение из вступительной истории — domain, импортирующий утилиту форматирования денег из пакета infrastructure — нарушало это правило. Даже при наличии интерфейса domain должен был импортировать его из пакета infrastructure. Путь импорта пересекал границу в неправильном направлении. Исправление то же, что и в hexagonal: перенести интерфейс во внутреннее кольцо (domain его определяет), а внешнее кольцо реализует его.
▸Почему это работает
Почему стабильность возрастает к центру? Внутренние кольца содержат чистую бизнес-логику: правила, которые меняются только при изменении самой бизнес-модели. Внешние кольца содержат технологические решения: базы данных, фреймворки, протоколы — они меняются по технологическим причинам (новая версия ORM, смена облачного провайдера, deprecation стороннего API), не связанным с бизнес-логикой. Структурируя зависимости к стабильному центру, вы гарантируете, что технологические изменения не могут вызвать изменения бизнес-правил.
Как Clean Architecture обобщает hexagonal
В unit 04 была представлена hexagonal architecture: порты, принадлежащие приложению, адаптеры с обеих сторон. Clean и Onion Architecture обобщают это с одной границы (domain/infrastructure) до N концентрических границ.
В hexagonal инверсия происходит один раз: domain определяет интерфейсы портов; infrastructure реализует их. Clean и Onion применяют ту же инверсию на каждой границе кольца. Кольцо Interface Adapters определяет интерфейсы, которые должны реализовывать Frameworks & Drivers. Кольцо Use Cases определяет интерфейсы (output ports, presenter interfaces), которые должно реализовывать кольцо Interface Adapters. Инверсия распространяется по всей цепочке внутрь.
Clean vs Onion — сходство и различие
Clean Architecture и Onion Architecture созданы независимо, но структурно сходятся. Обе пришли к одному правилу зависимостей. Практические различия:
Onion Architecture (Palermo, 2008) акцентирует Domain Model как абсолютный центр — чистые domain objects без каких-либо зависимостей. Domain Services в следующем кольце, Application Services далее, Infrastructure снаружи.
Clean Architecture (Martin, 2012/2017) вводит явное кольцо Interface Adapters, ответственное за перевод всех форматов данных между миром use case и внешним миром. Также разграничивает Entities (общеприкладные правила) и Use Cases (прикладные правила).
На практике команды часто смешивают оба подхода. Важно не то, какие названия колец вы используете — важно, соблюдается ли правило зависимостей.
▸lesson.inset.note
Концентрическая модель — это не фиксированное число колец. Мартин прямо говорит: диаграмма с четырьмя кольцами иллюстративна; реальные системы могут иметь больше. Что не обсуждается — это направление зависимости: только внутрь. Можно добавить кольцо Anti-Corruption Layer, кольцо Reporting, кольцо External Services — если зависимости каждого нового кольца указывают внутрь, архитектура корректна.
Кольцо Use Cases платформы импортирует интерфейс CurrencyFormatter из кольца Infrastructure, где изначально была написана утилита форматирования денег. Интерфейс не содержит инфраструктурно-специфичных методов — он чистый. Является ли это нарушением правила зависимостей, и почему?
Команда хочет добавить кольцо генерации PDF-отчётов снаружи кольца Interface Adapters, между Interface Adapters и Frameworks. Библиотека PDF — сторонний пакет. Где должны быть определены интерфейсы кольца PDF, и какое кольцо их реализует?
Кодовая база Onion Architecture имеет структуру: Domain Model → Domain Services → Application Services → Infrastructure. Разработчик говорит: «Onion и hexagonal — это одно и то же: оба просто помещают интерфейс между domain и базой данных». Чего не хватает в этом описании?
- 01Что такое dependency rule в Clean Architecture, и почему оно обеспечивает независимую тестируемость внутренних колец?
- 02Как Clean Architecture обобщает hexagonal architecture?
- 03В чём структурное различие между Onion Architecture и Clean Architecture?
Clean и Onion Architecture — концентрические кольцевые модели, обобщающие ключевое наблюдение hexagonal architecture на каждую границу слоёв. Центр архитектуры содержит наиболее стабильный, абстрактный код — чистые бизнес-правила, меняющиеся только при изменении самого бизнеса. Внешние кольца содержат технологические решения — базы данных, фреймворки, протоколы, — меняющиеся по технологическим причинам, не связанным с бизнес-логикой.
Dependency rule формулирует это точно: source-code зависимости могут указывать только внутрь. Внутреннее кольцо никогда не импортирует из внешнего. Внутреннее кольцо определяет интерфейс; внешнее реализует его. Каждая граница кольца применяет эту инверсию.
Нарушение из вступительной истории — кольцо Use Cases, импортирующее интерфейс из пакета Infrastructure — было технически тонким: интерфейс существовал, конкретный тип не импортировался. Но определение интерфейса жило в неправильном кольце, и у Use Cases был путь импорта, указывающий наружу. Исправление — владение: перенести интерфейс в кольцо, которое его потребляет.
Clean Architecture (Martin) и Onion Architecture (Palermo) различаются в основном названиями колец и акцентами. Обе приходят к одному структурному правилу. Число колец не фиксировано на четырёх. Что не обсуждается — направление: только внутрь.
Следующий урок рассматривает, что живёт во втором кольце — кольце Use Cases — и как паттерн interactor фиксирует «что делает приложение» как первоклассную структурную единицу.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.