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

От монолита к сервисам

Декомпозиция по швам bounded context через strangler fig: инкрементальная маршрутизация фич к новым сервисам при одновременном уменьшении монолита. Каждый сервис владеет своими данными — без shared database.

ARCH Senior ◷ 25 min
Уровень
ОсновыJuniorMiddleSenior

У B2B биллинговой платформы было 200 000 строк в одном Rails-монолите. CTO одобрил миграцию на сервисы. Команда выбрала прямой подход: взять самую сложную подсистему — управление заказами — и переписать её как микросервис. Шесть инженеров работали четыре месяца над новым OrderService. В день запуска запустили новый сервис рядом с монолитом и переключили трафик. Через час новый сервис и монолит отдавали противоречивые состояния заказов. Проблема: миграция перенесла API заказов, но данные заказов остались в общей базе. Billing всё ещё напрямую писал в таблицу orders. Inventory читал orders через свою ORM. OrderService имел API, но не владел данными. Через два месяца проект был заморожен. Урок: нельзя извлечь сервис, только переместив API. Нужно перемещать API и владение данными вместе, инкрементально, по швам, уже существующим в домене.

Поиск швов: bounded context как единицы декомпозиции

Правильный шов для извлечения сервиса — это bounded context (урок 06): область модели, где применяется один согласованный набор концепций и языка. Bounded context уже имеет естественную API-поверхность — трансляции, которые он экспортирует в другие контексты — и естественную границу данных — агрегаты, которыми он владеет.

Извлечение по неправильному шву — самая распространённая и дорогая ошибка при миграции на микросервисы. Неправильные швы производят сервисы, тесно связанные на уровне данных, даже будучи разделёнными на уровне API. Признаки неправильного шва:

  • Извлечённый сервис читает таблицы, которыми не владеет, требуя от монолита держать эти таблицы.
  • Два сервиса должны координироваться при каждой записи для поддержания согласованности — по сути распределённая транзакция.
  • Одна бизнес-операция требует последовательных вызовов API к трём-четырём сервисам — сервисы являются фрагментами воркфлоу, а не автономными возможностями.

Правильные швы соответствуют бизнес-возможностям, описываемым одним предложением без «и»: «управляет инвентарём продуктов», «обрабатывает жизненный цикл биллинга», «отслеживает статус заказа от размещения до доставки». Если описание требует «и» — возможно, это два шва, склеенных вместе.

Why this works

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

Паттерн strangler fig

Strangler fig (Фаулер, 2004) — инкрементальный паттерн миграции, названный в честь тропической лианы, растущей вокруг существующего дерева и в конце концов заменяющей его. Применительно к ПО:

  1. Поставь прокси перед монолитом. Весь трафик проходит через прокси (API-шлюз, слой маршрутизации или обратный прокси). Монолит изначально обрабатывает всё.
  2. Извлеки одну возможность в новый сервис. Новый сервис владеет частью домена — правильным bounded context.
  3. Маршрутизируй трафик этой возможности к новому сервису. Прокси направляет запросы извлечённой возможности к новому сервису. Код монолита для этой возможности становится мёртвым.
  4. Удали мёртвый код из монолита. Монолит уменьшается. Повторяй для следующей возможности.

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

Владение данными и правило database-per-service

Самое нарушаемое правило при миграции на микросервисы — владение данными. Strangler fig может маршрутизировать API-трафик к новому сервису с первого дня, но миграция владения данными занимает больше времени. Пока владение данными не передано, сервис — фасад, а не автономная единица.

Database-per-service означает, что у каждого сервиса есть собственное хранилище без чтения или записи его таблиц другими сервисами. Это не технологический выбор (один инстанс PostgreSQL может обслуживать несколько сервисов на разных схемах) — это контракт владения:

  • Только BillingService пишет в таблицы invoices и charges.
  • Никакой другой сервис не держит ORM-модель для этих таблиц.
  • Другие сервисы, которым нужны данные биллинга, вызывают API BillingService или потребляют его события.

Последовательность миграции для владения данными:

  1. Определить весь код в монолите, читающий или пишущий мигрируемые таблицы.
  2. Обернуть эти точки доступа за интерфейс в монолите, совпадающий с API-поверхностью нового сервиса.
  3. Задеплоить новый сервис и направить реализацию интерфейса монолита на API нового сервиса.
  4. Мигрировать данные в новую схему.
  5. Удалить таблицы монолита (или заблокировать схему так, чтобы записи отклонялись).
Викторина

Команда использует strangler fig для извлечения InventoryService из B2B-монолита. Через два месяца InventoryService обрабатывает весь API-трафик инвентаря. Однако BillingService (всё ещё в монолите) продолжает читать таблицу `inventory_reservations` напрямую через общую ORM. Техлид говорит: «Извлечение завершено — весь API-трафик идёт к InventoryService». Старший инженер говорит, что извлечение не завершено. Кто прав и почему?

Декомпозиция по возможности, а не по слою

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

Сервис, извлечённый по слою:

  • Не может быть независимо задеплоен (сервис логики зависит от сервиса данных для каждой операции).
  • Не может быть независимо протестирован (сервис логики не имеет поведения без сервиса данных).
  • Принуждает к синхронному зацеплению для каждого запроса (три сетевых хопа вместо нуля).

Сервис, извлечённый по бизнес-возможности, владеет своим полным вертикальным срезом: API, логика, хранилище. Это подход вертикальных срезов, применённый к декомпозиции сервисов.

Викторина

Архитектор предлагает декомпозировать B2B биллинговый монолит на три сервиса: DataService (весь доступ к БД), BusinessLogicService (все доменные правила) и ApiService (все HTTP-эндпоинты). Каждый запрос от фронтенда идёт к ApiService, который вызывает BusinessLogicService, который вызывает DataService. Какие структурные проблемы создаёт такая декомпозиция?

Последовательность: что извлекать первым

Не все модули одинаково хороши в качестве отправной точки для извлечения. Strangler fig работает лучше, когда первый извлекаемый сервис:

Имеет низкое зацепление с другими внутренностями монолита. Если биллинговая возможность обращается только к двум внутренним API (и эти вызовы можно обернуть за интерфейсы), извлечение относительно ограничено. Если она разделяет таблицы с каждым другим модулем, извлечение требует одновременной миграции каждой точки доступа.

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

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

Имеет чёткое владение. Команда, уже владеющая возможностью, может владеть её сервисом. Общий кросс-командный код сложнее всего извлекать.

Викторина

B2B биллинговая платформа планирует извлечения через strangler fig. Команда имеет двух кандидатов: (A) Модуль уведомлений — отправляет email и SMS, релизит редко, вызывается каждым другим модулем через общий класс NotificationService, не читает собственных таблиц (пишет в `notifications`), код чистый и самодостаточный. (B) Модуль отчётности — генерирует инвойсы и отчёты, релизит часто, читает таблицы `orders`, `invoices`, `customers` и `charges` напрямую через ORM, код сильно переплетён с биллинговой логикой. Какой модуль нужно извлекать первым и почему?

Вспомните перед уходом
  1. 01
    Что такое паттерн strangler fig и почему он избегает проблемы 'большой перезаписи'?
  2. 02
    Почему database-per-service — контракт владения, а не технологический выбор?
  3. 03
    Что не так с декомпозицией монолита по техническому слою (представление / логика / данные как три сервиса)?
Итог

Провальное извлечение на биллинговой платформе переместило API без перемещения данных. ORM монолита всё ещё владела таблицами; новый OrderService имел эндпоинт, но не данные. Каждая запись была гонкой между сервисом и оставшимся кодом монолита. Урок: сервис не автономен, пока не владеет своим хранилищем.

Strangler fig решает это инкрементально. Прокси перед монолитом маршрутизирует трафик срез за срезом к новым сервисам. Каждый срез извлекается с передачей владения данными — прямой доступ монолита к таблицам заменяется API-вызовами к новому сервису — до начала следующего среза. В любой момент система работает; монолит просто меньше.

Правильный шов для извлечения — bounded context: бизнес-возможность, называемая без «и». Декомпозиция по техническому слою — разделение представления, логики и данных в отдельные сервисы — производит наихудший исход: распределённое зацепление без автономности. Декомпозиция по возможности производит сервисы, развёртываемые, тестируемые и отказывающие изолированно.

Начинай с возможности с наименьшим исходящим зацеплением и наибольшей частотой изменений. Строй мышцу для извлечений через strangler fig на простом случае, затем применяй к сложным.

Практика

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

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

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

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

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

Trademarks belong to their respective owners. Editorial reference only.