Что такое паттерн Strangler Fig
Мартин Фаулер (Martin Fowler) описал этот паттерн после наблюдения за австралийскими деревьями: семечко удушающего инжира прорастает в кроне взрослого дерева, пускает корни вниз...
🟢 Junior Level
Strangler Fig (Паттерн «Удушающий инжир») — это архитектурная стратегия постепенной, эволюционной миграции монолитного приложения на микросервисную архитектуру путём пошаговой замены отдельных частей системы новыми сервисами до тех пор, пока монолит не отомрет полностью.
Фаза 0 (Монолит): [═════════════════ Монолит ═════════════════]
Фаза 1 (Первый шаг): [══════════════ Монолит ══════════════] + [User Service]
Фаза 2 (Развитие): [═══════════ Монолит ═══════════] + [User Service] + [Order Service]
Фаза 3 (Финал): [User Service] + [Order Service] + [Payment Service] + [Catalog Service]
(Старый монолит полностью остановлен и удален! 🎉)
Происхождение метафоры:
Мартин Фаулер (Martin Fowler) описал этот паттерн после наблюдения за австралийскими деревьями: семечко удушающего инжира прорастает в кроне взрослого дерева, пускает корни вниз к земле, оплетает ствол и со временем полностью заменяет собой старое дерево, которое естественным образом отмирает внутри новой древесины.
Почему отказ от «Big Bang Rewrite» спасает бизнес:
- Антипаттерн «Большой взрыв» (Big Bang): Попытка остановить развитие монолита на 2 года и переписать всё с нуля. 90% таких проектов заканчиваются банкротством: за 2 года бизнес уходит вперед, новая система не учитывает сотни скрытых краевых случаев монолита, а одновременный релиз приводит к фатальным падениям.
- Strangler Fig: Каждый спринт (2–4 недели) отделяется один изолированный бизнес-контекст, немедленно выкатывается в Production и приносит ценность, пока остальная система продолжает функционировать без единой секунды простоя (Zero Downtime).
🟡 Middle Level
1. Четыре фазы цикла миграции
┌───────────────────────────────┐
│ API Gateway / Ingress │
└───────┬───────────────┬───────┘
/api/v1/orders │ │ /api/v1/users (мигрировано!)
▼ ▼
┌────────────────┐ ┌─────────────────┐
│ Монолит │ │ User Service │
└────────────────┘ └─────────────────┘
- Intercept (Перехват): Перед монолитом развертывается API Gateway (Spring Cloud Gateway, Envoy, Nginx). Все клиентские вызовы идут строго через шлюз.
- Transform (Реализация): Новый сервис (например,
User Service) разрабатывается с нуля по современным стандартам и со своей изолированной базой данных (Database per Service). - Coexist (Сосуществование): Gateway перенаправляет маршрут
/api/v1/usersна новый микросервис. Все остальные вызовы продолжают прозрачно идти в монолит. Данные синхронизируются в реальном времени. - Eliminate (Удаление): Старый неиспользуемый код пользователей вырезается из монолита.
2. Защитный слой: Anti-Corruption Layer (ACL)
При отделении функционала новый микросервис рискует быть «отравлен» устаревшей и запутанной моделью данных монолита:
[Чистая доменная модель (Новый сервис)] ◄──► [Anti-Corruption Layer (ACL)] ◄──► [Монолит (Legacy API/DB)]
Anti-Corruption Layer (Слой предотвращения деградации): Специальный транслятор (фасад / адаптер), который преобразует устаревшие структуры монолита в чистые доменные объекты нового сервиса. Он защищает новую кодовую базу от проникновения легаси-терминов и кривых структур БД.
🔴 Senior Level
1. Двунаправленная синхронизация данных (Bidirectional Sync)
Сложнейший вызов миграции: пока распил не завершен, возможна ситуация, когда новый сервис уже принимает запись данных, но старый монолит всё ещё содержит функционал, требующий доступа к этим данным.
Синхронизация через CDC (Change Data Capture):
[Legacy DB] ──(Debezium CDC 1)──► [Kafka Topic] ──► [New Service DB]
▲ │
└──────────(Debezium CDC 2 / Outbox)──────────────────┘
Защита от бесконечного цикла репликации (Loop Avoidance):
Если база $A$ пишет в базу $B$ через CDC, а база $B$ реплицирует изменения обратно в базу $A$, возникает бесконечный цикл обновлений. Решение:
- Добавление технической колонки
source_system = 'NEW_SERVICE'. - Фильтр Debezium CDC или Kafka Connect игнорирует события, у которых источник совпадает с целевой системой.
2. Теневой запуск: Traffic Shadowing (Dark Launching)
Прежде чем переключить реальных пользователей на новый микросервис, необходимо доказать его идентичность по логике и стабильность по нагрузке:
┌───────────────────────────────┐
│ API Gateway / Envoy Proxy │
└───────┬───────────────┬───────┘
│ (Основной) │ (Теневой дубль 100% трафика в фоне)
▼ ▼
[ Монолит ] [ Новый сервис ] ──► [ Shadow DB (Запись изолирована) ]
│ │
└───────┬───────┘
▼
[ Diff Engine (Сравнение JSON) ]
- Envoy / Gateway Shadowing: Шлюз отправляет реальный запрос в монолит и возвращает ответ клиенту. Одновременно копия запроса асинхронно отправляется в новый микросервис.
- Diffing: Ответ нового микросервиса сравнивается побайтово или семантически с ответом монолита (инструменты типа Twitter Diffy).
- Безопасность: Ответ нового сервиса клиенту не отдается. Для операций мутации (POST/PUT) вызовы внешних систем (эквайринг, email) заменяются моками, а запись идет в теневую тестовую БД.
4 Tricky Questions
1. Как безопасно протестировать мутирующие операции (POST / PUT / DELETE) через Traffic Shadowing, не вызвав двойных списаний денег или отправки дубликатов SMS?
Ответ: Для безопасного теневого тестирования методов записи:
- Изоляция базы данных: Теневой сервис подключается к изолированной Shadow Database, куда копируются актуальные данные. Реальная база монолита не затрагивается.
- Mocking побочных эффектов (Side-Effects): На уровне сетевого клиента или Spring профиля (
@Profile("shadow")) отключаются вызовы внешних шлюзов (платежи, SMS, фискализация чеков). Вместо них ставятся моки (WireMock / No-Op адаптеры). - Сравнение доменных событий: Сравниваются не побочные эффекты, а сгенерированные бизнес-события в Kafka или результирующие состояния сущностей в Shadow DB с основной базой.
2. С какого модуля правильнее всего начинать распил монолита по паттерну Strangler Fig?
Ответ: Никогда не начинают с ядра системы (Core Domain — например, балансы пользователей или оформление заказов). Правильная стратегия:
- Низкая связность (Low Coupling): Модуль с минимальным количеством внешних зависимостей в монолите.
- Низкий бизнес-риск: Ошибка не должна привести к отзыву банковской лицензии.
- Идеальные кандидаты для старта: Сервис отправки нотификаций (Email/SMS), сервис генерации PDF-чеков, статический каталог статей, сбор телеметрии или сервис скидочных купонов. Команда набивает руку на настройке CI/CD, K8s, сетевой инфраструктуры и только затем переходит к Bounded Contexts средней сложности.
3. Что делать, если новый сервис требует выполнения межсервисной транзакции со старым монолитом во время переходного периода?
Ответ: Синхронный 2PC (двухфазный коммит) между монолитом и новым сервисом неприменим из-за несовместимости и блокировок. Решение: Применение Saga с участием монолита:
- Монолит адаптируется для поддержки двухфазных бизнес-команд (например, добавление метода API
POST /orders/cancelдля отката). - Новый сервис выступает в роли Saga Orchestrator, координируя шаги через асинхронные очереди сообщений (Kafka/RabbitMQ).
- Если операция в монолите падает, новый сервис выполняет компенсирующую транзакцию в своей БД.
4. Всегда ли нужно «душить» монолит до последней строчки кода (Правило 80/20)?
Ответ: Нет. Распил монолита — это бизнес-инвестиция, а не религиозный догмат. Часто первые 80% монолита (высоконагруженные, часто меняющиеся продуктовые фичи) успешно выносятся в микросервисы за 1 год. Оставшиеся 20% представляют собой стабильный, редко меняющийся легаси-код (например, закрытие финансового месяца или печать архивных отчетов раз в квартал). Выносить этот стабильный остаток экономически нецелесообразно: его затраты на переписывание превысят любую выгоду. Монолит превращается в один из рядовых сервисов экосистемы (Legacy Core Service) и спокойно живет годами за общим API Gateway.
🎯 Шпаргалка для интервью
Главное:
- Strangler Fig — стратегия постепенной, эволюционной замены монолита на микросервисы без остановки работы (Zero Downtime).
- Альтернатива «Большому взрыву» (Big Bang Rewrite), защищающая от банкротства и срыва сроков.
- Фазы: Intercept (API Gateway) $\to$ Transform (новый сервис) $\to$ Coexist (роутинг + синхронизация) $\to$ Eliminate (удаление легаси-кода).
- Anti-Corruption Layer (ACL) защищает домен нового сервиса от легаси-структур монолита.
- Данные мигрируют через CDC (Debezium + Kafka), избегая проблем Dual-Write.
- Проверка корректности: Traffic Shadowing (теневой дубль запросов) + канареечный релиз.
- Начинать миграцию нужно с наименее связанных и наименее рискованных модулей (нотификации, генерация отчетов).
Красные флаги (Чего говорить НЕЛЬЗЯ):
- ❌ «Лучший подход — заморозить проект на год и полностью переписать монолит с нуля» (Классический провал Big Bang).
- ❌ «Для переноса данных монолит должен писать одновременно в две базы данных через
@Transactional» (Dual-Write ломает согласованность при любых сетевых сбоях). - ❌ «Первым делом из монолита нужно выпиливать самый сложный и запутанный финансовый модуль» (Сломает бизнес на первом же шаге; начинают с простых слабосвязанных модулей).