Table of Contents
Использовать микросервисную архитектуру или нет? Статью с плюсами и минусами, внутренней коммуникацией, принципами и заблуждениями относительно микросервисной архитектуры опубликовал сайт proglib.io.
Простейший и популярный вариант архитектуры – монолитная. Каждый начинал с неё, и здесь нет никакой изоляции и распределённости: один монолит обрабатывает все запросы.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-2-min-1024x576.jpg)
Отчего возникают следующие проблемы:
- отказоустойчивость;
- горизонтальное масштабирование;
- применение одной технологии или языка и невыгодность переписывать огромный монолит;
- сложность рефакторинга из-за хранения кода в одном месте и куча legacy;
- трудности работы в команде разработчиков;
- чтобы использовать повторно, придётся дробить.
Второй по популярности вид архитектуры – пара монолитов, микс из монолита и сервисов или даже микросервисов. То есть вы сохраняете монолит, а доработки выполняете с использованием современных технологий.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-3-min-1024x578.jpg)
Это частично решает проблемы отказоустойчивости, масштабируемости и одного стека технологий.
Микросервисная архитектура – не новая идея, а разновидность сервис-ориентированной архитектуры. Сервис-ориентированная архитектура предусматривает модульность разработки и слабую связанность компонентов, поэтому получаем изолированную и распределённую систему.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-4-min-1024x578.jpg)
Главный минус – общая шина данных Enterprise Service Bus с огромными спецификациями и сложностями работы с абстракциями и фасадами.
Микросервисная архитектура
Микросервисная архитектура наследует от предшественницы изоляцию и распределённость. Здесь база данных не используется как шина данных, за исключением отдельных случаев в пользу производительности. По классической схеме компоненты изолируются и на уровне кода, и на уровне базы.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-5-min-1024x576.jpg)
Для микросервисов применяют контейнеризацию с оркестрацией и другими плюшками.
Следующее преимущество – протоколы обнаружения сервисов. Оцените наглядно разницу коммуникаций сервис-ориентированной и микросервисной архитектуры: у последней нет общей шины, и сервис обращается к любому другому напрямую:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-6-min-1024x571.jpg)
Выбор протоколов общения зависит от программиста. Например, вы используете REST для публичных запросов и RPC через AMQP для внутренних либо один общий протокол для всех.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-7-min-1024x573.jpg)
Разделяют микросервисы с точки зрения либо бизнеса, либо программиста для переиспользования. Но мешают этому две вещи:
- внутренние связи – при тесном взаимодействии микросервисы объединяют;
- транзакции – у разных микросервисов базы данных изолированы, а нужна одна общая.
Рассмотрим пример разделения сервисов. Вначале поставили задачу прикрутить конфигурацию к авторизации, позже потребовалась аналогичная для кошелька. Само собой, напрашивается выделение конфигурации в отдельный микросервис. Для этого копируют код из предыдущей задачи, добавляют RPC для внутренних связей, абстракцию над клиентом для удобства и начинают использовать в других микросервисах.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-8-min-1024x579.jpg)
Достоинства и недостатки микросервисной архитектуры
Как в любой распределённой архитектуре, получим накладные расходы на коммуникацию.
Концепция непрерывной интеграции и доставки (CI/CD) и построение архитектуры (контейнеризация, оркестрация, мониторинг и другое) требует большого количества времени.
Что насчёт отказоустойчивости? Часто её определяют как «падение одного сервиса не отражается других». Представьте, падает Audit
, а Wallet
теоретически продолжает работать – похоже на отказоустойчивость:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-9-min-1024x568.jpg)
А как же запросы по RPC, которые Wallet
продолжает слать? Необходимо программно предусмотреть ситуацию, когда Audit
не отвечает, и грамотно настроить rollback, поскольку базы разные, и транзакционно это сделать не получится.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-10-min-1024x574.jpg)
Или другая ситуация: падает микросервис авторизации, через который ходят другие. Чтобы продолжать обрабатывать запросы, добавляют код для неавторизованного пользователя. По существу, это мощный отказ.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-11-min-1024x578.jpg)
Масштабируемость у микросервисов достигается благодаря системам оркестрации, ведь остаётся достаточно серверных ресурсов в противоположность монолиту, который потреблял всю память и процессор.
Стандартный процесс разработки – кодинг, тестирование и развёртывание – в микросервисной архитектуре выглядит иначе. Первые два этапа сливаются, поскольку микросервис взаимодействует с кучей других. Чтобы локально сделать хоть один запрос, придётся запустить все эти микросервисы, поэтому тестирование вручную не подходит для подобной задачи.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-12-min-1024x573.jpg)
Представьте, вы тестируете перевод средств в банковской системе. Для этого вы создаёте двух пользователей, компанию, агента для пополнения счёта, делаете эмиссию в систему, чтобы пополнить хранилище агента деньгами, вносите средства на личный счёт отправителя и только потом делаете перевод получателю.
Будете выполнять столько запросов вручную? Добавьте ещё сложности взаимодействия микросервисов и получите ад.
Поэтому локально разработчик проводит юнит-тестирование, где вместо ответов микросервисов будут mock-объекты. Ещё понадобятся функциональные тесты, например, для отлавливания проблем коммуникации, а также интеграционные тесты. Они прогоняются вместе с юнит-тестами на этапе слияния рабочий копий в главную ветку разработки. И только потом программист проверяет функциональность руками. До развёртывания релизную версию тестирует QA.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-13-min-1024x575.jpg)
Микросервисная архитектура делает компоненты независимыми при разработке и развёртывании, чего не было в монолите.
Микросервисы используются повторно и экономят средства в плане бизнеса.
Программист получает относительную свободу в выборе языка и технологий для разработки отдельных частей проекта.
Контроль зависимостей
Трудно сопроводить и поддерживать 50 проектов с 50 репозиториями, если вдруг обнаружится баг безопасности, который нужно срочно пофиксить во всех них. Поэтому используйте контроль зависимостей, общие библиотеки для микросервисов и семантическое версионирование. Причём не делайте одну раздутую библиотеку, а разбивайте её на кучу маленьких, чтобы избежать страха выпуска мажорной версии.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-14-min-1024x577.jpg)
Инвертируйте зависимости: прописывайте версию драйвера в пакет, куда добавляете нужные микросервисы, чтобы потом не пришлось сломя голову перепроверять версии во всех проектах. Для внешних зависимостей лучше зафиксировать версии.
Базы данных
Поскольку базы данных в микросервисной архитектуре изолированные, вы используете разные их виды одновременно и получаете повышенный уровень безопасности, благодаря общению сервисов только через RPC. Но что делать, когда объединяемые данные в разных микросервисах?
Вот возможные решения проблемы:
- храните одно и то же значение в двух микросервисах, но появляются трудности с актуализацией данных;
- делайте RPC, правда, усложните работу с большими объёмами информации;
- выгрузите данные из всех баз для аналитики;
- сделайте миграцию данных, что тоже непросто и повлечёт написание RPC.
Представьте другой случай: при регистрации создаётся 3 сущности (пользователь, профиль и счёт), но на полпути что-то падает. Как откатить изменения, если данные распределяются по разным микросервисам? Думайте об этом на этапе проектирования.
Внутренняя коммуникация в микросервисной архитектуре
Для общения микросервисам нужен контракт: протокол и валидация данных. С последним справляется JSON Schema, но протокол также необходим. Требования при выборе способа коммуникации:
- строгость;
- общий протокол для сервера и клиента;
- генерация кода для любого языка;
- производительность.
В качестве протоколов используют Protocol Buffers, FlatBuffers, Apache Thrift. Сначала вы пишете предметно-ориентированный язык, отдаёте это программе-генератору кода и получаете сгенерированный клиент и сервер.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-15-min-1024x582.jpg)
Организация работы в команде
Команды делят по технологиям и следят за их размерами (не более 7–8 человек). Как правило, задачи выдают сеньорам, рядом с которыми хватает разработчиков уровнем ниже.
К примеру, два сеньора из одной команды получили по два микросервиса. Важно, чтобы программисты взаимодействовали между собой не только в локальной группе по конкретной задаче, но и с другими. Тогда в области их знаний будет много общего:
- язык;
- организация и шаблонизация кода для каждого микросервиса;
- библиотеки;
- концепция непрерывной интеграции и доставки;
- протокол коммуникации;
- документация.
А что объединит разнородные по языкам команды? Останется только CI/CD, глобально стандартизованный прокол и подход к документированию.
Устройство микросервисов
Микросервисы состоят из трёх слоёв: небольших обработчиков, бизнес-логики и мапперов данных. Последовательность выполнения запроса выглядит так:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-16-min-1024x575.jpg)
В сервисном слое сосредотачивается 99% всего кода. Поскольку в микросервисе несколько обработчиков, используйте Data Transfer Object (DTO), к которому вы будете приводить GET-запрос. Это облегчает обработку и валидацию.
Примеры кода
Не забывайте, что Node.js – асинхронный язык. Рассмотрим пример: приходит запрос, синхронно берёт HolyJSService
, асинхронно работает с маппером, выполняющим запись в базу данных, и процесс повторяется для последующих запросов. Никакого порядка в этом не наблюдается, поэтому выделяют два архитектурных подхода:
- stateless – не хранить состояние;
- stateful – добавлять объекту класса поля, которые нужны для выполнения запроса.
Когда используете stateful, при получении запроса вы создаёте новый объект HolyjSService
и заполняете необходимыми для выполнения данными. Дальше этот сервис асинхронно идёт в базу данных. Следующий запрос берёт новый экземпляр сервиса, и процесс повторяется.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-17-min-1024x575.jpg)
В коде вы реализовываете абстрактный класс с кучей полей и сеттеров, наследуете от него новый сервис и наполняете методами.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-18-min-1024x578.jpg)
Дальше обработчику нужен контейнер DI для получения нового экземпляра, который он пробрасывает в функцию. После её запуска и выполнения всех сеттеров вызываем сервисы в методе create
и возвращаем результат.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-19-min-1024x575.jpg)
При stateless-подходе используется контекст, который создаётся для каждого запроса отдельно, и единственный экземпляр сервиса, поскольку выполнение запросов от него не зависит.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-20-min-1024x569.jpg)
В контексте хранятся все необходимые поля и собственно метод для его создания:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-21-min-1024x568.jpg)
На уровне сервиса выглядит как дополнительный параметр:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-22-min-1024x578.jpg)
Для обработчика добавляем контекст вторым параметром – его возвращает метод createContextFromHttpRequest
:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-23-min-1024x579.jpg)
Сами по себе хэндлеры предельно простые:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-24-min-1-1024x577.jpg)
Поскольку для выполнения запросов часто нужны другие сервисы, контекст приходится прокидывать в них. Допустим, на первый микросервис поступил запрос. Присваивайте ему идентификатор X-Request-Id
, с которым он пройдётся по всем микросервисам. Благодаря логированию вы легко отследите путь внешнего запроса. X-Trace-Id
используется для обозначения единой бизнес-операции, состоящей из нескольких запросов.
Для создания идентификаторов используют OpenTracing.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-25-min-1-1024x576.jpg)
Рассмотрим пример, когда не получается написать микросервис без сервиса. Вам дали задачу реализовать управление доступом на основе ролей (RBAC). Вы проектируете три сущности: роль, ресурс и правило. Пишете RPC, а из соображений производительности и удобства добавляете дополнительный сервис для работы с этим RPC, ролями, правилами и ресурсами.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-26-min-1-1024x576.jpg)
Теперь отдельные микросервисы используют созданный сервис и общий интерфейс.
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-27-min-1-1024x572.jpg)
Если команде, которая пишет на другом языке, также понадобился RBAC, придётся написать для них дополнительный RPC. Однако не получится создать RPC для добавления фильтров или условий к управлению доступом.
Заблуждения
1. Это легче
После прочитанного вы вряд ли посчитаете микросервисную архитектуру лёгким делом, но это распространённое заблуждение. Сложность создания микросервиса наглядно показывает график:
![](https://techrocks.ru/wp-content/uploads/2019/11/micro-28-min-1-1024x575.jpg)
С самого начала вы медленно и печально разбираетесь с документацией, бесконечно подбираете протоколы и другие компоненты. К энному микросервису вы нарабатываете стандартные шаблоны, что в несколько раз облегчает процесс.
С точки зрения поддержки – наоборот, потому что увеличивается количество микросервисов. Новый разработчик вынужден будет часами сидеть за изучением диаграмм, чтобы понять глобальную картину и принцип работы системы.
2. Лучше производительность
Представьте, сколько придётся ждать отправителю комплексного запроса, пока он сходит в кучу разных микросервисов. Поэтому ради производительности переезжать на эту архитектуру не всегда целесообразно. В качестве альтернативы перепишите куски проекта на более эффективном языке, или добавьте профилирование к монолиту.
Заключение
Принимайте решение об использовании микросервисной архитектуры, только чётко осознав и взвесив все достоинства и недостатки. Вам будут нужны знания о создании распределённой архитектуры и возможность реализации необходимых структурных элементов со стороны бизнеса. Ведь без логирования, мониторинга, трассировки, непрерывной интеграции и оркестрации вы обрекаете себя на ад.