Загрузка...

Паттерн «Удушающая смоковница»: эволюционный путь к новой архитектуре без боли и стресса

Strangler Fig Pattern

Когда твой проект живёт уже не первый год, кодовая база разрослась, а каждое изменение напоминает поход по минному полю — это сигнал: пришло время что-то менять.
Но полная переписка — почти всегда провал. Время, риски, несовместимость, десятки регрессий, а потом ещё и «почему это не работает, ведь раньше работало»…

Паттерн Strangler Fig предлагает совсем другой путь: медленно, безопасно и осознанно обновлять систему, сохраняя рабочее состояние на каждом этапе.


🧬 Что такое «Удушающая смоковница» и откуда это название

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

То же самое мы можем сделать с нашей кодовой базой.

Не нужно уничтожать старое приложение — просто постепенно «обрастите» его новым, пока старое само не станет ненужным.


🧩 Проблема: почему просто «переписать» — почти всегда плохая идея

Представь старое приложение, которое:

  • работает в продакшене уже 5+ лет;
  • обслуживает тысячи пользователей;
  • хранит тонны данных;
  • интегрировано с десятками внешних сервисов;
  • и написано на PHP 7.3, с кучей устаревших зависимостей.

Ты хочешь перейти на Laravel 11, добавить микросервисы, Docker, Horizon и всё остальное «по красоте».
Но вот что реально произойдёт при попытке «всё переписать с нуля»:

  • ⚠️ Потеря фич (новая версия не покрывает всё, что было);
  • ⚠️ Миграция данных превращается в боль;
  • ⚠️ Тестирование — сплошная импровизация;
  • ⚠️ Продакшен стоит на паузе, пока всё не готово;
  • ⚠️ Команда делится на “старых” и “новых” — конфликт поколений в коде.

Итог — через 6 месяцев проект всё ещё «почти готов», а бизнес ждёт.

Strangler Fig решает всё это простым принципом:

“Не трогай то, что работает. Замени постепенно”.


🧠 Суть паттерна Strangler Fig

Идея проста:

  • Создаём прослойку между пользователем и старой системой (например, через API Gateway или Reverse Proxy).
  • Для новых частей функционала — направляем запросы в новую систему.
  • Для старых — всё работает как раньше.
  • Когда модуль полностью перенесён — переключаем маршрут.

В итоге новая система растёт рядом со старой, забирая на себя всё больше ответственности.


⚙️ Как это выглядит архитектурно

Пользователь
   │
   ▼
[ Gateway / Proxy Layer ]
   ├── /api/v1/... → Legacy Laravel
   └── /api/v2/... → New Laravel 11 / Microservices

Ты можешь использовать:

  • Nginx / Traefik — если это HTTP-приложение;
  • API Gateway (например, Kong, Laravel Octane Gateway, Laravel Reverb + Routes Proxy);
  • или даже встроенный router middleware в Laravel.

🧩 Пример на Laravel и Nginx

1️⃣ Добавляем слой маршрутизации

server {
    listen 80;
    server_name api.example.com;

    location /api/v2/ {
        proxy_pass http://new-api:8000; # Новый Laravel 11
    }

    location /api/ {
        proxy_pass http://legacy-app:8080; # Старый Laravel 7
    }
}

Теперь:

  • /api/v2/ — обслуживается новым приложением,
  • /api/ — идёт на старую систему.

Можно разворачивать новую архитектуру по кускам, не ломая продакшен.


2️⃣ Создаём новые маршруты

В новом Laravel 11:

// routes/api.php
Route::prefix('v2')->group(function () {
    Route::get('/users', [App\Http\Controllers\V2\UserController::class, 'index']);
});

А старое приложение по-прежнему обслуживает /api/users.

Постепенно фронтенд начинает использовать /api/v2/users, и старый эндпоинт можно выключить.


3️⃣ Переносим бизнес-логику по кускам

Например, сервис работы с пользователями:

Было:

// LegacyUserService.php
public function getAll() {
    return DB::table('users')->get();
}

Стало:

// NewUserService.php
use App\Models\User;

public function getAll() {
    return User::query()
        ->select('id', 'name', 'email')
        ->where('active', true)
        ->get();
}

Постепенно переносим не только код, но и структуру данных — например, добавляем миграции в новую БД.


4️⃣ Используем адаптеры для совместимости

Если фронтенд ожидает старый формат ответа — сделаем адаптер:

// UserTransformer.php
public static function legacyFormat(User $user): array
{
    return [
        'userId' => $user->id,
        'userName' => $user->name,
        'email' => $user->email,
    ];
}

Так новый код может подменить старый, не ломая интерфейсы.


🧭 Этапы внедрения паттерна

Этап Описание Цель
1 Определяем границы старого приложения Понять, что можно изолировать
2 Добавляем gateway / прокси Возможность направлять трафик по маршрутам
3 Переписываем первый модуль Проверка подхода на практике
4 Накатываем метрики и фичефлаги Контроль за переключениями
5 Переходим модуль за модулем Безболезненная миграция
6 Удаляем legacy Чистая архитектура

🧩 Варианты реализации

🔹 HTTP (API)

Самый частый вариант — как в примере выше.
Трафик маршрутизируется по URL.

🔹 Модульно внутри монолита

Можно делать это даже внутри одного Laravel-приложения:

  • Старый код — в app/Legacy/...
  • Новый — в app/New/...
  • В роутинге — решаем, куда направить запросы.

🔹 Через Events / Messaging

В микросервисной архитектуре можно перехватывать события (например, RabbitMQ, Kafka) и обрабатывать их уже новой системой.


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

🧩 1. Выбирайте правильные границы

Не нужно сразу трогать сложные части (например, биллинг или авторизацию).
Начните с модулей, где меньше зависимостей: отчёты, публичное API, внутренние сервисы.

⚙️ 2. Используйте фичефлаги

Laravel Pennant идеально подходит для переключения старого и нового кода:

if (Feature::active('new_profile')) {
    return $this->newProfileHandler();
}

return $this->legacyProfileHandler();

Можно включать новую реализацию только для тестовых пользователей.

🔄 3. Shadow Traffic

Очень крутая техника:
Пусть Gateway отправляет копию реальных запросов в новый сервис (без влияния на пользователя).
Так можно протестировать, что ответы совпадают — до того, как переключишь трафик.

📊 4. Метрики и логирование

Собирайте:

  • количество запросов к каждому маршруту;
  • время ответа;
  • ошибки / исключения.

Инструменты:

  • Horizon, Telescope, Sentry, Prometheus.

🔐 5. Храните данные централизованно

На первых порах лучше, чтобы новый и старый код использовали одну БД.
Когда всё стабилизируется — можно разделить на микросервисы и базы.


🚀 Результаты, которых можно достичь

  • Никакого даунтайма;
  • Всегда рабочее приложение;
  • Постепенная миграция (без шока для команды);
  • Возможность отката;
  • Параллельная работа legacy и нового кода.

Это реальный путь эволюции, а не революции в коде.


🔮 Бонус: связка с другими паттернами

Паттерн Strangler Fig часто работает в паре с другими:

  • Adapter — для сохранения интерфейсов при миграции;
  • Proxy / Gateway — для маршрутизации трафика;
  • Façade — для унификации старого и нового API;
  • Event Sourcing — для плавного перехода на новую модель данных;
  • Feature Flags — для динамического включения нового кода.

Всё это вместе даёт невероятно мощную стратегию безопасной трансформации архитектуры.


🧭 Резюме

Что делает Strangler Fig Почему это важно
Позволяет переписывать частями Без остановки системы
Сохраняет старую логику Нет потери бизнеса
Даёт контроль и метрики Можно замерять эффект
Готовит систему к микросервисам Эволюционный переход
Подходит даже для малого проекта Простая реализация

🌱 Заключение

Паттерн «Удушающая смоковница» — это про зрелость.
Он учит не рушить, а заменять, не спешить, а выстраивать.
Вы не переписываете проект — вы растите новый поверх старого, пока он не станет самостоятельным.

Самый надёжный способ переписать систему — никогда не делать это «в один день».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *