Мы часто проектируем «правильно»: используем проверенные подходы, следуем best practices, применяем паттерны. Но результат всё равно может оказаться далеким от ожидаемого. Архитектура выглядит логичной, решения — обоснованными, а в реальности система становится сложнее, дороже и больнее в сопровождении.
Почему так происходит? Разберём это на реальном кейсе миграции с Legacy-системы — и посмотрим, где заканчивается сила паттернов и начинается суровая реальность.
Краткое содержание статьи:
Как проектировать правильно?
К чему может провести правильное проектирование на примере истории миграции?
Почему получилось что-то «неправильное»?
Выводы.

Как проектировать правильно?
Представим, что мы участники команды разработки и на этапе своего становления IT-специалистами думаем о том, как решать разного рода задачи, в ходе которых мы учимся на ошибках.
Реализовали по нажатию кнопки тяжелую бизнес операцию — пользователь смотрит на крутящийся спиннер и ненавидит продукт (про асинхронное взаимодействие мы подумали позднее).
Предоставляя пользователю список заявок мы натравили все чтения на базу исполнения процессов — внезапно поток на чтение начинает влиять на поток выполнения (Да, в таких ситуациях лучше использовать CQRS паттерн).

Со временем мы понимаем: таких ситуаций лучше избегать. Появляется потребность в обобщённых решениях — паттернах.
Отвечая на вопрос, «Как же делать правильно?», мы говорим о том, что нужно использовать паттерны.
Паттерн — это решение, валидное в рамках конкретного контекста.
Мы накапливаем их, видим на разных уровнях абстракции, применяем снова и снова. И именно они формируют наше архитектурное мышление.

Про миграцию Legacy-систем
И, казалось бы, мы уже опыта поднабрались, паттернов наизучали. Теперь мы можем решать сложные задачи. Давайте рассмотрим одну из таких задач — миграция Legacy системы.

Оценим текущее состояние AS IS.

Представим, что у нас есть система, которая построена в старой оргструктуре — система оформления, в которой все сотрудники могли продавать продукты и заводить клиентов в учетных системах.
Новые вводные заставляют нас пересмотреть существующую систему:.
У нас есть новая оргструктура: продавец и ручник (руками выполняет действия в учетных системах для сложных случаев), они решают разные задачи, у них разные KPI и разные хотелки(требования).
Есть технические ограничения старой системы: старая версия Java, старая версия сервера приложений, эксклюзивный стек, на который трудно найти специалистов.
Новые бизнес-потребности в виде продажи продуктов бандлами.
Как мы их решаем?
Начнём с новой оргструктуры
Обычно когда у нас есть обширные группы пользователей с разными требованиями (функциональными и нефункциональными) для них можно завести разные порталы/модули одной системы или вообще рассмотреть выделение под эти группы отдельных систем.

В нашем случае распространённая практика — делать отдельные системы: одну, заточенную под продажи, одну — под тех, кто руками обрабатывает какие-то отклонения, если что-то пошло не так. Основополагающими тут были требования: процесс продажи должен быть быстрым, собирать минимальное количество данных, быть доступным на различных устройствах (продажи у партнёров, курьеров компании и пр.), а процесс ручной обработки должен создавать задачи, которые будет сотрудник решать по инструкции в контуре компании.
В конечном итоге, мы создали Welcome-зону для продаж.
Далее мы решаем проблему с новой функциональностью и техническими ограничениями и новыми бизнес-потребностями.
Создаём целевую систему
У нас появилась новая бизнес потребность: продавать продукты клиентам бандлами. Новую систему нужно было построить в архитектуре продукт-прогностиков и омниканальности до того, как это было мейнстримом. Старая система в такой концепции не разрабатывалась.

Появляется вопрос — «А как его встраивать?» Есть два варианта.
Старую систему учим делегировать функциональность новой системе мультизаявки.
Либо же другой паттерн — StranglerFig-паттерн (он же удавка), когда новую функциональность пускаем по новой ветке. Старая система продолжает делать свою работу, мы её особо сильно не трогаем, а новую функциональность (в варианте справа по белой стрелочке) пускаем через новую систему.
Мы выбрали второй вариант, потому что хотели использовать целевой стэк, хотели минимизировать доработки старой системы. Да, пусть она пока что будет делать свою старую работу, которую она уже умеет делать, но, по крайней мере, всё новое, что мы разработаем, будет уже написано на целевом стэке.
Выглядит как идеальная «учебниковая» миграция.
Рвём связь: переносим каркас процесса и составляем план миграции
Изначальную проблему мы вроде как решили. Но на пути к светлому будущему сталкиваемся с новыми проблемами, а именно с этой паразитной связью, которая тут помечена белой стрелочкой.

Как сейчас помню, когда я шел в детский сад отводить ребенка, мне позвонило сопровождение и сказало: «Слушай, у нас тут какая-то проблема на проде, подойди, пожалуйста, помоги разобраться». В итоге выяснилось, что команда «просто что-то забыла». Целевую связку от Welcome-зоны до мультизаявки доработали, а старую связку не доработали, потому что управление заявкой оставалось там в какой-то части.
Пострадал от этого не один я. Поэтому мы довольно-таки быстро пришли к тому, чтобы убрать эту связь — сделаем некий «фасад» (тоже паттерн). В итоге мы получаем картинку слева снизу AS IS.

Мы порвали эту веселенькую связь, которая доставляла нам проблем, и уже получили хорошую транзитивную архитектуру, которая в TO BE выглядит как на картинке справа.
Более того, в ходе становления поняли, что в целевой картине ручник уже сидит в мультизаявке в едином окне (а в AS IS они обозначены белыми стрелками от продуктовых систем к продуктовым системам). Благодаря Federated Case Management — паттерн, позволяющий выполнять заявки в одном окне.
И, казалось бы, формально всё сделано правильно:
Паттерны соблюдены.
Путь понятен.
Архитектура приближается к целевой и логична.
Что же мы не учли и что пошло не так?
Ограниченные ресурсы
На «кубиках и стрелочках» всё здорово. Но в суровой реальности сталкиваемся с ограниченностью ресурсов. По плану миграция, заняла бы 8 лет.
Создание фронт-системы: 1 год.
Создание системы «мультизаявка»: 1 год.
Разрыв связи: 1 год.
Основной продуктовый процесс из старой системы и FCM: 2 года.
Оставшийся функционал: 3 года.
Это достаточно долго. Конечно, не все 8 лет шла миграция. Параллельно пилились новые фичи, которые требовали всё больше ресурсов команд. К тому же автоматизация, которая образовалась в старой системе, оказалась достаточно объемной.
Мы потратили 5 лет и оставалось «всего» 3 года. Казалось бы, проблема решаема, можно ресурсов добавить и стахановскими темпами закрыть трехлетку за год. Но появились непредвиденные факторы, которых никто не ожидал.
Импортозамещение
У нас не только старая система попала под импортозамещение, но еще и целевая система! И пришлось как-то выкручиваться из этой ситуации. Ну что ж… Here we go again…

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

Едим змею с двух концов
Картинка выше может быть чуть непонятная. Покажу чуть-чуть другой разрез — функциональный.

Если посмотреть, на то, что слева, то нам оставалось всего ничего. Три года отводилось на то, чтобы перенести оставшийся функционал из старой системы в новую.
Но мы в новых реалиях концентрируемся на том, что в первую очередь (на картинке справа) нужно из старой системы перенести хвост, который больше всего болит и который стоит костью в горле: у неё есть технические ограничения, которые мешают нам работать. Не забываем также про перенос новой функциональности в целевую систему.
Затем, предыдущую целевую систему, как бы это странно ни звучало, переносим в последнюю очередь, ведь она спроектирована уже достаточно хорошо под актуальные бизнес потребности. Перенос ее процессов будет проще, чем со старой Legacy-системы, т.к. закладывались хорошие подходы при проектировании, разработке, документировании, тестировании.
Ну и собственно говоря, перейдем к результатам.
Мораль
Я рассказал про опыт иммиграции, который мы делали по паттернам. По паттернам выбирали проторенную дорожку. Но столкнулись с обстоятельствами, которые эту дорожку усложняют.
Какие же выводы из этого можно сделать?
№1. Без паттернов никуда
Паттерны валидны в рамках контекста. Их никуда девать не надо, их надо учить, их надо вести — паттерны помогают прокачиваться. Они совершенно точно положительно влияют на квалификацию, ведь это расширение кругозора и насмотренность. Пример: тот же самый MVC. Такой паттерн можно увидеть на разных уровнях абстракции и его использование сразу всплывает в голове, качественно решая задачу.
У нас в Альфе есть отдельная рабочая группа по паттернам. Мы их ведем в Конфлюенс в таком формате. Они есть по разным категориям: паттерны департаментов, паттерны взаимодействия между системами. Банально можно найти паттерн выбора между Kafka и IBM MQ.

Естественно, в эту историю хочется встроить ИИ, чтобы согласно паттернам формировались рекомендации при проектировании. Это позволит не просто шерстить Конфлюенс и в поисках категории и паттерна, чуть ли не автоматически формировать решение. На данный момент задача в проработке.
№2. Достаточно ли просто знать паттерн, чтобы успешно его применять?
Просто знать паттерн недостаточно. Часто бывают случаи «оверкилла» и паттерны применяют просто для того, чтобы применить, хотя по факту можно было обойтись проще. Поэтому в определении паттерна очень важно то, что он валиден в рамках контекста.
К тому же паттерн может устареть, его нужно пересматривать и актуализировать. Если есть подозрение, что используемый паттерн не актуален, то его пересогласовывают. У нас это делает рабочая группа по паттернам.
№3. Паттерны не учитывают ресурсные ограничения
Какую бы прекрасную архитектуру мы ни делали, у нас всегда будут ограниченные ресурсы. И вот этот вот факт недостаточных ресурсов нужно уметь подсвечивать. В нашем кейсе, например, чем новая целевая система отличается от старой? В ней есть отдельная платформенная команда и есть понимание от бизнеса, что они должны своими ресурсами, которые у них сейчас есть, перейти на эту новую платформу. В старой концепции платформенная команда была частью бизнеса и просто она захлебнулась от того, что ей пришлось дорабатывать фичи и времени на перенос оставалось все меньше и меньше.
№4. Приземляйте новый функционал в целевую систему, потому что она у нас не болит
Мысль крамольная, но на самом деле бизнесу в какой-то степени должно стать неудобно пользоваться старой системой. Это их подстегнет к тому, что они мигрируют в целевую систему, и быстрее поймут плюсы, которые получают от взаимодействия с целевой системой.
А навык продавать целевое решение – это одно из хороших умений, которым должен обладать архитектор. Поэтому в этой области тоже стоит прокачиваться. Но эта тема отдельного доклада.
Подписывайтесь на Телеграм-канал Alfa Digital, где рассказывают о работе в IT и Digital: новости, события, вакансии, полезные советы и мемы.
Может быть интересно:
