Привет! Меня зовут Дима, я архитектор в Купере. Хочу сегодня рассказать о шаблоне проектирования Strangler, который мы использовали для выноса бизнес-логики из монолитной системы в отдельный сервис.
Сначала обратимся к первоисточнику, а затем перейдем к практическим моментам, с которыми столкнулись в процессе работы.
История Strangler-паттерна
Впервые тремин сформулировал Мартин Фаулер после того, как он увидел особый вид фикусов, который поселяется на верхних ветвях деревьев. Постепенно растение спускается к земле, пускает корни, и, обволакивая дерево, использует его в качестве временной опоры и источника питательных веществ. В последнвии дерево превращается в труху, а фикус к тому моменту становится самостоятельным.
Проецируя метафору на разработку ПО, Фаулер предлагает осуществлять миграцию критически важных систем по эволюционному сценарию: постепенно создавать новую систему на базе старой, позволяя ей медленно расти в течение некоторого времени, пока первая не будет исключена («задушена» в терминологии автора).
В статье Фаулер называет такой подход Strangler Fig App, иллюстрируя его фотографией фикуса. Забегая вперед, скажу, что мне он скорее представляется домиком на дереве, поэтому прилагаю свое фото:
Такой подход снижает риски:
за счет возможности поступательной миграции на новую систему небольшими шагами, каждый из которых легко обратим. Вплоть до того, что миграцию мы можем ставить даже на паузу;
за счет возможности при любом исходе откатится в базовую конфигурацию (монолит).
Для простоты будем называть подход Strangler-паттерном.
Strangler-паттерн для декомпозиции легаси-систем
Подробно использование подхода для декомпозиции монолитов описал Сэм Ньюман в книге «От монолита к микросервисам», предлагая три шага:
Выявить функционал, который мы хотим перенести.
Имплементировать его в новом сервисе.
Переключиться на новую реализацию.
Под выявлением функционала автор понимает исследование логики на возможность ее локализации и процесс предварительного рефакторинга. На откуп читателям остается решение, как осуществить вынос: переписать все с нуля в новом сервисе, либо отрефакторить и портировать код. После чего переводить пользовательский трафик на новый сервис.
Использовать паттерн разумно в случаях, когда:
монолитная легаси-система представляет из себя черный ящик;
нет возможности вносить в нее изменения (либо мы этого не хотим, например, долгий релизный цикл).
На эту тему в книге есть забавная история: как-то автор работал над декомпозицией большой легаси-системы, старым коробочным решением, которое установили в организации. Реверс-инжениринг скомпилированной программы – дело неблагодарное, поэтому, чтобы хоть как-то разобраться в бизнес-логике, он связался с автором решения и просил предоставить исходный код. Для этого готов был дать любые гарантии о нераспространении, но автор кода ему отказал.
Через несколько лет Ньюман встретил автора системы на конференции и спросил, почему тот не выслал исходники, ведь никаких рисков не было. Автор неловко ответил, что исходные коды системы он просто потерял.
Это крайний и вырожденный случай. В реальности же мы в основном имеем дело с доступными для просмотра/изменений, хотя код и бывает достаточно запутанным.
Изначальные условия
Цель статьи — поделиться опытом использования Strangler-паттерна. Но сначала дам чуть больше контекста, чтобы пояснить, почему мы выбрали такой путь.
1. Большой Rails-монолит. Но одна из его частей, очень нагруженная, приводила к сбоям по причине нехватки ресурсов БД. Тяжелые запросы выводили из строя реплики БД, ставя под угрозу общее функционирование всей системы.
2. Сам функционал — это ряд связанных эндпоинтов поиска и фильтрации, доступных пользователю исходя из его геопозиции магазинов для доставки. Это упрощало задачу, так как первый пункт в плане от Сэма Ньюмана был фактически выполнен.
3. На тот момент у нас уже был проведен Event Stroming и выделен ограниченный контекст и его границы. Обьем выносимой бизнес-логики был понятен.
4. Речь шла о части бизнес-логики критического пользовательского пути (не выбрав магазин, невозможно положить товар в корзину и оформить заказ). Поэтому приоритетом был фокус на стабильности. Не было опции просто переключить трафик на новый сервис и надеяться, что все будет хорошо. Как стало ясно позже: хорошо получилось совсем не сразу.
Для использования Strangler-паттерна условия идеальные:
мы не хотим вносить изменения в монолит (долгий релизный цикл), а сразу фокусируемся на переносе бизнес-логики из него в новый сервис;
эндпоинты заместим целиком, поскольку бизнес-логика в них практически локализована (оказалось не так);
будем держать фокус на стабильности — в этом Strangler-паттерн как раз хорош!
Теоретический план миграции
Итеративный, поступательный и безопасный (в общем, очень хороший ?) план миграции бизнес-логики из монолита в отдельный сервис был составлен:
Начинаем с проксирования трафика через новый сервис. А дальше, «паразитируя» на трафике, сможем развернуться (режим проксирования).
В параллель запускаем бизнес-логику в новом сервисе и постепенно переносим ее в новое место (режим зеркалирования).
Заменяем старую реализацию на новую (режим замещения).
И вероятно в финале придем к схеме обогащения ответа в легаси системе (режим композиции API).
Разберем шаги подробней.
Проксирование трафика
С API-гейтвея пускаем пользовательский трафик в новый сервис. Он ничего не делает, а только проксирует запросы в монолитную систему. Это шаг позволяет начать исследование бизнес-логики и способов использования монолитных эндпоинтов.
Зеркалирование бизнес-логики
Это режим, в котором запросы выполняются параллельно и в сервисе, и в монолите.
На этом этапе производим перенос бизнес-логики в сервис. Чтобы понять, насколько одинаково работает старая и новая реализации, сравниваем ответы из монолита и сервиса, рассчитывая метрику их совпадения (метрика точности). Тут происходит поэтапный вынос бизнес-логики, что в итоге дает высокие значения метрики точности.
Важно, что клиенту возвращается результат запроса из монолита, поэтому зеркалирование не влияет на пользователей.
Замещение функционала
Это целевой режим миграции на новый эндпоинт в отдельном сервисе: запросы обрабатываются только в сервисе. На этом этапе предполагается, что монолит будет исключен из схемы за ненадобностью (в теории).
Сам процесс замещения плавный и постепенный. С помощью набора фича-флагов мы можем гибко управлять долей трафика между режимами зеркалирования/замещения. С одной стороны, это позволяет отслеживать метрику точности на реальном трафике, с другой, адаптировать в процессе новый сервис под профиль нагрузки.
Композиция API (опционально)
Композиция API – схема, при которой бизнес-логика срабатывает в новом сервисе, а итоговый ответ для клиента частично обогащается в легаси-системе.
Необходимости в этом шаге может не быть, если новый API-сервиса локализован и не возвращает данные за рамками своей предметной области. В нашем случае это было не так: необходимых данных было недостаточно, и пришлось частично обогащать ответ в монолите. Для этого нужно было внести в него измерения.
Выбор сводился к двум вариантам:
скомпозировать API и сохранять совместимость;
реализовать новый более идиоматичный с архитектурной точки зрения API и перевести клиентов на него.
Из-за невозможности обновления мобильного приложения в тот момент пришлось выбрать первый.
План миграции: симбиоз паттернов и подходов
Новый план в деталях далеко ушел от оригинального Strangler-паттерн, хотя основная идея сохранена. Получилась ситуационная подборка различных методов и подходов, которая включает в себя ряд особенностей:
API-gateway паттерн: переключение трафика предполагает, что в системе есть API-гейтвей или другой вариант реверс-прокси, чтобы управлять трафиком.
Authentication: нужно уметь аутентифицировать трафик, так как от этого зависит бизнес-логика. Об этом я подробнее писал в статье об аутентификации трафика.
Паттерн «Параллельное исполнение»: Очередная идея позаимствована из книги Ньюмана: когда новый и старый функционал работают в параллель. А у нас есть возможность сравнить результаты обоих исходов. Наверняка вы знакомы с библиотекой scientist, которая предназначена для безопасного рефакторинга кода. Это примерно то же самое, только для рефакторинга монолита в сервис.
Еще несколько подходов к управлению и миграцией данных из четвертой главы книги Сэма Ньюмана: бизнес-логика сервиса базируется на данных, владельцем которых является другой сервис или монолит.
Практика
Начинаем идти по плану.
Проксирование, первые проблемы
Ранее я назвал режим проксирования паразитным: еще ничего полезного не сделано, а уже увеличивается связность системы. Из-за нового посредника возрастает латенси запросов от сетевых вызовов. Улучшить ситуацию помогло включение персистентного клиента для Ruby, который переиспользует установленное TCP-соединение. Но до момента полного отказа от легаси-системы этого не избежать, так что считаем ситуацию нормой.
Проксирование очень чувствительно к капасити веб-сервера (Puma для Ruby), особенно под нагрузкой, когда монолит начинает медленней отвечать, запросы скапливаются в прокси. Остро проблема проявлялась на нагрузочных тестах. Пришлось тонко настроить автоскейлинг, выделяя большее капасити, и уменьшить таймаут ожидания ответа.
Уже при проксировании начали проявляться преимущества эволюционного подхода: мы имели возможность итерациями переводить трафик (даже для режима прокси) на новую систему, минимально сокращая негативное влияние на клиентов.
Многогранная бизнес-логика монолита
Всю полноту бизнес-логики легаси-системы объять невозможно. Рано или поздно найдется код под тремя фича-флагами и под кучей вложенных условий, который неожиданно сработает.
В сервисе с самого начала хотелось делать все красиво, не вынося избыточную бизнес-логику. Мы провели исследование кода и выявили, на каких входящих параметрах базируется бизнес-логика, ввели вайт-лист HTTP-заголовков, которые прокидываем при проксировании.
Ночью успешно переключили трафик на новый сервис, а с утра узнали, что старые мобильные клиенты лишились части функционала из-за фильтрации HTTP-заголовков, на которые опиралась многогранная бизнес-логика.
Переключились обратно, поправили, снова включили.
Запасаем патроны
Метод внимательного разглядывания кода монолита подвел. А получать новые знания о легаси-системе через инциденты и влияние на пользователей не хочется.
Уберечь от проблемы мог бы регресс функционала, желательно автоматический. Совсем идеально использовать реальные профили запросов с настоящими атрибутами (query-параметры, http-заголовки), влияющими на бизнес-логику.
Проанализировав логи, мы собрали исторические данные о запросах пользователей и назвали их «патронами». С их помощью стреляли в продакшен «как пользователи» и делали регресс, сравнивая результаты одинаковых запросов на сервис и монолит.
Выявить значимые параметры от незначимых также можно с помощью патронов: делаем несколько итераций регресса с усеченным набором патронов. Если ответы не совпадают, значит поведение на усеченном наборе отличается, и отброшенный параметр был важный.
Статистически значимая выборка патронов и простой скрипт сравнения результатов закрыли проблему регресса функциональности, причем автоматического.
Мы успешно преодолели первый шаг плана миграции по Strangler-паттерну и перешли к самому интересному – зеркалированию.
Зеркалирование
Зеркалирование – это этап переноса бизнес-логики, который предполагает исполнение логики в сервисе в параллель с монолитной основной.
Но невозможно перенести то, чего не знаешь: мы имели представление о базовых сценариях, но всю полноту и многогранность бизнес-логики не понимали.
На этом шаге предстояло:
разобраться в логике оригинальных эндпойнтов;
перенести ее в новый сервис;
провалидировать, что ничего не сломалось.
Не решаем все проблемы сразу
В новом сервисе хочется решить все проблемы сразу: отрефакторить код, исправить огрехи в бизнес-логике и починить найденные уязвимости, сменить стек реализации. Ведь просто переносить легаси-код из одного сервиса в другой – некрасиво и скучно!
Во второй главе книги Ньюман предупреждает о губительности такого подхода и предлагает неукоснительно следовать простому правилу. Цель миграции на микросервис должна быть только одна. Губительно для проекта не только отсутствие целей, но и их множество.
Бывает так, что к основной цели добавляют еще несколько побочных, к примеру:
Переход на другой стек;
Исправление ошибок и уязвимостей;
Улучшение перформанса;
Фикс багов;
Любые другие изменения/улучшения.
В нашем случае цель – стабилизировать проблемную часть монолита за счет переноса нагрузки на новый сервис.
Казалось бы, почему нельзя сразу перейти с Ruby на Golang? Но, чтобы перенести бизнес-логику, ее нужно понимать. Смена стека, по моему мнению, дает дополнительную когнитивную нагрузку и отнимает часть мыслительных ресурсов, которые вам обязательно понадобятся, чтобы распутать легаси-код.
Подробней об этом я писал в своем телеграм-канале.
В процессе мы поняли, что, если не зафреймится только на миграции бизнес-логики и пытаться делать все сразу, проект никогда не закончится (вспомним вайт-лист HTTP-заголовков).
Бизнес-логика: ожидание-реальность
Когда вы заивентштормили бизнес процессы, расписали и обособили баундед контексты – все складно и красиво: независимые сервисы, описывающие локализованные бизнес-процессы, связаны между собой асинхронно…
На практике большая часть из этого не существует ни в виде выделенных сервисов, ни в виде локализованных модулей в монолите. А выделяемая бизнес-логика базируется на проекциях данных, источником которых служит несуществующий ныне автономный ограниченный контекст. Только он в монолите сейчас! В общем, все на все завязано.
Зависимость от данных
Первые попытки вытянуть часть функционала из монолита блокируются отсутствием в новом сервисе необходимых для работы бизнес-логики данных. Проблема не в самом отсутствии данных (они есть в монолите), а скорее в том, что нет формализованных источников (ни Kafka-топиков, ни реализованных эндпойнтов) для их получения. Назовем это явление missing-data-sources. Подробней о подходах к проблеме я писал в другом посте.
Особо остро проблема стоит при выезде первых сервисов из монолитного приложения, когда все необходимые источники данных находятся в монолите. С постепенным разъездом на сервисы появляются новые кафка-топики, которые частично покрывают потребности.
В процессе работы с отсутствующими источниками данных мы выделили три основных подхода к решению проблемы (подробный пост в телеграм-канале). По возрастанию степени адекватности перечислю их.
Синхронный поход в монолитную систему
Самое очевидное – ходить в монолитную систему синхронным способом и запрашивать интересующие вас данные. Плохо, потому что синхронно и не идиоматично. Хорошо, так как быстро и дешево.
Асинхронный способ
Два других варианта – перейти на асинхронный способ доставки данных, например, через Kafka-топик. И в зависимости от возможности проработать его контракт. Это будет либо временный переходный топик (с которого тоже нужно будет сьехать), либо проработанный и согласованный с потенциальными владельцами данных топик с полноценным идиоматичным контрактом.
В случае нечеткой проработки контракта может получиться, что сам топик будет иметь «монолитный характер». Например, содержать информацию из нескольких предметных областей, и в последующем нужна будет его декомпозиция.
Во втором случае нужно большее вовлечение владельцев части монолита, которая служит источником данных. Но можно согласовать идиоматичный контракт, который при выезде сервиса сохраняется (но это не точно).
Синхронизация на ручном приводе
Все три варианта – это моя интерпретация подходов, которые Сэм Ньюман описал в четвертой главе книги (в списке представлены их названия).
Четвертый вариант: если данные статичны или изменяются редко, их вообще можно не синхронизировать, а копировать. В этом случае лучше заблаговременно согласовать с овнерами данных совместные действия на случай изменения в первоисточнике (например, овнеры обновляют и в копиях). Такая "Kafka на ручном приводе" с соответствующими «гарантиями ручного привода».
Единственного правильного подхода к синхронизации данных нет. Мы исходили из ресурсной возможности сделать максимально асинхронно и возможности потенциальных овнеров данных вовлечься в процесс согласования контракта Kafka-топика.
В итоге для переноса бизнес-логики нам понадобилось реализовать несколько мини-проектов по запуску соответствующих источников данных% где-то на базе монолита, где-то на базе сервисов. Где-то пришлось скопировать данные, где-то перенести владение ими в сервис.
Объем работы над этим подготовительным этапом занял большую часть времени.
Деградация бизнес-логики
Это мой любимый вариант миграции бизнес-логики – через ее удаление.
По мере того, как мы разбирали нюансы исторической бизнес-логики, натыкались на древние и сомнительные артефакты.
К примеру, однажды наткнулись на сомнительную бизнес-логику. Сложность формулировки задачи однозначно указывала на ее узкую применимость. А расследование показало, что заказчика у фичи не было. Автор задачи в JIRA и исполнитель уже не работают в компании.
Максимально выгодно избавиться от такой бизнес-логики заранее, чтобы не переносить ее в новый сервис. Лучший вынесенный код из монолита – выпиленный.
Чтобы подтвердить или опровергнуть гипотезу, пришлось вкатить метрики на количество срабатываний этой части кода. За две недели – ни одного срабатывания.
Итого – фича не нужна и не работает. Значит ее можно не переносить! Значит зависимости от дополнительных данных нет!
Повезло? Но заметьте, чтобы выпилить код, пришлось написать код, который измеряет количество срабатываний. Как говорится: «Больно? Зато бесплатно».
Продуктовые блокеры
В дальнейшем кейсы со сложными взаимосвязями бизнес-логики, препятствующими простому портированию в сервис, мы начали называть продуктовыми блокерами.
Продуктовый блокер – это элемент бизнес-логики, полноценное сохранение которого при миграции проблематично и требует дополнительных усилий. В одном случае решением может быть реализация источников необходимых данных, в другом – временный или полный отказ от части функционала.
Вопрос, как поступить с продуктовым блокером, дискуссионный. Он, с одной стороны, упирается в технические ограничения (насколько долго сохранить функционал), с другой, – в продуктовые (влияние на бизнес-метрики продукта, конверсия, GMV и тд). Каждый случай требует изучения и согласования.
Продуктовый тех долг
Продолжая работать над переносом бизнес логики, мы вышли на другой класс проблем – это фича-флаги, A/B-тесты и другие элементы кодовой базы монолита, которые увеличивают вариативность кода.
А в процессе переезда нам бы хотелось бы максимально заморозить бизнес-логики, чтобы не делать лишнюю работу и не сталкиваться с отличием поведения новой и старой реализации из-за рассинхронизации значений фича-флагов или настроек А/B-тестов.
Такие моменты мы классифицировали как продуктовый тех долг: согласовывали с овнерами отказ от использования флагов, выпил кода или, на крайний случай, ручной процесс синхронизации фича-флагов в двух местах. Принцип такой же: чем меньше кода мы переносим и чем более он детерминирован, тем меньшее количество проблем будет в процессе.
Метрика точности
Как убедится, что бизнес-логика перенесена в сервис в полном объеме и ничего не потеряно?
Нужен способ валидации качества и полноты миграции. Им стали метрики точности. При зеркалировании мы сравниваем ответ от монолитной системы с результатом работы перенесенного кода.
Метрика точности – это процент совпадения результатов: насколько бизнес-логика в сервисе совпадает с бизнес-логикой в легаси-системе. А на это влияет полнота и точность ее переноса, а так же актуальность данных, которые мы проливаем в сервис.
На старте, когда никакой бизнес-логики в сервисе нет, значение метрики точности равно нулю.
По мере переноса бизнес-логики значение увеличивается. Большой рывок происходит при переносе основного функционала. Мы срываем низко висящие фрукты – быстрыми темпами переносим основной фукционал.
А дальше сложней, поскольку увеличение метрики точности происходит анализом несовпадений конкретных результатов и поиском эйдж-кейсов, которые мы не учли.
Разбор эдж-кейсов требует большего погружения в особенности бизнес-логики. Время, затрачиваемое на поиски проблем, увеличивается: нужно исследовать, выявлять нюансы и исторические подоплеки, привлекать экспертов.
Наступает момент, когда дальнейшее увеличение точности становится нецелесообразным. В таком случае стоит прекратить, согласовав на перед с продуктом, на какое несовпадение вы готовы.
Выявили три причины, которые влияют на метрику точности:
неполнота перенесенной бизнес-логики (не учли/не вынесли какой-то эйдж-кейс в коде);
неактуальность данных (не все события приходят через топики, отчего вычисления в сервисе производятся на отстающих данных);
эффект асинхронности (частный случай второго).
О последней причине расскажу подробней.
Эффект асинхронности (eventual consistency)
Если в монолите любые изменения данных в БД происходят одномоментно (в одной транзакции), то при переходе в асинхронную архитектуру на такое рассчитывать не приходится – данные приходят с задержкой. К примеру, если у консюмера образуется лаг и происходят задержки при обработке и обновлении данных в сервисе, то вычисления производятся на отстающих данных. В критические моменты больших лагов видна явная корреляция провала метрик точности с ростом лага консюминга. Этот эффект называют Eventual Consistency. Он предполагает, что данные в конечном счете в распределенных системах согласованы, но в моменте могут различаться. Это плата за обеспечение высокой доступности.
Что делать с этой проблемой?
Не называть это проблемой – это эффект перехода в независимые сервисы. Мы должны принять этот эффект и научиться с ним работать, в том числе продуктово.
Оптимизировать прием данных, снижая скорость задержки. В докладе про инбокс-аутбокс (видео, слайды) паттерн я рассказывал, что мы для этого делали.
Как бы мы ни хотели, 100% точности ответом мы не достигнем. Поэтому согласовали с продуктом значение 98-99% как приемлемое и закончили перенос бизнес-логики в сервис.
Плавная раскатка
Даже в режиме зеркалирования мы не можем позволить себе включить его сразу на 100% запросов, потому что не уверены, какой объем ресурсов вынесенная бизнес-логика потребует в отдельном сервисе.
Другое дело – плавно включать зеркалирование и оценивать, оценивать ситуацию, по необходимости подстраивать систему под нагрузку, настраивать авто скейлинг.
Кстати, помните о патронах для автоматического регресса? Ими мы зарядили K6 для приближенного к реальности нагрузочного тестирования.
Замещение бизнес-логики
Все готово: бизнес-логика перенесена и работает в параллель с основной, точность нас устраивает и согласована в продуктом. Впереди самый главный регресс – на настоящих пользователях.
И плавное переключение в режим замещения сразу выводит нас на новые, неучтенные эйдж-кейсы. Они редкие, и метрика точности (в рамках согласованной погрешности) их не улавливает. При этом эти эйдж-кейсы могут быть значимыми в плане бизнес-велью. Так и случилось.
Мы несколько раз откатывались на предыдущий шаг, чтобы разобраться. Как говорится, топор своего дорубится.
Ретроспективно я отметил несколько важных моментов:
Возможность откатиться на предыдущий шаг ни раз выручала, помогая снизить риски и в спокойном режиме исследовать проблему (не доводя до инцидента);
Количество откатов в процессепревысило все ожидания. В этом плане откат стал рабочим инструментом, нежели нештатной ситуацией;
Численная основа формулы метрик точности – это трафик от пользователей, поэтому распространенные случаи она выявляет хорошо. Но редкий не значит не важный.
На этом этап замещения был пройден.
Обогащение
Хоть бизнес-логику и удалось вынести, с частью данных, которые отдавали эндпоинты, совладать не получилось: замещаемые ручки отдавали данные, концептуально относящиеся к другим контекстам (да еще и к нескольким). Тянуть эти данные в сервис не хотелось, и, чтобы сохранить совместимость API, мы перешли к схеме «обогащения».
Бизнес-логика выполняется автономно, а для рендеринга полноценного ответа задействуется монолит. В перспективе такой техдолг может переехать на BFF (backend-for-frontend) либо быть устранен через миграцию клиента на новый API. Но это не быстро.
Схема сильно связанная и не изящная, но временная и необходимая. Поэтому ее стоит принять как нормальную стадию процесса миграции. Тем более, что сам Фаулер описывает использование Strangler-паттерна как долгосрочное: подход гибкий и предполагает паузы в эволюции или процессе удавливания старой системы, порой даже на неопределенный срок.
На этапе обогащения (композиции API) мы закончили, цель достигнута – нагрузка с монолита была перенесена на сервис!
Ретроспектива
Strangler-паттерн – рабочий вариант, который позволил сфокусироваться на надежности. Мы не раз откатывали изменения на шаг назад и всегда держали в уме возможность временно вернуться на оригинальную легаси-систему или сделать откат на предыдущий шаг. Это добавляло уверенности и позволило двигаться безопасно. В последствии мы несколько раз прибегали к его использованию в других проектах, и чтобы не писать одинаковый код несколько раз, реализовали библиотеку для Ruby – гем sbmt-strangler.
Strangler-паттерн – это надежный, зрелый и сознательный (дедовский) эволюционный подход, что подтверждает опыт его использования. Но процесс миграции требует большего количества времени на реализацию и в два раза больше вычислительных ресурсов. Потому что в моменте будет работать две версии системы.
Strangler-паттерн точно не убережет вас от погружения в дебри легаси бизнес-логики. Это все лишь технический способ миграции, который не поможет решить проблемы с высокой связанностью и запутанностью бизнес-логики в легаси-системе. Разобраться с этим придется при любом раскладе.
Изменения в легаси-систему все равно придется вносить. Несмотря на долгий релизный цикл, изменения в монолите перед выносом могут быть оправданы, потому что экономят недели работы (например, деградация бизнес-логики).
Tech-команда Купера (ex СберМаркет) ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.