В монолитном приложении, управляемом единым процессом, компоненты вызывают друг друга на уровне языка или с помощью вызовов функции. Они могут быть тесно связаны, если вы создаете объекты с кодом (например, new ClassName()), или могут вызываться несвязанно, если вы используете внедрение зависимости, ссылаясь на абстракции, а не на конкретные экземпляры объекта. В любом случае объекты выполняются в одном процессе. Самая сложная задача при переходе от монолитного приложения к приложению на базе микрослужб заключается в изменении механизма взаимодействия. Прямое преобразование внутрипроцессных вызовов в вызовы RPC к службам приведет к чрезмерному и неэффективному взаимодействию, не подходящему для распределенных сред. Трудности разработки распределенных систем известны так хорошо, что существует свод принципов под названием Заблуждения о распределенных вычислениях, где перечислены предположения, которые часто делают разработчики при переходе от монолитных к распределенным конструкциям.
Единого решения не существует. Их несколько. Одно из решений — максимальная изоляция бизнес-микрослужб. В этом случае вы используете асинхронное взаимодействие между внутренними микрослужбами и заменяете детальное взаимодействие, типичное для внутрипроцессной связи между объектами, менее детальным. Для этого вы группируете вызовы и возвращаете клиенту данные, агрегирующие результаты нескольких внутренних вызовов.
Приложение на основе микрослужб представляет собой распределенную систему, работающую на нескольких процессах или службах, иногда даже не нескольких серверах или узлах. Обычно каждый экземпляр службы — это процесс. Таким образом, службы должны взаимодействовать по протоколу внутрипроцессного взаимодействия, например HTTP, AMQP или двоичному протоколу, такому как TCP, в зависимости от характера каждой службы.
Сообщество микрослужб исповедует философию "умные конечные точки и глупые каналы". Этот слоган рекомендует создавать структуру, где отдельные микрослужбы будут минимально зависеть друг от друга и иметь максимальную внутреннюю согласованность. Как уже говорилось, каждая микрослужба обладает собственными данными и собственной логикой предметной области. Но микрослужбы, состоящие из комплексного приложения, обычно просто хореографии используют взаимодействие REST, а не сложные протоколы, такие как WS-* и гибкие события, а не централизованные оркестраторы бизнес-процессов.
Обычно используются два протокола — запросы и ответы HTTP с исходными API (в основном для запросов) и легкие асинхронные сообщения при передаче обновлений в несколько микрослужб. Более подробно это описано в следующих разделах.
Типы связи
Клиент и службы могут взаимодействовать через различные типы связи в зависимости от сценария и целей. Эти типы связи можно разделить на два направления.
Первая группа определяет, является протокол синхронным или асинхронным:
- Синхронный протокол. HTTP — это синхронный протокол. Клиент отправляет запрос и ожидает ответа от службы. Это не зависит от выполнения кода клиента, которое может быть синхронным (поток заблокирован) или асинхронным (поток не заблокирован, ответ в конечном итоге будет отправлен). Здесь важно, что протокол (HTTP/HTTPS) является синхронным и код клиента сможет продолжить выполнение задачи только после получения ответа от HTTP-сервера.
- Асинхронный протокол. Другие протоколы, например AMQP (протокол, поддерживаемый многими операционными системами и облачными средами), используют асинхронные сообщения. Код клиента или отправитель сообщения обычно не ожидает ответа. Он просто отправляет сообщение, как при отправке сообщения в очередь RabbitMQ или другого брокера сообщений.
Вторая группа определяет, имеет запрос одного или нескольких получателей:
- Один получатель. Каждый запрос должен обрабатываться только одним получателем или службой. Например, шаблон Command.
- Несколько получателей. Каждый запрос может обрабатываться разным количеством получателей — от нуля до нескольких. Такой тип взаимодействия должен быть асинхронным. Например, механизм publish/subscribe, используемый в таких шаблонах, как архитектура, управляемая событиями. Он основан на интерфейсе шины событий или брокере сообщений, когда события обновляют данные в нескольких микрослужбах. Обычно это реализуется через служебную шину или подобный объект, например служебную шину Azure, с помощью тем и подписок.
Приложение на базе микрослужб часто использует комбинацию этих стилей взаимодействия. Наиболее распространенный тип — взаимодействие с одним получателем по синхронному протоколу, например HTTP или HTTPS, при вызове обычной службы веб-API HTTP. Для асинхронного взаимодействия между микрослужбами обычно используются протоколы сообщений.
Полезно знать об этих направлениях, чтобы разбираться в доступных механизмах взаимодействия, но это не самый важный аспект создания микрослужб. Ни асинхронность выполнения потока клиента, ни асинхронность выбранного протокола не так важна при интеграции микрослужб. Что важно, так это возможность асинхронной интеграции микрослужб без ущерба для их независимости, как описывается в следующем разделе.
Асинхронная интеграция микрослужб способствует их автономности
Как уже упоминалось, при создании приложения на базе микрослужб важно подумать о способе их интеграции. В идеале взаимодействие между внутренними микрослужбами необходимо свести к минимуму. Чем меньше взаимодействия между микрослужбами, тем лучше. Вам часто придется каким-то образом интегрировать микрослужбы. И когда это необходимо, помните, что взаимодействие между микрослужбами обязательно должно быть асинхронным. Это не означает, что нужно использовать конкретный протокол (например, асинхронный обмен сообщениями, а не синхронный HTTP). Просто взаимодействие между микрослужбами должно происходить только путем асинхронного распространения данных. Но попытайтесь устранить зависимость от других внутренних микрослужб в начальной операции запроса-ответа HTTP.
Если это возможно, никогда не используйте синхронное взаимодействие (запрос-ответ) между несколькими микрослужбами, даже для запросов. Каждая микрослужба должна быть автономной и доступной для клиента, даже если другие службы в этом приложении отключены или не работают. Если вы считаете, что одна микрослужба должна обращаться к другой (например, отправлять HTTP-запрос на получение данных), чтобы предоставить ответ клиентскому приложению, вы создадите архитектуру, неустойчивую к сбоям.
Более того, наличие зависимостей HTTP между микрослужбами, как при создании длинных циклов запрос-ответ с цепочкой HTTP-запросов, как показано в первой части рисунка 4-15, не только нарушит автономность микрослужб, но и повлияет на их производительность, если одна из служб в цепочке не будет работать правильно.
Чем больше синхронных зависимостей между микрослужбами, например запросов, тем больше время отклика в клиентских приложениях.
Рис. 4-15. Антишаблоны и шаблоны при взаимодействии между микрослужбами
Как показано на схеме выше, при синхронном обмене данными "цепочка" запросов создается между микрослужбами при обслуживании запроса клиента. Это антишаблон. В асинхронной связи микрослужбы используют асинхронные сообщения или опрос по HTTP для взаимодействия с другими микрослужбами, но запрос клиента обрабатывается сразу.
Если микрослужба должна вызвать дополнительное действие в другой микрослужбе, по возможности не выполняйте это действие синхронно в рамках исходной операции запроса и ответа микрослужбы. Руководствуйтесь принципом асинхронности (с помощью асинхронного обмена сообщениями или событий интеграции, очередей и т. д.). Старайтесь не вызывать действие синхронно в рамках исходной синхронной операции запроса и ответа.
И, наконец (и именно на этом этапе создания микрослужб возникают проблемы), если исходной микрослужбе нужны данные, принадлежащие другим микрослужбам, не создавайте синхронные запросы этих данных. Лучше реплицировать или распространять эти данные (только необходимые атрибуты) в базу данных исходной службы, используя итоговую согласованность (обычно с помощью событий интеграции, как описано в следующих разделах).
Как уже упоминалось в статье Определение границ модели предметной области для каждой микрослужбы, дублирование данных в нескольких микрослужбах допускается. Более того, таким образом вы сможете перевести данные на конкретный язык, используя термины этой области или ограниченного контекста. Например, в приложении eShopOnContainers есть микрослужба identity-api, которая отвечает за большую часть данных пользователя с сущностью, называемой User. Но если вам нужно хранить данные о пользователе в микрослужбе Ordering, вы используете отдельную сущность под названием Buyer. Сущность Buyer имеет тот же идентификатор, что и исходная сущность User, но содержит лишь некоторые атрибуты, необходимые для предметной области Ordering, а не весь профиль пользователя.
Вы можете использовать любой протокол для асинхронной передачи и распространения данных в микрослужбах, чтобы достичь итоговой согласованности. Как уже упоминалось, можно использовать события интеграции с помощью шины событий или брокера сообщений или даже HTTP, чтобы опрашивать другие службы. Это неважно. Главное правило — не создавать синхронные зависимости между микрослужбами.
В следующих разделах описываются разные стили взаимодействия, которые вы можете использовать в приложении на базе микрослужб.
Стили взаимодействия
Вы можете выбирать разные протоколы для взаимодействия в зависимости от желаемого типа взаимодействия. В качестве механизма взаимодействия с помощью синхронных запросов и ответов обычно используются HTTP и REST, особенно если вы публикуете службы за пределами узла Docker или кластера микрослужб. Если взаимодействие между службами осуществляется в пределах узла Docker или кластера микрослужб, вы можете использовать двоичный формат взаимодействия (например, WCF с помощью TCP и двоичного формата). Кроме того, вы можете использовать механизмы асинхронного взаимодействия на основе сообщений, например AMQP.
Существует также несколько форматов сообщений, например JSON или XML, или даже двоичных форматов, которые могут быть более эффективными. Если вы выбрали нестандартный двоичный формат, возможно, службы с этим форматом не следует публиковать. Вы можете использовать нестандартный формат для внутреннего взаимодействия между микрослужбами. Например, при взаимодействии между микрослужбами на узле Docker или кластере микрослужб (в оркестраторах Docker) или для собственных клиентских приложений, которые обращаются к микрослужбам.
Операция запрос-ответ с использованием HTTP и REST
Если клиент использует взаимодействие типа "запрос-ответ", он посылает запрос к службе, которая обрабатывает этот запрос и отправляет ответ. Взаимодействие типа "запрос-ответ" особенно хорошо подходит для запроса данных от клиентских приложений для пользовательского интерфейса в режиме реального времени. Поэтому в архитектуре микрослужб лучше всего использовать этот механизм взаимодействия для запросов, как показано на рис. 4-16.
Рис. 4-16. Использование взаимодействия типа "запрос-ответ" по протоколу HTTP (синхронно или асинхронно)
Когда клиент использует взаимодействие типа "запрос-ответ", он предполагает, что ответ придет быстро, меньше чем через секунду или максимум через несколько секунд. Если ответ задерживается, необходимо реализовать асинхронное взаимодействие на основе шаблонов обмена сообщениями и технологий обмена сообщениями. Этот подход рассматривается в следующем разделе.
Популярный стиль архитектуры для взаимодействия типа "запрос-ответ" — это REST. Этот подход основан на HTTP-протоколе и тесно связан с ним. Он принимает HTTP-команды, например GET, POST и PUT. REST — это самый распространенный архитектурный подход к взаимодействию при создании служб. Службы REST можно использовать при разработке служб веб-API ASP.NET Core.
Использование служб HTTP REST в качестве IDL имеет дополнительные преимущества. Например, если вы используете метаданные Swagger для описания API службы, вы можете применять средства, создающие заглушки клиента, которые могут обнаруживать и использовать ваши службы напрямую.
Дополнительные ресурсы
- Мартин Фаулер. Модель зрелости Ричардсона Описание модели REST.
https://martinfowler.com/articles/richardsonMaturityModel.html - Swagger Официальный сайт.
https://swagger.io/
Push-уведомления и связь в режиме реального времени по протоколу HTTP
Другой вариант (обычно используется не для тех же целей, что и REST) — это связь "один ко многим" в режиме реального времени с платформами более высокого уровня, например ASP.NET SignalR, и такими протоколами, как WebSockets.
Как показано на рисунке 4-17, связь в режиме реального времени по протоколу HTTP означает, что серверный код может принудительно отправлять содержимое подключенным клиентам, когда данные становятся доступны, а не ждать, пока клиент запросит новые данные.
Рис. Асинхронное взаимодействие "один ко многим" на основе сообщений в режиме реального времени
SignalR — это лучший способ достичь взаимодействия в реальном времени для передачи содержимого клиентам с внутреннего сервера. Поскольку взаимодействие происходит в режиме реального времени, изменения отображаются в клиентских приложениях почти моментально. Обычно это обрабатывается таким протоколом, как WebSockets, с помощью множества подключений WebSockets (по одному для каждого клиента). Типичный пример — служба передает изменение счета матча множеству клиентских веб-приложений одновременно.