Как разбить монолитное приложение на микросервисы без рефакторинга

За последние несколько лет микросервисы приобрели немалую популярность и значительно сказались на моей деятельности full-stack разработчиком. Но за все это время я ни разу не усомнился в монолитной архитектуре. Микросервисы привносят дополнительную сложность, которая в большинстве случаев не оправдывается сопутствующими преимуществами. Вот почему я настойчиво продвигаю и поддерживаю монолитный подход. И такая позиция вызывает множество дискуссий. 

Как по мне, монолитная и микросервисная архитектура не так уж сильно отличаются. При правильном проектировании монолит состоит из модулей со строгими границами. Этот принцип наделяет структуру модуля такими характеристиками, как сильная связность (англ. high cohesion) и слабая связанность (англ. low coupling), в результате чего оптимизируется процесс обслуживания приложения и внесения в него изменений. При монолитном подходе несколько команд могут работать над одним приложением, не мешая друг другу. Этот аргумент часто выдвигается в поддержку микросервисов. 

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

Модули: Заказ (Order), Товар (Product), Список товаров в наличии (Inventory), Доставка (Delivery)

Концепция модульного монолита определенно не нова, но привлекает все больше внимания как противопоставление микросервисам. Специалисты, такие как Сэм Ньюман, пишут книги по данной теме, а их идеи рождают новые технологии. Так появился проект Spring Modulith для Java-приложений. Этот тип монолита в полной мере отвечает потребностям большинства компаний, в которых мне довелось работать. 

На мой взгляд, микросервисы  —  это просто распределенный вариант монолитной архитектуры. Распределенные системы не отличаются простотой и дешевой стоимостью, поэтому, выбирая их, следует очень хорошо подумать. Такие аргументы, как улучшенная структура приложения и многокомандная поддержка, звучат неубедительно. А вот аргументы, связанные с функционированием такой системы, как то масштабирование, устойчивость и способность к развертыванию, заслуживают должного внимания. 

Рефакторинг модульного монолита в микросервисы не представляет сложности, если установлены достаточно строгие границы модуля. Реорганизовывать модули можно по отдельности независимо друг от друга. После полного рефакторинга упомянутого ранее интернет-магазина приложение принимает следующий вид: 

Существует много способов (шаблонов) для организации микросервисной архитектуры. Универсального решения нет, поэтому правильный выбор всегда зависит от контекста. Шаблон API gateway (пер. API-шлюз), используемый в примере, прекрасно подходит для разделения сервисов, решения проблем безопасности и многих других задач. С него начиналась работа над многими моими проектами. 

Идея начать с монолита и постепенно перепроектировать его в микросервисы кажется превосходной, но не очень практичной, исходя из личного опыта. Рефакторинг модуля в сервис требует больших затрат: настройка нового проекта, конечных точек, запросов, операций и т.д. Когда последовательный технологический процесс приложения уже запущен, становится сложно найти ресурсы, чтобы его перенаправить, при постоянном спросе пользователей на новые функциональности. Слишком длительное ожидание нарушает порядок вещей и быстро накаляет атмосферу. 

Эти обстоятельства потребовали осмысления проблемы. В идеале монолит можно перепроектировать в микросервисы без рефакторинга. Определение того, что и где выполняется, попадает в зону ответственности конфигурации, при этом заданные настройки могут изменяться в любое время. Мысль неплохая, но как трансформировать модули в сервисы, не затрагивая код? В тот момент я как раз прорабатывал концепцию, которая помогла с ответом на вопрос.

Работая full-stack разработчиком, я довольно много времени трачу на настройку взаимодействия между фронтендом и бэкендом, при этом не могу избавиться от ощущения необоснованных ресурсозатрат. В статье “Как ускорить full-stack разработку, не создавая API” я описал варианты для автоматизации связей между этими частями процесса и предложил решение, которое подразумевает перенос взаимодействия из кода в среду выполнения. При таком подходе фронтенд напрямую может импортировать и вызывать компоненты бэкенда. Среда выполнения перехватывает все импорты бэкенда, создает и обеспечивает удаленную реализацию: работает как инжектор зависимостей (англ. dependency injector).

Рассмотренный вариант взаимодействия между модулями позволил решить проблему их автоматического перевода в сервисы. Однако среда выполнения была ограничена одним сегментом фронтенда и бэкенда. Поэтому ее следовало перепроектировать для поддержки неограниченного количества сегментов. Каждый из них допускает индивидуальное или групповое развертывание во фронтенде или в одном/нескольких бэкендах. Сегмент содержит один и более компонентов из одного или нескольких модулей. Его содержимое определяется конфигурацией. Теперь приложение интернет-магазина можно настроить так: 

В этом примере каждый модуль размещается в отдельном сегменте. Сегмент Delivery требует балансировки нагрузки (из-за функциональности отслеживания и обработки движения товаров) и развертывается на нескольких серверах. Остальные сегменты сгруппированы и развертываются вместе на одном сервере. 

Как вариант, сначала модули размещаются в одном сегменте, а затем переносятся в отдельные. Поскольку сегмент существует только в конфигурации, перемещение модуля в другой сегмент никак не влияет на код. Таким образом, своевременная реорганизация приложения выполняется без рефакторинга. 

Среда выполнения стала проектом с открытым ПО, выпускаемым под лицензией MIT. Он называется Jitar, аббревиатура от Just-In-Time-ArchitectuRe (пер. Своевременная архитектура). Благодаря full-stack поддержке эта среда выполнения реализована как слой поверх Node.js. Поэтому она подходит только для приложений JavaScript и TypeScript. Хотя нечто подобное можно создать на Java (и, возможно, NET?). Дополнительную информацию о Jitar вы найдете в документации и по ссылке на репозиторий GitHub.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Peter van Vliet: How I Split a Monolith Into Microservices Without Refactoring

Предыдущая статьяШаблон проектирования “Цепочка ответственности” в TypeScript
Следующая статьяПочему стоит использовать обратные вызовы и асинхронный код на NodeJS