Шаблон Strangler Fig: безопасный способ миграции монолитных приложений
В современной разработке программного обеспечения устаревшие монолитные приложения являются распространенной проблемой. Со временем успешная кодовая база становится настолько большой и взаимосвязанной, что внесение простых изменений становится рискованным, развертывание занимает часы, а масштабирование отдельных функций практически невозможно.
Когда команды решают модернизировать свои системы путем перехода на микросервисы, они сталкиваются с важным вопросом: Как нам переписать систему, не нарушив при этом наш текущий бизнес?
Одним из вариантов является переписывание «большого взрыва»: создание новой системы с нуля за закрытыми дверями и переключение всего за один день. Однако это невероятно рискованно и часто приводит к неудаче.
К счастью, есть более безопасная и надежная альтернатива: Шаблон инжира-душителя. В этом руководстве мы рассмотрим, что такое шаблон Strangler Fig, почему он работает и как его применять шаг за шагом, используя понятные диаграммы и реальный код.
Реальная аналогия: инжир-душитель
Узор назван в честь инжира-душителя, растения, произрастающего в тропических лесах.
Семя инжира-душителя прорастает в верхних ветвях существующего дерева-хозяина. Вместо того, чтобы расти снизу вверх, инжир растет вниз:
- Он пускает корни вниз по стволу дерева-хозяина, пока они не достигнут лесной подстилки и не закрепятся в почве.
- Со временем все больше корней вырастают, обвиваются вокруг хозяина и сливаются вместе.
- У инжира растут листья, которые блокируют попадание света на дерево-хозяин.
- В конце концов, дерево-хозяин умирает и гниет, оставляя на своем месте пустую смоковницу-душитель.
В архитектуре программного обеспечения устаревший монолит — это дерево хостов, а новые микросервисы — это «душитель». Мы создаем новые сервисы по краям монолита, постепенно отводя трафик от устаревшей системы до тех пор, пока монолит не будет полностью отключен.
Почему переписывание «Большого взрыва» провалилось
Прежде чем погрузиться в механику паттерна «Душитель Фиг», давайте поймем, почему альтернатива — полное переписывание — настолько опасна:
- Месяцы (или годы) не имеют значения: разработчики тратят много времени на написание кода, но ничего из этого не запускается до тех пор, пока не будет завершен весь проект.
- Увеличение объема: за два года переписывания бизнес-потребности изменились. Цель перемещается, и новая система должна поддерживать функции, которых не было на момент начала переписывания.
- Отсутствует неявное поведение: Монолиты содержат годы недокументированных исправлений ошибок и обработки крайних случаев. При переписывании с чистого листа эти детали часто забываются.
- Высокий риск развертывания: одновременное отключение массивной устаревшей системы и включение новой создает огромный радиус взрыва, если что-то пойдет не так.
Как работает паттерн «Душитель»
Основная идея паттерна «Душитель Фиг» — постепенная миграция. Вместо миграции всей системы вы переносите по одной небольшой функции или «фрагменту» за раз.
Процесс миграции выполняется в пять ключевых этапов:
1. Определите ограниченный контекст
Посмотрите на свой монолит и определите единую, автономную бизнес-возможность, которую легко извлечь. К хорошим кандидатам относятся:
- Функции, которые часто меняются (поэтому команда быстро получает выгоду от независимого развертывания).
- Простые функции с низким уровнем риска (например, статические часто задаваемые вопросы или раздел пользовательских настроек) для тестирования конвейера миграции.
- Функции с четкими, четко определенными границами базы данных.
2. Реализация нового микросервиса
Создайте выявленную возможность в виде совершенно нового современного микросервиса. Этот сервис имеет собственную базу данных, собственный конвейер развертывания и построен с использованием современных стеков технологий. Важно отметить, что старая функция монолита пока остается активной и неизменной.
3. Введение уровня перехвата
Чтобы сделать миграцию прозрачной для ваших пользователей, вы добавляете Уровень перехвата (например, шлюз API или обратный прокси-сервер) перед приложением. Весь клиентский трафик теперь сначала направляется на этот шлюз.
Первоначально шлюз направляет 100 % всех запросов к устаревшему монолиту.
4. Постепенный переход трафика
После того как новый микросервис будет полностью протестирован и готов, вы обновите правила маршрутизации на уровне перехвата. Вместо маршрутизации запросов на перенесенную функцию (например, /api/users) в монолит, шлюз перенаправляет их в новый микросервис.
Все остальные запросы продолжают идти к монолиту. Если новая служба выйдет из строя или обнаружит ошибки, вы можете быстро обновить шлюз, чтобы направить трафик обратно в монолит, гарантируя минимальные сбои.
5. Вывод из эксплуатации и повторение
После того как новый микросервис будет работать стабильно в течение определенного периода времени, вы можете безопасно удалить соответствующий код внутри устаревшего монолита.
Затем вы выбираете следующую функцию и повторяете процесс. Со временем монолит сжимается до тех пор, пока в нем не останется трафика, и вы сможете полностью вывести из эксплуатации устаревший сервер.
Уровень перехвата: пример экспресс-маршрутизации
Сердцем фигуры «Душитель» является Слой перехвата. Он позволяет перенаправлять запросы без изменения клиентских приложений (веб-приложений, мобильных приложений).
Вот практический пример Node.js с использованием прокси-сервера шлюза на основе Express. Он маршрутизирует запросы динамически: входящие запросы передаются новым сервисам, если они соответствуют перенесенным путям; в противном случае они возвращаются к унаследованному монолиту.
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const PORT = 8080;
// Configuration: Target server URLs
const LEGACY_MONOLITH_URL = 'http://legacy-monolith-server:3000';
const NEW_USER_SERVICE_URL = 'http://new-user-service:3001';
const NEW_PAYMENT_SERVICE_URL = 'http://new-payment-service:3002';
// Simple logging middleware to track traffic distribution
app.use((req, res, next) => {
console.log(`[ROUTE LOG] Incoming request: ${req.method} ${req.url}`);
next();
});
// 1. MIGRATED: User registration & profile requests route to the new microservice
app.use('/api/users', createProxyMiddleware({
target: NEW_USER_SERVICE_URL,
changeOrigin: true,
pathRewrite: {
'^/api/users': '/v1/users', // Translate path format if necessary
}
}));
// 2. MIGRATED: Payment transactions route to the new payment microservice
app.use('/api/payments', createProxyMiddleware({
target: NEW_PAYMENT_SERVICE_URL,
changeOrigin: true
}));
// 3. FALLBACK: All other legacy routes automatically default to the Monolith
app.use('/', createProxyMiddleware({
target: LEGACY_MONOLITH_URL,
changeOrigin: true
}));
app.listen(PORT, () => {
console.log(`Interception Gateway routing traffic successfully on port ${PORT}`);
});
Работа с базой данных: самое сложное
Хотя маршрутизация трафика API относительно проста, управление данными является наиболее сложным аспектом монолитной миграции. Монолит обычно имеет одну массивную базу данных, таблицы которой тесно связаны между собой.
При извлечении службы необходимо также извлечь ее данные. Существует два распространенных подхода к решению этой проблемы:
- Двойная запись. Уровень перехвата или приложение записывает данные как в устаревшую базу данных, так и в новую базу данных службы одновременно на этапе перехода. Это обеспечивает синхронизацию обеих баз данных.
- Отслеживание измененных данных (CDC). Такой инструмент, как Debezium, отслеживает журнал транзакций устаревшей базы данных и автоматически передает изменения в новую базу данных микросервисов практически в реальном времени.
Убедившись, что базы данных полностью синхронизированы и новая база данных корректна, вы переключаете трафик чтения на новую службу и закрываете устаревшие таблицы.
Сравнение: Big Bang Rewrite и Strangler Fig
| Функция/показатель | Переписывание Большого Взрыва | Узор «Душитель» с инжиром |
|---|---|---|
| Уровень риска | Чрезвычайно высокий | Низкий и управляемый |
| Петля обратной связи | Очень медленно (только в конце) | Быстрое (непрерывное производственное тестирование) |
| Стратегия отката | Жесткий (требует восстановления резервных копий) | Легко (изменить правила маршрутизации в шлюзе) |
| Влияние на бизнес | Подрывной | Нулевое время простоя |
| Сложность системы | Высокий (на этапе сборки) | Высокий (на этапе миграции) |
| Время развертывания | Массовый релиз сингла | Небольшие, частые обновления |
Заключение
Шаблон Strangler Fig — это отраслевой стандарт для миграции монолитов на микросервисы. Заменяя устаревший код постепенно, а не весь сразу, вы исключаете риск «большого взрыва».
Он обеспечивает небольшой размер развертываний, обеспечивает мгновенную обратную связь с реальным производственным трафиком и позволяет вашей команде продолжать приносить пользу бизнесу на протяжении всего жизненного цикла миграции.
Хотя управление миграцией баз данных и запуск двух параллельных систем усложняют эксплуатацию, безопасность, предсказуемость и стабильность, которые оно обеспечивает, делают его предпочтительным выбором для современной миграции в облако.
Узнайте больше о разработке программного обеспечения и серверной части в блоге Ghaznix →