Что лучше для web-приложений - монолит или микросервисы? Многие ответят на этот вопрос, что, мол, все инструменты хороши, если их использовать по назначению. В таком случае у меня, как у человека, в силу своего возраста, довольно консервативного и неохотно воспринимающего непроверенные временем концепции, возникает другой вопрос - а чем хорош монолит? Где его ниша? Стоит ли переключаться на микросервисы или монолит ещё не изжил себя и на мой век хватит?
В фокусе моих интересов не гигантские web-приложения типа Gmail, Facebook, Twitter, а web-приложения, созданные на базе таких платформ, как Wordpress, Drupal, Joomla, Django, Magento и им подобным. Под катом мои субъективные мысли на этот счёт. Ничего нового - всё те же 33 буквы кириллицы и 26 букв латиницы вперемешку.
Причина появления микросервисов
Для начала хорошо бы определить, в чём причина появления микросервисов? Чем оказался плох монолит?
Здесь мне очень помогла статья "Архитектура микросервисов" (автор оригинала - Herberto Graca, перевод - @AloneCoder. Основные отличительные черты микросервисов:
разграничение контекстов: что бы ни подразумевалось под словом "контекст";
надпроцессное взаимодействие: части одного приложения взаимодействуют посредством HTTP-запросов, а не в рамках процессов одного сервера;
независимая масштабируемость: части приложения, как следствие предыдущего пункта могут размещаться на разных аппаратных ресурсах и масштабироваться отдельно от других частей;
гетерогенность используемых инструментов: различные части приложения могут быть написаны на различных языках программирования;
На мой взгляд, основная причина появления микросервисов - потребность в независимом масштабировании частей приложения. Всё остальное - либо необходимые условия (разграничение контекстов и надпроцессное взаимодействие), либо следствия (гетерогенность инструментов, независимые команды разработчиков).
Таким образом, основное ограничение монолита - это то, что он не может быть отмасштабирован частично.
Пределы вертикального масштабирования
Вертикальное масштабирование (наращивание мощности аппаратных ресурсов) вполне доступно для монолита. Где же граница, после которой вертикальное масштабирование становится неэффективным? У меня нет большого опыта работы с высоконагруженными системами, поэтому я основываюсь на стороннем опыте. Так, Александр Бындю в своей статье "Переход от монолитной архитектуры к распределенной" даёт такие количественные характеристики:
Работа с большими объемами данных - для нас от 500 ГБ и таблицы от 100-150 млн. записей уже считаются большим объемом. ...
Работа с нагрузками - либо от пользователей (примерно от 100 запросов в секунду к вашим серверам заставят вас задуматься об оптимизации запросов и скорости отклика), либо от внутренних сервисов. ...
Исходя из этого, могу предположить, что монолитное web-приложение вполне комфортно будет масштабироваться вертикально:
на базах общим размером до 0.5ТБ;
с таблицами, в которых количество записей не превышает 100 млн.;
при сетевых нагрузках не выше 100 запросов/сек.
Понятно, что это очень приближённые значения и очень многое зависит конкретного web-приложения, но мой собственный опыт говорит, что это довольно осторожные оценки и вертикальным масштабированием можно добиться приемлемой работы и на куда больших размерах и нагрузках.
Получается, что, если web-приложение не выходит за указанные рамки (или, что касается базы данных, удерживается в этих рамках), то есть смысл оставаться в рамках монолитной архитектуры.
Микрофронтенды
С некоторым для себя удивлением осознал, что микросервисы - это про масштабирование backend'а и только про него. Пользовательский интерфейс отрабатывает на фронте, в браузере клиента. И по своей сущности является классическим "монолитом" - взаимодействие компонентов фронта происходит либо в рамках одного процесса, либо на уровне процессов одного аппаратного устройства и ни о каком горизонтальном масштабировании речь не идёт в принципе. Причём такое положение одинаково для любого типа фронта - SPA, PWA или классическая HTML-страничка с фрагментами JS. В статьях типа "Разделение монолитного приложения на микросервисы" вы ничего не найдёте про то, как делить монолит, чтобы компоненты фронта соответствовали "своим" микросервисам.
Т.к. зависимость по данным между фронтом и микросервисами остаётся и должна поддерживаться (новый атрибут у сущности в микросервисе может требовать новое поле на форме в UI), то микросервисный подход с бэка "протекает" на фронт в виде микрофронтендов.
Из 4 основных характеристик микросервисов, описанных выше, в разделе "Причина появления микросервисов", к микрофронтендам можно отнести только две:
разграничение контекстов;
гетерогенность используемых инструментов (если под "инструментами" подразумевать фреймворки - React, Angular, Vue, ...);
В минусе - надпроцессное взаимодействие и горизонтальное масштабирование.
Что характерно, первая причина (разграничение контекстов) является следствием появления микросервисов на бэке и главной причиной возникновения самого понятия "микрофронтенд".
Из неприятного - приемлемых, общепринятых рецептов создания микрофронтендов пока нет. По крайней мере в статьях про разделение монолитного приложения на микросервисы нет ссылок на рецепты создания микрофронтендов, соответствующих микросервисам.
Из непонятного - зачем к web-компонентам, виджетам, портлетам, добавились ещё и микрофронтенды?
Монолитен ли монолит?
Корректно ли вообще говорить о монолитной архитектуре в контексте web-приложений? Ведь любое web-приложение размещается, как минимум, в двух местах - на сервере и в браузере клиента, а классической вообще считается трёхуровневая архитектура.
Если рассматривать классическое web-приложение с точки зрения надпроцессного взаимодействия, то на каждом уровне своя группа процессов (браузер, backend, базы данных), а контексты вполне себе разграничиваются на уровне отдельных модулей (библиотек, компонентов, плагинов). Такая архитектура позволяет масштабироваться горизонтально в определённых пределах - "балансировщик нагрузки + несколько экземпляров backend'а", "база данных на запись + кэширующие in-memory DB на чтение". Но чего себе точно не может позволить классическое web-приложение, так это независимого масштабирования частей приложения на уровне отдельных модулей/плагинов (контекстов).
Получается, что явным признаком монолитного приложения является его развёртываемость в виде единого целого (либо отдельно по уровням - контент для браузера, серверная логика, структура данных).
Плагины
Как ни крути, но в любом приложении есть зацепление (coupling) по данным между пользовательским интерфейсом на клиентской стороне и хранилищем данных на серверной. Если данные хранятся в базе, то они каким-то образом должны быть показаны пользователю (или получены от него). Путём разбиения функциональности приложения на отдельные области (разграничение контекстов) можно очень сильно снизить зацепление между отдельными фрагментами разрабатываемого приложения (модулями). Вплоть до получения плагинов - модулей, подключаемых к приложению по необходимости.
Но внутри плагина сквозное зацепление по данным между пользовательским интерфейсом, бизнес-логикой и хранилищем всё равно остаётся. Поэтому плагины оформляются в виде отдельных пакетов, в которых совместно находятся элементы кода, соответствующие всем трём уровням приложения (UI, бизнес-логика, данные). Сами пакеты распространяются через соответствующие менеджеры пакетов (maven, composer, npm).
Платформы
Многие современные web-приложения строятся на базе какой-нибудь платформы (framework - Wordpress, Django, ...), предоставляющей базовую функциональность, и набора плагинов для этой платформы (публичных или собственного производства), позволяющего удовлетворять бизнес-потребности заказчика.
И если плагины позволяют разделить функциональность приложения на части, то платформы - это как раз тот инструмент, что вновь объединяет части в единое целое. Причём делают они это отдельно для каждого уровня (UI, бизнес-логика, данные), предлагая плагинам соответствующую инфраструктуру для встраивания в приложение на отдельном уровне и для передачи данных плагина между уровнями:
Зачастую платформы используют метаданные менеджеров пакетов (файлы package.json
, composer.json
соответствующих пакетов) для распознавания "своих" плагинов в общей массе зависимостей приложения.
Режимы работы web-приложения
Web-приложение в общем случае может работать в следующих режимах:
Web UI: поддержка работоспособности пользовательского интерфейса на клиенте (раздача статики, обмен данными, представление данных в браузере);
Web API: backend-сервисы для доступа к бизнес-функциям приложения (с фронта или от сторонних приложений);
CLI: консольный доступ к бизнес-функциям приложения (как правило, сервисным);
Cron: выполнение задач по-расписанию (например, зачистка устаревших или временных данных);
Async: асинхронная обработка ресурсоёмких событий (например, списание продуктов со склада при оформлении заказа в магазине);
Для работы в режимах, допустим, CLI и Cron приложению не обязательно нужно находиться на одном аппаратном устройстве. Несмотря на общую кодовую базу, приложение в разных режимах можно запускать на разных серверах (тоже своего рода горизонтальное масштабирование). Главное, что их связывает - единое хранилище данных, которое, кстати, можно попытаться разбить "побазно" и каждую базу разместить на отдельном сервере (подход из мира микросервисов). Для приложения в режиме Web API можно сделать балансировку нагрузки и также разнести экземпляры приложения по разным серверам.
Резюме
Строго говоря, термин "монолит" неприменим к web-приложениям в силу их распределённости (клиент-сервер). Можно принять за "монолитное" приложение, у которого:
единая кодовая база (с учётом, что клиентская часть реализована на HTML/CSS/JS, а серверная - на Java/C#/PHP/Python/JS/Go/..., но собирается всё в единое приложение);
модули (plugins), реализующие различные контексты приложения, объединяются одной платформой (framework) для обеспечения сквозной работоспособности модулей на различных уровнях приложения (UI / бизнес-логика / данные);
общее логическое хранилище данных (которое может быть разнесено на различные сервера по типам данных - файлохранилище, RDBMS, in-memory DBs);
приложение в различных режимах работает, как правило, в рамках процессов одного аппаратного устройства (разумеется, с учётом разделения по уровням - UI / бизнес-логика / данные);
На мой субъективный взгляд у приложений такого типа в современном мире есть ещё достаточно перспективное будущее в ограниченной нише мелко-средних web-приложений (БД до 0.5ТБ, < 100 млн. записей на таблицу, < 100 запросов в сек. по сети).
Смысл заморачиваться микросервисами в web-приложении есть только в том случае, если приложению грозит шанс стать популярным и выйти в разряд highload-приложений. Но, как писал классик "Все счастливые семьи похожи друг на друга, каждая несчастливая семья несчастлива по-своему." (с) В случае выхода приложения на highload нужно будет "творчески осмысливать" создавшуюся ситуацию и предпринимать шаги по снижению напряжённости, соответствующие данному конкретному случаю. Благо, что сам характер web-приложения предполагает распределённость и есть некоторое поле не только для вертикального, но и горизонтального масштабирования "монолита".
В обычной, до-highload'овой ситуации достаточно вложить свои интеллектуальные усилия в разделение контекстов в "монолите" (разбиение кодовой базы на плагины) и асинхронное выполнение ресурсоёмких операций, что позволит проще перейти к микросервисам, если горизонтальное масштабирование станет для web-приложения жизненно-важным.
Послесловие
Некоторые на Хабре интересуются, зачем я пишу тут всякое? Отвечаю - подобная писанина помогает упорядочить собственный поток сознания, а иногда и получить из ноосферы полезную обратную связь в виде комментов.
Очень сожалею, если вас напрягло прочтение моего опуса, но прошу отметить, что вы это сделали совершенно добровольно. И если вдруг вы решите поставите посту минус с пометкой "Ничего не понял после прочтения", то попробуйте перечитать ещё раз - вдруг всё-таки что-то поймёте. Или найдите другую причину. Спасибо за понимание :)
Если же кто-то нашёл для себя что-то полезное - я только рад.
"Счастье для всех, даром, и пусть никто не уйдёт …" (с) АБС