open atlas
↑ К треку
Архитектурные паттерны ARCH · 05 · 03

Entity core и его стоимость

Кольцо Entities — наиболее стабильный код системы: чистые бизнес-правила без зависимостей. Реальная стоимость Clean Architecture — налог на маппинг и косвенное обращение на каждой границе кольца. Он оправдывается только в конкретных обстоятельствах.

ARCH Middle ◷ 24 min
Уровень
ОсновыJuniorMiddleSenior

Спустя восемнадцать месяцев после принятия Clean Architecture для B2B-платформы заказов команда провела аудит. Хорошие новости: платформа была расширена четырьмя новыми delivery mechanisms — мобильное приложение, партнёрский API, батч-сервис сверки и ops-CLI — все подключились к существующим interactors без изменений бизнес-логики. Новые инженеры могли найти каждое бизнес-правило в одном месте. Плохие новости: кодовая база раздулась. Теперь для «заказа» существовало пять отдельных структур данных — entity Order в кольце Entities, ORM-класс OrderRecord в адаптере базы данных, OrderResponseModel в кольце Use Cases, OrderDTO в REST-адаптере и OrderGraphQLType в GraphQL-адаптере. Каждая граница кольца порождала код маппинга. Простое изменение «добавить поле в заказ» затрагивало все пять классов и три функции маппинга. Команда задала честный вопрос: окупается ли архитектура? Ответ: для этой платформы — да. Но они видели, как коллеги применяли тот же паттерн к двадцатитабличному CRUD-приложению для внутренней отчётности и производили тот же объём кода маппинга при нулевой структурной пользе. Clean Architecture не универсально правильна. У неё есть конкретная стоимость и конкретные условия, при которых эта стоимость оправдана.

Entity core — что там живёт и почему

Кольцо Entities — самое внутреннее кольцо в Clean Architecture. Оно содержит общеприкладные бизнес-правила: логику, которая была бы верна и валидна в любом приложении, построенном на этом бизнес-домене, независимо от delivery mechanism или используемой инфраструктуры.

На B2B-платформе заказов Entity core содержит:

  • Класс Order с инвариантами жизненного цикла (pending-заказ должен иметь хотя бы одну позицию перед подтверждением; отменённый заказ не может перейти в confirmed; итог должен равняться сумме позиций)
  • Класс Invoice со структурными инвариантами (позиции должны суммироваться в итог; оплаченный счёт не может быть изменён)
  • Value object Money с правилами арифметики с учётом валюты
  • Перечисления OrderStatus, InvoiceStatus, представляющие валидные состояния
  • Domain events (OrderConfirmed, InvoiceGenerated) как чистые структуры данных

Что отсутствует в кольце Entities: всё, что касается инфраструктуры, прикладного потока или фреймворка. Никаких вызовов репозиториев. Никакого async/await-обращения к базе данных. Никаких ORM-аннотаций @Column или @Entity. Никаких HTTP-статус-кодов. Никаких импортов фреймворка логирования. Классы entity — чистые объекты, которые могут работать в полностью изолированном тесте без внешних зависимостей.

Аргумент стабильности

Аргумент в пользу сохранения Entity core чистым — это аргумент стабильности: entities — последний код, который нужно трогать при технологических изменениях. Базы данных мигрируют. Веб-фреймворки обновляются. Появляются мобильные клиенты. Ни одно из этих событий не должно изменить правило «Итог Invoice должен равняться сумме его позиций». Это правило постоянно, пока держится бизнес-модель.

Сохраняя entities свободными от инфраструктурных импортов, вы гарантируете: обновление ORM не может вызвать изменения entity. Переход с REST на GraphQL не может потребовать касания бизнес-логики Order. Новое требование облачного провайдера не может пройти в domain-правила. Самое внутреннее кольцо меняется только при изменении бизнес-модели — что и является единственным изменением, которое должно правомерно затрагивать его.

Эта стабильность также даёт выигрыш в тестировании. Unit-тесты entity — самые дешёвые тесты в системе: инстанцировать domain object, вызвать метод, проверить результат. Никакой базы данных, HTTP-сервера, mock-фреймворка. Entity инкапсулирует свои инварианты — для их тестирования нужен только класс entity и любые value types, которые он использует.

Почему это работает

Почему именно «общеприкладные» правила для entities? Терминология Мартина различает правила, которые были бы валидны в нескольких приложениях одного предприятия (инварианты Order применимы к клиентскому порталу, операционной консоли и инструменту финансовой отчётности), и правила, специфичные для рабочего процесса одного приложения (конкретные шаги для «разместить заказ через партнёрский API»). Entities принадлежат самому внутреннему кольцу, потому что они — наиболее переиспользуемые, наиболее стабильные и наименее подверженные изменениям бизнес-знания системы.

Честная стоимость — маппинг и косвенное обращение

Структурные преимущества Clean Architecture реальны. Стоимость не менее реальна, и её необходимо признать перед выбором паттерна.

Налог на маппинг. Каждая граница кольца требует кода трансляции. Поле entity Order не появляется автоматически в OrderResponseModel (кольцо Use Cases), OrderDTO (REST-адаптер) или ORM-классе OrderRecord (адаптер базы данных). Код маппинга на каждой границе пишется явно. Поле, начинающееся на entity, обычно появляется в четырёх-пяти отдельных классах и требует трёх-четырёх функций маппинга.

В аудите B2B-платформы: добавление deliveryDeadline к entity Order означало:

  1. Добавить поле в Order (кольцо Entities) с инвариантом (deadline должен быть после даты создания)
  2. Добавить в GenerateInvoiceRequest и GenerateInvoiceResponse там, где нужно (кольцо Use Cases)
  3. Добавить в OrderRecord и функцию ORM-маппинга (кольцо Infrastructure)
  4. Добавить в OrderDTO и код сериализации (REST-адаптер)
  5. Добавить в OrderGraphQLType и resolver (GraphQL-адаптер)

Это налог на косвенное обращение. В layered architecture можно затронуть два файла. В Clean Architecture затрагиваешь пять — в обмен на гарантию, что каждое кольцо не знает форматов других.

Церемония определений интерфейсов. Каждый репозиторий, каждый сервис, каждый output port требует определения интерфейса в кольце Use Cases и конкретной реализации в другом месте. Для системы с тридцатью use cases это тридцать интерфейсов input port, тридцать классов interactor, потенциально тридцать интерфейсов output port, тридцать request models и тридцать response models. Прежде чем написать единственную строку бизнес-логики, вы строите значительный объём структуры.

Когнитивная нагрузка. Новые инженеры, приходящие в команду, должны понять кольцевую модель, прежде чем смогут найти любой кусок логики. Вопрос онбординга «где живёт проверка billing hold?» имеет точный ответ (кольцо Use Cases, внутри interactor) — но только если понимаешь архитектуру.

Когда Clean Architecture избыточна

Clean Architecture окупается в конкретных обстоятельствах. За их пределами она генерирует церемонию без структурной отдачи.

Избыточна для: CRUD-приложений. Система, которая создаёт, читает, обновляет и удаляет записи без сложных инвариантов, state machines жизненного цикла или нескольких delivery mechanisms, не имеет бизнес-логики для защиты. Entities без реальных инвариантов (просто getters и setters над строкой базы данных) не выигрывают от нахождения в чистом самом внутреннем кольце.

Избыточна для: небольших или краткосрочных приложений. Внутренний дашборд отчётности для десяти человек, скрипт миграции данных, throwaway proof-of-concept — они будут заменены до того, как долгосрочные преимущества архитектуры начнут накапливаться.

Избыточна для: команд без дисциплины поддержания границ. Преимущества Clean Architecture исчезают в момент, когда разработчик из удобства добавляет импорт фреймворка в entity. Налог на маппинг постоянен; структурные гарантии постоянны только при соблюдении dependency rule. Если команда не может или не будет соблюдать границы колец, стоимость платится, но выгода не получается.

Стоит того для: долгоживущих приложений со сложными domain-правилами. Когда бизнес-логика действительно сложна — несколько состояний жизненного цикла, инварианты, требующие соблюдения, правила, растущие со временем, — entity core обеспечивает стабильный фундамент, переживающий годы инфраструктурных изменений.

Стоит того для: нескольких delivery mechanisms над общим доменом. Четыре delivery mechanisms B2B-платформы (веб, мобильный, партнёрский API, CLI) переиспользовали одни и те же interactors. Добавление четвёртого механизма обошлось команде только одним адаптером. Без Clean Architecture у них было бы четыре копии логики генерации счёта.

Стоит того для: команд, которые будут расти и меняться. Когда команда, строящая систему, не та, что её поддерживает, явные контракты (request models, port interfaces) служат документацией, которую компилятор принудительно соблюдает.

lesson.inset.note

Практическая эвристика: посчитай количество delivery mechanisms, разделяющих одну бизнес-логику. Если ответ один — только HTTP API, и так будет всегда — симметричное преимущество Clean Architecture гипотетично. Если ответ три или больше — или есть достоверный roadmap к трём или более — паттерн interactor начинает окупаться. Вторая эвристика: посчитай domain-инварианты. Если можешь описать все бизнес-правила домена пятью предложениями, entity core мало что защищает. Если описание правил занимает сессию у доски, core стоит налога.

Викторина

Команда платформы добавляет новое поле `billingRegion` в entity Order. В полностью реализованной кодовой базе Clean Architecture сколько файлов минимально должно измениться, и почему?

Викторина

Стартап строит B2B-систему управления заказами как монолит для одного клиента, ожидаемый срок работы — восемнадцать месяцев с последующей перестройкой. CTO предлагает Clean Architecture. Каков честный архитектурный совет?

Викторина

Команда строит B2B-платформу заказов, сейчас имеющую один HTTP API. Product roadmap показывает мобильное приложение, партнёрскую EDI-интеграцию и ops-CLI, запланированные на следующие два года. CTO спрашивает: стоит ли принимать Clean Architecture сейчас или «подождать и посмотреть»? Каков структурный аргумент за принятие в начале?

Вспомните перед уходом
  1. 01
    Что принадлежит Entity core, и каков аргумент стабильности за сохранение его чистым?
  2. 02
    Что такое налог на маппинг, и что делает его приемлемым?
  3. 03
    Назови три признака того, что Clean Architecture избыточна для данного проекта.
Итог

Entity core — самое внутреннее кольцо в Clean Architecture — наиболее стабильный, наиболее абстрактный, наиболее независимо тестируемый код системы. Он содержит общеприкладные бизнес-правила: инварианты, верные в любом приложении на этом бизнес-домене, независимо от delivery mechanism или инфраструктурной технологии. Что отсутствует: ORM-аннотации, типы базы данных, HTTP-концепции, импорты фреймворка. Entity меняется только при изменении бизнес-модели.

Честная стоимость Clean Architecture — налог на маппинг. Каждая граница кольца его берёт: domain-поле появляется в нескольких структурах данных, формат каждого кольца подходит для его цели, каждая граница требует явного кода маппинга. Это не артефакт плохой реализации — это прямое структурное следствие разделения колец. Аудит платформы обнаружил пять отдельных классов для «заказа» и три функции маппинга на поле. Вот счёт.

Счёт стоит оплатить при конкретных условиях: долгоживущие приложения, где инфраструктурные изменения опережают изменения бизнес-модели; системы с несколькими delivery mechanisms, переиспользующими одну бизнес-логику; сложные домены, где entity core действительно имеет инварианты для защиты; команды, которые будут расти и меняться. Для CRUD-приложений, throwaway-систем, бэкендов с единственным delivery mechanism и доменов с тривиальными инвариантами счёт приходит без соответствующей отдачи.

На этом завершается unit по Clean и Onion Architecture. Следующий unit вводит Domain-Driven Design — который идёт дальше концепции entity core и спрашивает, как моделировать бизнес-домен так, чтобы модель сама стала архитектурой.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.