Поделиться через


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

Подсказка

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

Архитектура микросервисов .NET для приложений .NET в контейнерах, миниатюра обложки электронной книги.

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

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

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

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

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

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

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

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

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

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

Контекст группы разработки

Мы также предполагаем следующее о процессе разработки для приложения:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Схема клиентских приложений с помощью eShopOnContainers в одном узле Docker.

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

На приведенной выше схеме показано, что клиенты Mobile и 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, а затем развертывать и запускать их в среде разработки Docker. В любом случае использование контейнеров для источников данных позволяет разработчикам создавать и развертывать их в течение нескольких минут, не подготавливая внешнюю базу данных или любой другой источник данных с жесткими зависимостями от инфраструктуры (облачной или локальной).

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

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

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

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

Такое решение на основе микрослужб имеет множество преимуществ:

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

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

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

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

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

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

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

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

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

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

Такое решение на основе микрослужб также имеет некоторые недостатки:

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

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

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

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

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

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

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

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

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

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

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

Схема сравнения шаблонов внешней и внутренней архитектуры.

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

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

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

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

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

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

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

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

Схема, показывающая 12 сложных микрослужб в мировой архитектуре полиглота.

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

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

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

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

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