Що таке патерн 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: Кожні кілька спринтів відокремлюється один ізольований бізнес-контекст, що негайно постачається в 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 ігнорує події, джерело яких збігається з поточною цільовою системою.
2. Тіньовий запуск: Traffic Shadowing (Dark Launching)
Перш ніж спрямувати реальних користувачів на новий мікросервіс, необхідно гарантувати його ідентичність за бізнес-логікою та перевірити стійкість під навантаженням:
┌───────────────────────────────┐
│ API Gateway / Envoy Proxy │
└───────┬───────────────┬───────┘
│ (Основний) │ (Тіньовий дубль 100% трафіку у фоні)
▼ ▼
[ Моноліт ] [ Новий сервіс ] ──► [ Shadow DB (Ізольований запис) ]
│ │
└───────┬───────┘
▼
[ Diff Engine (Порівняння JSON) ]
- Envoy / Gateway Shadowing: Шлюз спрямовує реальний запит у моноліт і повертає його відповідь клієнту. Одночасно асинхронна копія запиту вирушає в новий мікросервіс.
- Diffing: Відповідь нового мікросервісу порівнюється (наприклад, за допомогою інструментів на кшталт Twitter Diffy) з відповіддю моноліту на рівні полів JSON.
- Безпека: Відповідь нового сервісу клієнтові не віддається. Для мутуючих операцій (POST/PUT) виклики зовнішніх шлюзів (еквайринг, SMS) замінюються моками, а запис ведеться в тіньову тестову БД.
4 Tricky Questions
1. Як безпечно протестувати мутуючі операції (POST / PUT / DELETE) через Traffic Shadowing, уникаючи подвійних списань грошей або надсилання повторних SMS?
Відповідь: Для безпечного тіньового тестування запитів запису:
- Ізоляція сховища: Тіньовий сервіс підключається до окремої Shadow Database, куди заздалегідь скопійовано актуальний стан даних. Робоча база моноліту не модифікується.
- Mocking побічних ефектів (Side-Effects): На рівні клієнтів або профілю Spring (
@Profile("shadow")) вимикаються виклики зовнішніх провайдерів (платіжні шлюзи, смс-шлюзи). Замість них підставляються No-Op або WireMock заглушки. - Звірка доменних подій: Порівнюються не фізичні зовнішні виклики, а згенеровані бізнес-події в тестових чергах Kafka або фінальний стан ентіті в Shadow DB порівняно з основною базою.
2. З якого модуля найдоцільніше розпочинати розпил моноліту за патерном Strangler Fig?
Відповідь: Ніколи не починають із ядра системи (Core Domain — наприклад, розрахунок балансів чи оформлення замовлення). Правильна стратегія вибору:
- Низька зв’язність (Low Coupling): Модуль із мінімальною кількістю перехресних викликів у коді моноліту.
- Низький бізнес-ризик: Помилка в першій ітерації не повинна призвести до критичних фінансових втрат.
- Ідеальні кандидати для старту: Сервіс сповіщень (Email/Push/SMS), сервіс формування PDF-звітів, статичний каталог статей чи довідників, сервіс промокодів. Команда відпрацьовує CI/CD, Kubernetes, мережеву взаємодію та Observability на безпечному компоненті перед переходом до ядра.
3. Що робити, якщо новий сервіс вимагає виконання розподіленої транзакції зі старим монолітом під час перехідного періоду?
Відповідь: Синхронний двофазний коміт (2PC) між новим сервісом та монолітом непридатний через ризики дедлоків та несумісність драйверів. Рішення: Використання патерну Saga з участю моноліту:
- Моноліт доопрацьовується для надання компенсувальних API (наприклад, метод
POST /orders/{id}/cancelдля повернення статусу). - Новий сервіс бере на себе роль Saga Orchestrator, керуючи кроками через черги подій.
- Якщо операція в моноліті падає, оркестратор викликає компенсувальну дію у власній базі даних.
4. Чи обов’язково розпилювати моноліт до останнього рядка коду (Правило 80/20)?
Відповідь: Ні. Розпил моноліту — це інженерно-економічне рішення, а не догма. Зазвичай перші 80% функціоналу (високонавантажені модулі, що активно розвиваються командами) переносяться в мікросервіси за рік-два. Залишок у 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) із запобіганням циклічній реплікації.
- Валідація: Traffic Shadowing (тіньове дублювання запитів) + Diff Engine.
- Старт міграції — зі слабкозв’язаних і низькоризикових модулів (сповіщення, звіти).
Червоні прапорці (Чого говорити НЕ МОЖНА):
- ❌ «Найкращий підхід — заморозити розробку на рік і повністю переписати моноліт з нуля» (Класична катастрофа Big Bang).
- ❌ «Для переносу даних моноліт повинен писати одночасно у дві бази в одній транзакції
@Transactional» (Dual-Write ламає консистентність при будь-якому мережевому збої). - ❌ «Розпил слід починати з найскладнішого серцевинного фінансового модуля» (Паралізує бізнес на першому ж релізі; починають із периферійних модулів).