Стратегии модернизации легаси
Миграция схем и CDC — Пакетная работа, опорные темы БД. Сверка — SQL для тестировщика.
Когда локальный рефакторинг (статья 3, методы рефакторинга) уже не спасает масштаб — нужна стратегия — как жить с монолитом годами, как выводить функции в новые сервисы, когда допустим полный rewrite (переписывание с нуля). Здесь — архитектурные приёмы и критерии выбора пути.
Назад: безопасные изменения. Итоги: статья 5.
С чего начать, если вы новичок
Представьте старый интернет-магазин — один большой сервер (монолит) на PHP 5, без тестов, автор уволился пять лет назад. Заказчик просит "мобильное приложение и оплату по СБП". Вы не можете за неделю "переписать всё на Go" — магазин должен продавать каждый день. Стратегия отвечает на вопрос: как менять систему по кусочкам, не останавливая бизнес.
| Термин | Простыми словами |
|---|---|
| Модернизация | Улучшение старой системы: код, архитектура, инфраструктура, процессы |
| Монолит | Одно приложение, в котором смешаны заказы, каталог, оплата |
| Микросервис | Отдельная программа с одной зоной ответственности (например, только "пользователи") |
| Маршрутизатор | "Диспетчер" запросов: решает, отдать запрос старому коду или новому |
| Rewrite | Выбросить старую реализацию и написать новую с чистого листа |
| Трафик | Поток запросов пользователей к API или сайту |
Локальный рефакторинг (статья 3) — "починить комнату в доме". Стратегии из этой главы — "перестраивать этажи, пока люди живут в доме" или "строить новый корпус и переводить жильцов".
Пошаговый цикл работы с легаси
Универсальный цикл (повторяется на каждую крупную задачу):
- Оценка риска — стоимость простоя, критичность данных, регуляторика.
- Инвентаризация — код, конфиги, логи, люди, старые репозитории (статья 2).
- Анализ — зависимости, горячие точки, сценарии.
- Защитные тесты — characterization, API, e2e на критичном пути.
- Локальная правка — минимальный контекст, Mikado при необходимости.
- Наблюдаемость и откат — метрики, feature flags, быстрый rollback.
- Фиксация знаний — ADR, диаграмма, обновление runbook.
Шаги 4 (защитные тесты) и 7 (фиксация знаний) отличают управляемую эволюцию от разовых правок "на авось". Без тестов вы не узнаете, сломали ли оплату; без ADR и runbook следующий разработчик снова начнёт с нуля.
Разбор шагов для новичка
| Шаг | Что делают на практике | Пример |
|---|---|---|
| Оценка риска | Спросить: что будет, если релиз упадёт на час? | Интернет-магазин — критично; внутренний отчёт — терпимее |
| Инвентаризация | Список репозиториев, серверов, cron, "кто в курсе" | Выяснили, что биллинг живёт в Excel + скрипте на сервере |
| Анализ | Нарисовать, кто кого вызывает | "Оплата" тянет за собой 12 модулей — выносить последней |
| Защитные тесты | Зафиксировать текущее поведение | Characterization на расчёт скидки VIP |
| Локальная правка | Маленький PR в одной зоне | Вынести расчёт скидки в отдельный класс |
| Наблюдаемость | Метрики, логи, флаг "откатить" | 5% трафика на новый сервис, остальное — монолит |
| Фиксация знаний | ADR: "почему вынесли скидки в сервис X" | Ссылка в Confluence + файл в docs/adr/ |
Strangler Fig (инжир-душитель)
Strangler Fig (буквально "душитель-инжир") — название от тропического растения: лиана обвивает дерево и со временем заменяет его своим стволом. В ПО та же идея — новая реализация постепенно обрастает вокруг старой, трафик и ответственность переключаются по частям, монолит сжимается, пока его можно отключить.
Подробная статья с этапами миграции: Strangler Fig в разделе архитектуры.
Суть по шагам (история "профиль пользователя")
- Сегодня мобильное приложение ходит на
https://shop.example/api/...— всё обрабатывает монолит. - Команда пишет новый сервис
users-serviceс тем же JSON на выходе. - Перед монолитом ставят маршрутизатор (Nginx, Kong, Envoy, облачный API Gateway).
- Правило: запросы
GET/PUT /api/users/*→users-service, всё остальное → монолит. - Пользователь не замечает смены: URL тот же, ответ похож.
- Через месяц выносят "каталог", потом "корзину"; монолит уменьшается.
- Когда в монолите остаётся мало — его выключают или оставляют как архив.
Маршрутизатор — единая "входная дверь". Клиенты (сайт, приложение, партнёры) по-прежнему стучатся в один адрес; внутри запрос перенаправляют.
Обратная совместимость — старые клиенты продолжают работать без срочного обновления: те же коды ошибок, те же поля JSON (или аккуратное версионирование /api/v2).
Пример маршрутизации (Nginx)
# новый сервис забирает только users
location /api/users {
proxy_pass http://users-service:8080;
}
# всё остальное — пока в монолит
location /api/ {
proxy_pass http://legacy-monolith:8080;
}
Параллельный прогон (shadow / compare)
Перед тем как отдавать ответ клиенту из нового сервиса, команду мучает вопрос: "а вдруг новый код считает скидку иначе?"
Shadow-режим (теневой прогон):
- Запрос приходит в маршрутизатор.
- Ответ пользователю по-прежнему формирует монолит (как сейчас).
- Копия запроса тихо уходит в новый сервис; его ответ сравнивают с монолитом в логах или в отдельной системе.
- Расхождения чинят, пока совпадение не станет приемлемым.
- Только потом переключают боевой ответ на новый сервис (часто через feature flag — процент трафика).
Так снижают риск "большого переключения в пятницу вечером".
Strangler — осознанный план выноса, а не разовая обёртка. Сначала выбирают bounded context (ограниченный контекст в DDD): кусок предметной области с понятными границами. Часто первым выносят профиль или справочники — мало связей с оплатой. Оплату и склад оставляют на потом — там самые жёсткие зависимости. Подробнее — Strangler в архитектуре.
Anti-Corruption Layer (слой антикоррупции)
Anti-Corruption Layer (ACL) — "переводчик" между вашим аккуратным кодом и чужой моделью легаси. Слово corruption здесь про заражение домена — если вы тащите в новый сервис поля вроде CustNo, LoyaltyCode = "V" и магические числа, вся новая команда начнёт думать "так и надо" — и технический долг переедет в микросервис.
DTO (Data Transfer Object) — простая "коробка" с полями для передачи по сети, без бизнес-логики.
Правило для новичка — новый домен (заказ, клиент, тариф) описываете своими типами; один модуль-адаптер знает про уродливый SOAP/XML/таблицу TBL_CUST_1998.
| Без ACL | С ACL |
|---|---|
OrderService напрямую парсит XML легаси | OrderService видит только Order и CustomerId |
| Переименование поля в монолите ломает 20 файлов | Правите адаптер в одном месте |
| Тесты требуют поднять весь монолит | Тесты подменяют ILegacySoapClient заглушкой |
Когда новый код обязан говорить с легаси:
- легаси отдаёт "странный" DTO или XML;
- ACL приводит к вашим типам и правилам;
- изменения в монолите локализованы за адаптером.
Код ITЗагрузка примера кода…
ACL часто живёт рядом со Strangler: новый сервис + адаптер к старому API.
Clean room (чистая комната)
Clean room пришёл из микроэлектроники и судебных споров о интеллектуальной собственности (IP): компания A хочет сделать чип "как у B", но без копирования исходников B. Решение — разделить людей на две "комнаты":
- в грязной (dirty) комнате изучают чужой продукт и пишут спецификацию поведения ("при входе X система делает Y");
- в чистой (clean) комнате пишут новую реализацию только по спецификации, без доступа к исходникам чужого кода.
В легаси ту же идею используют, когда старый код токсичен (спагетти, устаревшие паттерны) и копировать его структуру опасно — вы хотите поведение как у старого, но архитектуру новую.
В легаси адаптируют так:
| Зона | Кто | Что делает |
|---|---|---|
| "Грязная" (dirty) | аналитики, reverse engineers | изучают старую систему, пишут спецификации поведения, API, форматы |
| "Чистая" (clean) | разработчики замены | пишут новый код без просмотра исходников старого — только по спецификации |
Чистая комната — это clean room, а не наоборот. "Грязная" — где легаси; "чистая" — где новая реализация без переноса стиля и багов копипастой.
Применимо, когда:
- нужна юридически обоснованная независимая реализация;
- старый код настолько токсичен для архитектуры, что копировать паттерны опасно;
- команда большая и роли можно разделить.
На практике полный clean room редок; чаще — упрощённый вариант — один человек читает легаси, другой проектирует API по документу, ревью ловит "утечку" старых имён в новый код.
Защитный слой с внешним легаси
Если вы не владеете кодом вендора — "защитный слой" у вас на границе:
- строгий контракт (OpenAPI, schema registry);
- таймауты, circuit breaker, идемпотентность;
- логирование запрос/ответ (без секретов) для расследований;
- тесты на вашей стороне по записанным фикстурам.
Это не Strangler внутри монолита, но та же идея: не пускать чужую модель глубоко в домен.
Инструменты (краткий каталог)
Инструменты не заменяют понимание (статья 2), но ускоряют карту местности:
- Зависимости: NDepend (C#), ArchUnit/jQAssistant (Java), dependency-cruiser (JS/TS).
- Качество и долг: SonarQube, CodeClimate, Snyk Code.
- Поиск по коду: Sourcegraph, OpenGrok, CodeQL.
- Декомпиляция: Ghidra, dotPeek, JD-GUI.
- API без исходников: mitmproxy, Charles → черновик OpenAPI в Postman/Stoplight.
- Диаграммы: PlantUML, Mermaid, Structurizr.
Выбирайте 2–3 инструмента под стек, не весь список сразу.
Когда переписывать с нуля?
Полный rewrite (переписывание с нуля) — отключить старую систему и заменить новой за один или несколько больших релизов. Это один из самых рискованных ходов: новая система почти наверняка получит новые баги; старые годами обкатаны бизнесом и регуляторами.
Исторический пример из индустрии: проекты, где команда годами писала "версию 2.0", пока "версия 1" всё ещё приносила деньги — и в итоге v2 опаздывала, а v1 раздувалась. Поэтому по умолчанию выбирают Strangler, а rewrite оставляют для жёстких случаев.
"Давайте перепишем на микросервисы за полгода" — почти всегда повод уточнить — какой кусок первым, как переключим трафик, кто будет поддерживать два контура параллельно.
Без ответов это rewrite без стратегии.
Rewrite чаще оправдан, если
- Технологический тупик — нельзя запустить на поддерживаемой ОС/железе, порт нереален (край: 16-bit DOS).
- Лицензия — нельзя продолжать использовать компонент и нельзя заменить точечно.
- Потеря знаний — восстановление дороже controlled rewrite и бизнес готов к длительному параллельному существованию двух систем.
- Цели бизнеса недостижимы на текущей архитектуре (масштаб, география, регуляторика) даже со Strangler.
В остальных случаях
Предпочтительны Strangler, ACL, рефакторинг с тестами. Даже "старый" монолит можно сужать годами без остановки выручки.
Кривая стоимости изменений (интуиция)
Стоимость правки
^
| **** без тестов / без карты зависимостей
| **
| **___________ после тестов + диаграмм + ACL
+------------------------> время
Инвестиция в тесты и карту окупается скоростью следующих релизов.
Баланс — баги в легаси и новые фичи
Практичное правило для планирования:
- Критичный инцидент — чиним с characterization-тестом, без побочного рефакторинга.
- Фича в легаси-модуле — закладываем время на шов/тест; иначе фича удвоит долг.
- 20% спринта (или отдельный поток) — погашение долга в зоне, которую всё равно трогаете.
В госсекторе и финтехе добавляется слой регламентов: "странный" код может быть следствием аудита, не лени.
Связь с другими разделами
| Тема | Где углубляться |
|---|---|
| Strangler, монолит → микросервисы | design/2125, разделение монолита |
| Тесты, CI | 7-05 Тестирование |
| Техдолг в спринтах | 7-03 Методология |
| Читаемость при правках | 7-10 Культура кода |
Дальше — итоги раздела и чек-лист.