Стратегии модернизации легаси
Когда локальный рефакторинг (статья 3) уже не спасает масштаб — нужна стратегия: как жить с монолитом годами, как выводить функции в новые сервисы, когда допустим полный rewrite (переписывание с нуля). Здесь — архитектурные приёмы и критерии выбора пути.
Назад: безопасные изменения. Итоги: статья 5.
С чего начать, если вы новичок
Представьте старый интернет-магазин: один большой сервер (монолит) на PHP 5, без тестов, автор уволился пять лет назад. Заказчик просит «мобильное приложение и оплату по СБП». Вы не можете за неделю «переписать всё на Go» — магазин должен продавать каждый день. Стратегия отвечает на вопрос: как менять систему по кусочкам, не останавливая бизнес.
| Термин | Простыми словами |
|---|---|
| Модернизация | Улучшение старой системы: код, архитектура, инфраструктура, процессы |
| Монолит | Одно приложение, в котором смешаны заказы, каталог, оплата |
| Микросервис | Отдельная программа с одной зоной ответственности (например, только «пользователи») |
| Маршрутизатор | «Диспетчер» запросов: решает, отдать запрос старому коду или новому |
| Rewrite | Выбросить старую реализацию и написать новую с чистого листа |
| Трафик | Поток запросов пользователей к API или сайту |
Локальный рефакторинг (статья 3) — «починить комнату в доме». Стратегии из этой главы — «перестраивать этажи, пока люди живут в доме» или «строить новый корпус и переводить жильцов».
Вы уже понимаете, где в коде боль (статья 2), и умеете делать маленькие безопасные правки (статья 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 приводит к вашим типам и правилам;
- изменения в монолите локализованы за адаптером.
// Новый домен не знает про LegacyCustomerRecord
public sealed class CustomerAdapter : ICustomerLookup
{
private readonly ILegacySoapClient _legacy;
public async Task<Customer> FindAsync(CustomerId id, CancellationToken ct)
{
var raw = await _legacy.GetCustomerAsync(id.Value, ct);
return new Customer(
id: new CustomerId(raw.CustNo),
name: raw.FullName?.Trim() ?? "Unknown",
tier: MapTier(raw.LoyaltyCode) // странные коды — только здесь
);
}
private static Tier MapTier(string? code) => code switch
{
"V" => Tier.Vip,
"G" => Tier.Gold,
_ => Tier.Standard
};
}
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 Культура кода |
Дальше — итоги раздела и чек-лист.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Два смысла термина, признаки «тихого» и «кричащего» наследия, управляемое и критическое легаси, связь с техдолгом. Реверс-инжиниринг, восстановление контекста из git и людей, диаграммы, анализ бинарников. Рефакторинг, characterization tests, швы (seams), приёмы Фезерса, Mikado, защита от регресса. Итоги раздела «Легаси-код» для новичков: два смысла термина, типы наследия, три опоры (понять, безопасно менять, модернизировать), правила команды и мини-словарь. Вопросы по разделу «Легаси-код» с привязкой к статьям 1–4; темы из других глав энциклопедии помечены отдельно.Что такое легаси и как его узнать
Понимание легаси-системы
Безопасные изменения в легаси
Итоги
Чек-лист самопроверки