В статье попробую понятно и с картинками сопоставить 2 подхода к типовой задаче "распила" монолитного приложения.
В качестве примера выбираю знакомую мне сферу - платежи.
Вот наш монолит, знакомьтесь.
Монолит умеет принимать запросы на проведение платежа от интернет-магазинов и в зависимости от параметров направлять эти запросы в один из банков.
Фактически такая платежка является посредником между продавцом и банком, но наш сервис не простой прокси-посредник, а скорее агрегатор и хранитель всех платежей для нескольких интернет магазинов
(в реальности между человеком-плательщиком и банком может выстраиваться целая цепочка посредников, и каждый имеет свою долю-малую, но это уже совсем другая история...).
Чтобы разделить монолит, мы должны знать все значимые бизнес-функции системы:
1. Проведение операций (платежей нескольких типов, возвратов, выплат, переводов, с использованием различных платежных инструментов)
- получить запрос по API
- проверить операцию на наличие признаков мошенничества
- проверить, доступна ли операция для клиента
- определить маршрут платежа (то есть в какой банк направить платеж, однако, банк может предоставить несколько виртуальных терминалов для проведения операций, в таком случае нужно определить не только банк, но и терминал)
- провести взаимодействие с выбранным банком (фактически исполнить операцию и получить результат)
- отправить уведомление о результатах операции
2. Личный-кабинет для клиента (интернет-магазина или другого агрегатора продавцов)
- поиск операций
- статистика операций
- открытие терминала для проведения платежей в определенной валюте
3. Администрирование системы
- создание нового клиента
- управление доступными операциями клиента
- создание правил роутинга от клиента к банку в зависимости от различных параметров
- управление лимитами для клиента и/или от банка
- ведение черных списков платежных инструментов, IP-адресов и прочего
4. Служебные функции
- модуль наблюдения за работоспособностью системы
- модуль для проведения нагрузочных тестов
- логи системы для разбора инцидентов
- отдельная база данных с подготовленными витринами для аналитики - источниками данных для BI-систем.
Начинаем распил монолита...
Микросервисный подход
Посмотрим как выглядит типовая микросервисная архитектура.
Определим особенности нашего подхода
- Внедряем API Gateway - это единая точка входа для запросов от Front UI. Все преимущества такого подхода расписывать не стану, а отправлю изучить рекламную страничку istio
- Используем подход один сервис - одна БД и решительно отвергаем подход использования одной БД для нескольких сервисов
- Применяем концепцию API First и не чураемся синхронных REST запросов между сервисами - скорость возведения здания в приоритете, кто-то осудит такой экономичный подход, но ведь у нас хрущевка, а не дворец
- Стремимся к прекрасному - для асинхронных межсервисных взаимодействий используем брокер сообщений, стремимся воплотить лучшие образцы событийно-ориентированной архитектуры, нам близка концепция один писатель - много читателей, поэтому в качестве брокера мы выбрали kafka
- Наш ФРОНТ "на любителя", но у нас нет сил делить наш Front UI на красивые микрофронтенды, но мы знаем про паттерн Backends for Frontends и очень хотим сделать красиво удобно, но, кажется, легче все снести и строить заново (жильцы нашего здания не поймут, приходится признать, что "реновация" порой дешевле, чем капитальный ремонт).
Микросервисный подход для платежки
Выделяем как бизнес, так и технические функции в отдельные микросервисы. Сore transaction - это ядро системы, сервис использует других для проведения операций.
Получив запрос от клиента, сore transaction последовательно вызывает: antifraud, client config, routing, limiter, а результаты операции отправятся в kafka, чтобы их прочитали: dwh, notificator и statistics.
Используем паттерны:
- OutBox - для снижения нагрузки разводим операции на запись и чтение базы данных,
- Adapter - чтобы не изменять ядро для создания каждой новой интеграции.
Композиционный подход для платежки
Выделяем полноценные бизнес-функции в отдельные сервисы.
Применяем концепцию: центральный оркестратор использует сервисы-агенты.
- Все взаимодействия с клиентом - clients contacts
- Статистика по операциям для клиента - clients statistics
- Внутреннее конфигурирование клиента - clients configs
- Все проверки операция на мошенничество - antifraud
- Функция исполнения операции в указанном терминале банка - payments.
- Спрут сервис-оркестратор, который предоставляет API для клиента и содержит бизнес-логику исполнения запроса - Operation manager
Под капотом у оркестратора и его агентов может быть микросервисная архитектура.
Сравним распределение функций по сервисам
Что изменилось в композиционном подходе?
- antifraud, statistics и все служебные функции не изменились,
- routing, limiter и client config - объединились в единый сервис,
- notificator - стал сервисом clients contacts, взяв на себя все взаимодействия общения с клиентом,
- core transaction - распался на 2 части: вся оркестрация, то есть вспомогательные вызовы других сервисов, вся внутренняя логика проведения операций остается за operation manager, а все интеграции с банками, то есть внешняя логика на payments.
Как изменилось ядро системы?
На первый взгляд может показаться, что payments превратился в набор адаптеров, отчасти так и есть, но нет - под капотом сохраняется основной универсальный протокол, который направляет запрос на нужный адаптер, реализующий интеграцию с банком.
Давайте подумаем, не нагрузить ли сервис payments функцией маршрутизации операций? То есть добавить routing - ведь функция определения конкретного терминала банка необходима именно для проведения платежей.
Однозначного ответа не нахожу, но принимаю решение в пользу упрощения payments, а роутинг, лимиты и все конфигурирование клиента локализуем в другом сервисе, тем самым избавляем payments от необходимости иметь пользовательский интерфейс.
Отметим, что для всех наших композиционных сервисов с большой нагрузкой справедлив паттерн OutBox, но все детали реализации сервисов не выносится на архитектурную схему.
Какие сервисы расширились при композиционном подходе?
notificator - стал сервисом clients contacts, взяв на себя все взаимодействия общения с клиентом.
Существование расширенного сервиса clients contacts еще больше оправдано в организации, где точек контакта с клиентом больше, где требуется регламентировать каналы коммуникации. С другой стороны, такой подход совершенно не оправдан, если организация занимается только платежами и сервис ничего кроме информации о платежах не отправляет.
Резюмируем
Отмечу ограниченность применения концепции "Умный оркестратор + исполнительные Агенты" - проблема согласованности данных никуда не уходит. Например, сервис управляющий лимитами клиента должен знать о всех успешных операциях, следовательно нужно обеспечить обновление данных, иначе оркестратору будет отправлен некорректный ответ. Аналогичная проблема есть и в микросервисах.
Поэтому мы используем брокер событий для синхронизации данных и оркестратор для запросов во вспомогательные сервисы и исполнения логики бизнес-процесса.
На мой взгляд микросервисный подход лучше для организаций, в которых нет большого разнообразия систем и каждая отвечает за определенный набор процессов, возможно эксплуатируется отдельным подразделением. В таком случае дополнительный - композиционный уровень иерархии сервисов может всех запутать.
Но для крупных корпораций, где зоопарк систем не охватить ни на одной архитектурной схеме, композиционный подход гораздо лучше.
Выделение бизнес-функций, которые можно переиспользовать и объединение микросервисов по контекстам - это порой единственная возможность навести порядок.
Для больших компаний:
ЕСЛИ в микросервисной архитектуре сложно решиться на независимые релизные циклы из-за пересечения зон ответственности команд, а еще сложнее отказаться от общего регрессионного тестирования на всю систему, ТО в композиционном подходе точно нужно решаться, делать сервисы реально независимыми со своими системами мониторинга, интеграционными авто-тестами, стандартизированными логами... в общем, чтобы сделать красиво придется как следует поработать))
Мораль сей басни такова:
Чем чаще я беру на прокат велосипед, тем больше хочу свой хороший!
Ну кто захочет использовать чужие старые микросервисы? При первой возможности сделаю свой новенький и блестящий (хоть документация будет), альтернатива - годами страдать...
Выход из этого ада или многократного дублирования функций в создании сервисов понятных (задокументированных) и надежных, которые приятно использовать - будто для тебя делали!
Звучит как тост, на этом и закончу!