.NET Tutorial
Middle6 мин чтенияВопрос 17 из 17

Что такое Strangler Fig — постепенная миграция legacy?

Метафора

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

Big bang против Strangler Fig

Big bang rewrite

Старт переписывания

1–3 года разработки
без выпуска ценности

Переключение за один раз

Всплывают забытые
правила и интеграции

Strangler Fig

Фасад перед legacy

Мигрирован модуль 1 —
уже в продакшене

Модули 2, 3 ...

Legacy выключен

Big bang rewrite

Старт переписывания

1–3 года разработки
без выпуска ценности

Переключение за один раз

Всплывают забытые
правила и интеграции

Strangler Fig

Фасад перед legacy

Мигрирован модуль 1 —
уже в продакшене

Модули 2, 3 ...

Legacy выключен

Два пути модернизации
Big bang Strangler Fig
Выпуск ценности В конце Постоянно
Риск Огромный, сосредоточен в одном переключении Малый, распределён по шагам
Откат Практически невозможен Переключить маршрут обратно
Обратная связь Поздно С первого модуля
Цена Две системы в разработке, заморозка старой Две системы в эксплуатации какое-то время, фасад, синхронизация данных

Как это работает

/api/catalog/* (мигрировано)

/api/orders/* (мигрировано)

всё остальное

CDC / события —
синхронизация данных

Клиенты

Фасад / API Gateway
(YARP, NGINX)

Новый сервис Catalog
.NET 10

Новый сервис Orders

Legacy-монолит
.NET Framework 4.8

Новая БД

Legacy БД

/api/catalog/* (мигрировано)

/api/orders/* (мигрировано)

всё остальное

CDC / события —
синхронизация данных

Клиенты

Фасад / API Gateway
(YARP, NGINX)

Новый сервис Catalog
.NET 10

Новый сервис Orders

Legacy-монолит
.NET Framework 4.8

Новая БД

Legacy БД

Фасад маршрутизирует запросы между старой и новой системой

Шаги для каждой функции

следующая функция

1. Выбрать функцию
(высокая ценность,
слабые связи)

2. Реализовать
в новой системе

3. Настроить
синхронизацию данных

4. Переключить трафик
постепенно (1% → 100%)

5. Наблюдать,
сравнивать результаты

6. Удалить код
в legacy

следующая функция

1. Выбрать функцию
(высокая ценность,
слабые связи)

2. Реализовать
в новой системе

3. Настроить
синхронизацию данных

4. Переключить трафик
постепенно (1% → 100%)

5. Наблюдать,
сравнивать результаты

6. Удалить код
в legacy

Цикл миграции одной функции

Фасад на YARP

Маршрутизация: мигрированное — в новый сервис, остальное — в legacy
{
  "ReverseProxy": {
    "Routes": {
      "catalog-new": { "ClusterId": "catalog", "Order": 1, "Match": { "Path": "/api/catalog/{**rest}" } },
      "legacy-all":  { "ClusterId": "legacy",  "Order": 100, "Match": { "Path": "{**catch-all}" } }
    },
    "Clusters": {
      "catalog": { "Destinations": { "d1": { "Address": "http://catalog-service:8080" } } },
      "legacy":  { "Destinations": { "d1": { "Address": "http://legacy-iis.internal" } } }
    }
  }
}

Постепенное переключение — по проценту трафика, пользователям-тестировщикам или feature flag:

C#
app.MapReverseProxy(proxy => proxy.Use(async (ctx, next) =>
{
    var flags = ctx.RequestServices.GetRequiredService<IFeatureManager>();
    if (ctx.Request.Path.StartsWithSegments("/api/orders") && !await flags.IsEnabledAsync("NewOrdersService"))
        ctx.Features.Get<IReverseProxyFeature>()!.AvailableDestinations = GetLegacyDestinations(ctx);
    await next();
}));

Microsoft предлагает для ASP.NET Framework → ASP.NET Core готовые инструменты: System.Web Adapters и YARP-based incremental migration (новое приложение ASP.NET Core проксирует немигрированные маршруты в старое, общая сессия и аутентификация).

Данные — самая сложная часть

Варианты:

  1. Общая БД на переходный период — новый сервис читает/пишет в старую схему. Быстро, но сохраняет связанность; допустимо как временный шаг.
  2. Синхронизация через CDC (Debezium) или события: legacy остаётся источником истины, новая БД — производная, пока функция не мигрирована полностью.
  3. Двойная запись через фасад/новый сервис (с outbox, не наивно!) на время перехода.
  4. Перенос владения: после переключения новая система — источник истины, старая получает изменения обратно, если они ей ещё нужны.

Anti-Corruption Layer

Новый домен:
Order, Money, CustomerId

Anti-Corruption Layer
адаптеры и трансляторы

Legacy:
TBL_ZAKAZ, поля F1..F87,
статусы 'A','Z','X'

Новый домен:
Order, Money, CustomerId

Anti-Corruption Layer
адаптеры и трансляторы

Legacy:
TBL_ZAKAZ, поля F1..F87,
статусы 'A','Z','X'

ACL переводит модель legacy в новую модель

ACL (термин DDD) не даёт странностям старой модели «просочиться» в новую: маппинг статусов, форматов, кодировок, разбиение «божественных» таблиц. Новая система говорит на своём языке, а переводом занимается изолированный слой.

Практические советы

  • Начинайте с функции, которая приносит ценность и слабо связана с остальным (каталог, уведомления, отчёты), чтобы быстро доказать работоспособность подхода.
  • Замораживайте изменения в мигрируемой части legacy, иначе придётся переносить движущуюся цель.
  • Сравнивайте результаты (shadow traffic / dark launch): отправлять копию запросов в новую систему и сравнивать ответы, не отдавая их пользователю.
  • Удаляйте мигрированный код из legacy — иначе через год будут поддерживаться обе версии.
  • Наблюдаемость обеих систем и фасада — единые трассировки и метрики.
  • Иметь план завершения: без него «временное» сосуществование становится постоянным.

Вопросы на засыпку

Почему большие переписывания часто проваливаются?

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

Что делать с функциями, которые нельзя выделить по URL?

Перехватывать не только HTTP: события (подписка на изменения legacy через CDC), фоновые задания (переносить по одному), UI (микрофронтенды или iframe-вставки), вызовы внутри кода (выделение интерфейса в legacy и реализация через вызов нового сервиса — branch by abstraction).

Что такое Branch by Abstraction?

Техника для замены компонента внутри кода: вводится абстракция перед старой реализацией, все вызовы переводятся на неё, создаётся новая реализация, переключение через флаг, затем старая удаляется. Это Strangler Fig на уровне кода, без долгоживущих веток в git.

Как понять, что миграция завершена?

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

Итог

Strangler Fig заменяет legacy постепенно: фасад перехватывает трафик, функции переносятся по одной с постепенным переключением и возможностью отката, а старый код удаляется. Главные сложности — данные и изоляция моделей (CDC, события, Anti-Corruption Layer). Это медленнее на старте, но надёжнее и ценнее для бизнеса, чем большое переписывание.