Проектирование ориентированного на микрослужбы приложения

Совет

Это содержимое является фрагментом из электронной книги, архитектуры микрослужб .NET для контейнерных приложений .NET, доступных в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно читать в автономном режиме.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

В этом разделе рассматривается разработка гипотетического серверного корпоративного приложения.

Спецификации приложения

Это гипотетическое приложение обрабатывает запросы путем выполнения бизнес-логики, обращения к базам данных и последующего возврата ответов HTML, JSON или XML. Мы хотим сказать, что приложение должно поддерживать различные клиенты, включая браузеры рабочего стола, в которых работают одностраничные приложения (SPA), классические веб-приложения, мобильные веб-приложения и собственные мобильные приложения. Приложение также может предоставлять интерфейс API для использования третьими лицами. Также должна быть предусмотрена возможность асинхронной интеграции его микрослужб или внешних приложений, поскольку такой подход помогает повысить устойчивость микрослужб в случае частичных сбоев.

Приложение будет состоять из следующих типов компонентов.

  • Компоненты уровня представления. Они отвечают за обработку пользовательского интерфейса и использование удаленных служб.

  • Логика предметной области или бизнес-логика. Это логика домена приложения.

  • Логика доступа к базе данных. Сюда входят компоненты доступа к данным, обеспечивающие доступ к базам данных (SQL или NoSQL).

  • Логика интеграции приложений. Сюда входит канал обмена сообщениями, построенный на брокерах сообщений.

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

Должна быть предусмотрена возможность развертывания приложения в нескольких инфраструктурных средах (в общедоступных облачных и локальных средах), и желательно, чтобы это приложение было кроссплатформенным, способным легко перемещаться с Linux на Windows (или наоборот).

Контекст команды разработчиков

Также предполагаются следующие особенности процесса разработки для приложения.

  • Имеется несколько команд разработчиков, работающих над разными областями приложения.

  • Новые члены команд должны быстро включаться в работу, и приложение должно быть простым для понимания и легко модифицируемым.

  • Приложение будет иметь долгий срок развития и постоянно меняющиеся бизнес-правила.

  • Вам потребуется хорошая долгосрочная поддержка, что означает гибкость при реализации будущих изменений и возможность обновления нескольких подсистем с минимальным воздействием на другие подсистемы.

  • Вы хотите реализовать непрерывную интеграцию и непрерывное развертывание приложения.

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

Выбор архитектуры

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

При таком подходе каждая служба (контейнер) реализует набор целостных и тесно связанных функций. Например, приложение может состоять из таких служб, как служба каталога, служба заказов, служба корзины, служба профилей пользователей и т. п.

Микрослужбы взаимодействуют с помощью протоколов, таких как HTTP (REST), но также и по возможности асинхронно (например, с помощью AMQP), особенно при распространении обновлений с помощью событий интеграции.

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

Каждая микрослужба имеет собственную базу данных, что позволяет ей быть полностью отделенной от других микрослужб. При необходимости согласованность между базами данных из разных микрослужб достигается с помощью событий интеграции на уровне приложения (через логическую шину событий), как показано в разделе "Команда" и "Разделение ответственности запросов" (CQRS). Из-за этого бизнес-ограничения должны учитывать итоговую согласованность между несколькими микрослужбами и связанными базами данных.

eShopOnContainers: эталонное приложение для .NET и микрослужб, развернутых с помощью контейнеров

Чтобы вы могли сосредоточиться на архитектуре и технологиях и не думать о гипотетической области бизнеса, в которой вы можете не разбираться, мы выбрали хорошо известную область бизнеса, а именно упрощенное приложение электронной коммерции (интернет-магазин), которое представляет каталог продукции, принимает заказы от клиентов, проверяет запасы и выполняет другие бизнес-функции. Исходный код этого приложения на основе контейнеров можно найти в репозитории GitHub eShopOnContainers.

Приложение состоит из нескольких подсистем, в том числе нескольких внешних пользовательских интерфейсов магазина (веб-приложения и собственного мобильного приложения), а также внутренних микрослужб и контейнеров для всех необходимых серверных операций с несколькими шлюзами API в качестве консолидированных точек входа во внутренние микрослужбы. На рис. 6-1 показана архитектура этого эталонного приложения.

Diagram of client apps using eShopOnContainers in a single Docker host.

Рис. 6-1. Архитектура эталонного приложения eShopOnContainers для среды разработки

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

Среда размещения. На рис. 6-1 вы видите несколько контейнеров, развернутых в одном узле Docker. Этого можно добиться, выполнив развертывание на один узел Docker с помощью команды docker-compose up. Однако если вы используете оркестратор или контейнерный кластер, каждый контейнер может работать на другом узле, и на любом узле может выполняться любое количество контейнеров, как описывалось ранее в разделе, посвященном архитектуре.

Архитектура взаимодействия. В приложении EShopOnContainers используются два типа взаимодействия, в зависимости от вида функционального действия (запросы или обновления и транзакции).

  • Связь по протоколу HTTP между клиентом и микрослужбой через шлюзы API. Такое взаимодействие используется для запросов и при получении обновлений или команд транзакций от клиентских приложений. Использование шлюзов API подробно описано в следующих разделах.

  • Асинхронное взаимодействие на основе событий. Такое взаимодействие осуществляется через шину событий для распространения обновлений на микрослужбы или для интеграции с внешними приложениями. Шина событий может быть реализована с помощью любой технологии инфраструктуры брокера обмена сообщениями, таких как RabbitMQ, или с помощью более высокого уровня (абстракции) служебных автобусов, таких как Служебная шина Azure, NServiceBus, MassTransit или Brighter.

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

Владение данными в каждой микрослужбе

В этом примере приложения каждая микрослужба имеет свою собственную базу данных или источник данных, хотя все базы данных SQL Server развертываются как отдельный контейнер. Такое проектное решение было принято только для того, чтобы облегчить разработчику получение кода из GitHub, его клонирование и открытие в Visual Studio или Visual Studio Code. Или же оно облегчает компиляцию пользовательских образов Docker с помощью .NET CLI и Docker CLI с последующим развертыванием и запуском их в среде разработки Docker. В любом случае использование контейнеров для источников данных позволяет разработчикам выполнять сборку и развертывание за считанные минуты без необходимости подготовки внешней базы данных или любого другого источника данных с жесткими зависимостями от инфраструктуры (облачной или локальной).

В реальной рабочей среде для обеспечения высокой доступности и масштабируемости базы данных должны размещаться на серверах баз данных в облаке или в локальной среде, но не в контейнерах.

Таким образом, единицы развертывания для микрослужб (и даже для баз данных в этом приложении) представляют собой контейнеры Docker, а эталонное приложение является многоконтейнерным приложением, использующим принципы микрослужб.

Дополнительные ресурсы

Преимущества решения на основе микрослужб

Решение на основе микрослужб, такое как представлено здесь, имеет много преимуществ:

Каждая микрослужба относительно невелика, соответственно ею легко управлять и развивать. В частности:

  • Разработчик может легко разобраться и быстро приступить к работе с хорошей производительностью.

  • Контейнеры запускаются быстро, что повышает производительность разработчиков.

  • Интегрированная среда разработки, такая как Visual Studio, может загружать небольшие проекты быстрее, что тоже повышает производительность разработчиков.

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

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

Можно разделить работу между несколькими командами разработки. Каждая служба может принадлежать одной команде разработки. Каждая команда может управлять своими службами, разрабатывать, развертывать и масштабировать их независимо от остальных команд.

Проблемы более изолированы. Если в одной службе возникает проблема, она изначально влияет только на эту службу (за исключением случая использования неправильного проекта с прямыми зависимостями между микрослужбами), и другие службы могут продолжать обрабатывать запросы. Напротив, один вышедший из строя компонент в архитектуре монолитного развертывания может привести к остановке всей системы, особенно когда речь идет о ресурсах, например об утечке памяти. Кроме того, после устранения проблемы в микрослужбе можно развернуть только затронутую микрослужбу без ущерба для остальной части приложения.

Можно использовать новейшие технологии. Так как возможно независимо разрабатывать службы и запускать их параллельно (благодаря контейнерам и .NET), вы можете приступать к использованию последних технологий и платформ в нужный момент и не застревать на устаревшем стеке или платформе для всего приложения.

Недостатки решения на основе микрослужб

Решение на основе микрослужб, такое как представлено здесь, имеет некоторые недостатки:

Распределенное приложение. Распределенное приложение усложняет разработчикам проектирование и создание служб. Например, разработчики должны реализовать взаимодействие между службами с помощью таких протоколов, как HTTP или AMQP, что упрощает тестирование и обработку исключений. Это также приводит к увеличению задержек в системе.

Сложность развертывания. Приложение, имеющее множество типов микрослужб, которому требуется высокий уровень масштабируемости (должна быть предусмотрена возможность создания большого количества экземпляров каждой и балансировки этих служб на большом числе узлов), подразумевает высокую степень сложности развертывания для ИТ-операций и управления. Если вы не используете инфраструктуру на основе микрослужб (например, оркестратор и планировщик), то на реализацию этой дополнительной сложности может потребоваться гораздо больше усилий разработчиков, чем на само бизнес-приложение.

Атомарные транзакции. Атомарные транзакции между несколькими микрослужбами обычно невозможны. Бизнес-требования должны включать итоговую согласованность между несколькими микрослужбами. Дополнительные сведения см. в проблемах обработки идемпотентных сообщений.

Увеличенная потребность в глобальных ресурсах (таких как общий объем памяти, диски и сетевые ресурсы для всех серверов или узлов). Во многих случаях при замене монолитного приложения на приложение с микрослужбами объем изначальных глобальных ресурсов, необходимых новому приложению на основе микрослужб, будет больше инфраструктурных требований исходного монолитного приложения. Это объясняется тем, что более высокий уровень детализации и распределенные службы требуют больше глобальных ресурсов. Однако учитывая низкую стоимость ресурсов в целом и преимущества возможности масштабирования определенных частей приложения, повышенная стоимость ресурсов в сравнении с долгосрочными затратами при разработке монолитных приложений обычно является лучшим выбором для больших, долгосрочных приложений.

Проблемы с прямым взаимодействием клиента и микрослужбы. Когда приложение большое и имеет десятки микрослужб, возникают проблемы и ограничения, если приложению требуется прямое взаимодействие клиента с микрослужбой. Одна из проблем заключается в возможном несоответствии между потребностями клиента и интерфейсами API, предоставляемыми каждой микрослужбой. В некоторых случаях клиентскому приложению может потребоваться выполнить много отдельных запросов для создания пользовательского интерфейса, что может оказаться неэффективным в Интернете и непрактичным в мобильной сети. Таким образом, запросы от клиентского приложения в серверную систему необходимо максимально сократить.

Другая проблема, связанная с прямым взаимодействием клиента с микрослужбой, состоит в том, что некоторые микрослужбы могут использовать протоколы, не адаптированные для Интернета. Одна служба может использовать двоичный протокол, в то время как другая — обмен сообщениями AMQP. Эти протоколы не адаптированы для брандмауэра, и их лучше использовать внутренним образом. Как правило, для взаимодействия за пределами брандмауэра в приложении следует использовать такие протоколы, как HTTP и WebSockets.

Еще одним недостатком прямого взаимодействия клиента со службами является то, что оно затрудняет выполнение рефакторинга контрактов для этих микрослужб. Со временем разработчикам может потребоваться изменить способ разделения системы на службы. Например, они могут объединить две службы или разбить службу на несколько служб. Однако если клиенты взаимодействуют со службами напрямую, выполнение такого рода рефакторинга может нарушить совместимость с клиентскими приложениями.

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

Разделение на микрослужбы. Наконец, независимо от подхода, выбранного для архитектуры микрослужб, еще одна задача состоит в том, чтобы решить, как разделить все приложение на несколько микрослужб. Как упоминалось в разделе, посвященном архитектуре, существует несколько методов и подходов. В сущности, вам нужно определить области приложения, которые отделены от других областей и имеют небольшое количество жестких зависимостей. Во многих случаях это согласуется с разделением на службы по варианту использования. Например, в нашем приложении интернет-магазина имеется служба заказов, отвечающая за всю бизнес-логику, относящуюся к процессу заказа. У нас также есть служба каталога и служба корзины, которые реализуют другие возможности. Желательно, чтобы каждая служба имела только небольшой набор задач. Это аналогично принципу разделения обязанностей (SRP), используемому для классов, который утверждает, что класс должен иметь только одну причину для изменения. Однако в данном случае это относится к микрослужбам, поэтому область будет больше, чем один класс. Прежде всего, микрослужба должна быть автономной, с начала до конца, включая ответственность за свои источники данных.

Внешняя и внутренняя архитектура и конструктивные шаблоны

Внешняя архитектура — это архитектура микрослужб, состоящая из нескольких служб и соответствующая принципам, описанным в разделе "Архитектура" данного руководства. Однако в зависимости от характера каждой микрослужбы и независимо от выбранной высокоуровневой архитектуры микрослужб распространенный и иногда рекомендуемый подход заключается в наличии разных внутренних архитектур на основе разных шаблонов для различных микрослужб. Микрослужбы даже могут использовать разные технологии и языки программирования. На рис. 6-2 показано это разнообразие.

Diagram comparing external and internal architecture patterns.

Рис. 6-2. Внешняя и внутренняя архитектура и конструктивные шаблоны

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

Другая причина использования разных технологий для микрослужб может заключаться в сути каждой микрослужбы. Например, лучше использовать функциональный язык программирования, например F#, или даже язык, например R, если вы нацелены на домены искусственного интеллекта и машинного обучения, а не более объектно-ориентированный язык программирования, например C#.

Суть в том, что каждая микрослужба может иметь другую внутреннюю архитектуру на основе других конструктивных шаблонов. Не все микрослужбы следует реализовывать с помощью расширенных шаблонов DDD, так как это может оказаться чрезмерным техническим усложнением. Аналогично, сложные микрослужбы с постоянно меняющейся бизнес-логикой не следует реализовывать как компоненты CRUD, иначе вы можете получить код низкого качества.

Новый мир: множество архитектурных шаблонов и многоязыковые микрослужбы

Существует множество архитектурных шаблонов, используемых архитекторами и разработчиками программного обеспечения. Ниже приведены некоторые из них (смешанные архитектурные стили и архитектурные шаблоны).

Вы также можете создавать микрослужбы с множеством технологий и языков, таких как ASP.NET веб-API Core, NancyFx, ASP.NET Core SignalR (доступно с .NET Core 2 или более поздней версии), F#, Node.js, Python, Java, C++, GoLang и многое другое.

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

Diagram showing 12 complex microservices in a polyglot world architecture.

Рис. 6-3. Множество архитектурных шаблонов и многоязыковые микрослужбы

Шаблоны с несколькими архитектурами и многоязыковые микрослужбы позволяют комбинировать языки и технологии в зависимости от потребностей микрослужбы, при этом службы по-прежнему смогут взаимодействовать друг с другом. Как показано на рис. 6-3, в приложениях, состоящих из многих микрослужб (с ограниченным контекстом в технологии с предметно-ориентированным проектированием, или просто "подсистем" как автономных микрослужб), можно реализовывать каждую микрослужбу по-разному. Каждая микрослужба может иметь свой архитектурный шаблон и использовать разные языки и базы данных в зависимости от характера приложения, бизнес-требований и приоритетов. В некоторых случаях микрослужбы могут быть аналогичными. Но обычно это не так, потому что границы контекста и требования каждой подсистемы, как правило, различны.

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

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

Не существует простого решения или правильного архитектурного шаблона для каждого конкретного случая. У вас не может быть "один шаблон архитектуры для правила всех". В зависимости от приоритетов каждой микрослужбы необходимо выбрать другой подход для каждого из них, как описано в следующих разделах.