Чому «велике переписування» постійно провалюється
Коли legacy-система стає нестерпною, перший інстинкт — заморозити роботу над фічами, переписати все правильно з нуля, а потім перемкнутися. Це зрозумілий інстинкт, і він постійно призводить до провалу з одних і тих самих причин: переписування займає значно більше часу, ніж планувалося, бо крайні випадки старої системи ніде не задокументовані, окрім самого коду; бізнес насправді не може на рік заморозити запити на нові фічі, поки інженери переписують систему; а сам момент перемикання — це одна подія з високими ставками, коли всі приховані відмінності між старою та новою системою одночасно перетворюються на продакшн-інциденти.
Це не означає, що legacy-системи не варто модернізувати. Це означає, що підхід «переписати і перемкнутися» — неправильний інструмент для систем, які продовжують обслуговувати бізнес, поки ви їх виправляєте.
Що таке патерн Strangler Fig насправді
Названий на честь фікуса-душителя (strangler fig) — ліани, яка обвиває дерево-хазяїна і поступово заміняє його, причому саме дерево остаточно не зникає аж до самого кінця. У застосуванні до софту це означає: замість того, щоб переписувати всю систему і перемикатися одразу, ви розміщуєте маршрутизаційний шар перед legacy-системою і поступово переносите трафік на нові, правильно побудовані компоненти — по одному за раз. Legacy-система продовжує обробляти все, що ви ще не мігрували. Під час процесу ніщо ніколи повністю не «падає», і якщо в щойно мігрованому компоненті виникає проблема, ви просто перенаправляєте цей трафік назад на legacy-систему, поки виправляєте її.
Ви не замінюєте систему. Ви замінюєте її частини, по одній, за маршрутизаційним шаром, який робить перехід непомітним для всіх, хто від неї залежить — зокрема, здебільшого, і для вашої власної команди.
Як це відбувається насправді, етап за етапом
- Спершу мапуйте, потім чіпайте. Визначте реальні межі системи — не ті, що на початковій архітектурній схемі, а ті, які код має сьогодні. Саме на цьому етапі виявляється більшість «прихованих вимог», закодованих у legacy-системі, і пропуск цього кроку — головна причина того, що проєкти зі Strangler Fig згодом ідуть не так.
- Розмістіть маршрутизаційний шар попереду. Проксі або фасад, який може направляти конкретний запит або на legacy-систему, або на новий компонент — по маршруту або навіть по клієнту. Саме цей елемент робить безпечним усе інше — без нього ви повертаєтесь до одноразового «великого перемикання».
- Мігруйте спершу частину з найнижчим ризиком. Не найдратівливішу частину, а саме найменш ризиковану — з чіткими межами та невеликим радіусом ураження, якщо щось піде не так. Це підтверджує правильність підходу та маршрутизаційного шару ще до того, як через нього почне проходити щось критичне для клієнтів.
- Запустіть стару і нову версію паралельно та перевіряйте. Направте частину реального трафіку на новий компонент і порівнюйте його вихідні дані з даними legacy-системи для того самого вхідного запиту, перш ніж повністю перемикати цей маршрут. Так виявляються крайні випадки, які legacy-система тихо обробляла, а про них ніхто вже не пам'ятав.
- Повторюйте і тримайте шлях відкату живим. Кожен мігрований компонент має бути можливо перенаправити назад на legacy за лічені хвилини, а не години, — доти, доки залишаються будь-які сумніви. Саме це перетворює «перемикання з високими ставками» на «зворотну зміну з низькими ставками».
- Виводьте legacy з експлуатації лише тоді, коли на неї вже нічого не маршрутизується. Не раніше. Стара система заслуговує на «пенсію» лише тоді, коли від неї справді нічого не залежить — це підтверджується логами самого маршрутизаційного шару, а не припущеннями.
Де команди помиляються
Дві помилки пояснюють більшість зупинених проєктів зі Strangler Fig. Перша — пропустити етап мапування і мігрувати те, що здається найпростішим, що зазвичай означає виявлення критичної незадокументованої поведінки вже на третій частині в продакшені, а не на другій. Друга — ставитися до маршрутизаційного шару як до тимчасового хаку, а не як до справжньої інфраструктури: він потребує такого ж моніторингу, тестування та чергувань on-call, як і будь-яка інша частина шляху запиту, бо на час міграції він і є цим шляхом запиту.
Є legacy-система, яку ніхто не хоче чіпати?
Ми змапуємо, що вона насправді робить — а не те, що написано в документації, — перш ніж щось пропонувати.
Поширені запитання
Скільки часу займає міграція за патерном Strangler Fig порівняно з переписуванням?
У більшості випадків довше в перерахунку на загальний час — але бізнес увесь цей період продовжує отримувати цінність і працювати, замість того щоб ставити все на одну дату перемикання. Важливе порівняння — це не загальний календарний час, а ризик і безперервність роботи.
Чи працює це для системи без тестів і без документації?
Часто це навіть кращий варіант саме через це — етап паралельного запуску та перевірки (маршрутизація реального трафіку і на стару, і на нову систему з порівнянням результатів) стає вашим набором тестів, виявляючи незадокументовану поведінку по ходу справи, замість того щоб припускати, що ви її вже знаєте.
А якщо реальна проблема — це схема бази даних legacy-системи?
Патерн однаково застосовний — маршрутизаційний шар і етапи міграції можуть працювати і на рівні даних, а не лише на рівні запитів, переносячи право власності на дані частинами з тим самим захисним механізмом паралельного запуску.
Цей патерн підходить лише для великих корпоративних систем?
Ні — принцип (поступова заміна за маршрутизаційним шаром із перевіркою перед перемиканням) масштабується як донизу, до одного проблемного модуля в невеликій кодовій базі, так і догори, до цілого моноліту.