Марк Ричардс

Паттерны архитектуры

программного обеспечения

Общие шаблоны архитектуры и способы их применения


Исходный текст, 2015 / Русский перевод, 2023


Оглавление

Глава 4
Паттерн архитектуры микросервисов
Архитектура микросервисов быстро завоёвывает популярность в отрасли как жизнеспособная альтернатива монолитным приложениям и сервис-ориентированным архитектурам. Поскольку этот архитектурный паттерн всё ещё развивается, в отрасли существует много путаницы в том, что это за паттерн и как он реализуется. Этот раздел работы предоставит вам ключевые концепции и базовые знания, необходимые, чтобы понять преимущества (и компромиссы) этого важного архитектурного шаблона и того, подходит ли он для вашего приложения.
Описание паттерна
Независимо от выбранной вами топологии или стиля реализации, существует несколько базовых концепций, которые применяются к основному архитектурному паттерну. Первая из этих концепций — это понятие автономно-развёрнутых единиц (separately deployed units). Как показано на Рисунке 4-1, каждый компонент архитектуры микросервисов развёртывается как автономная единица, что упрощает развёртывание благодаря эффективному и оптимизированному конвейеру доставки, повышает масштабируемость и обеспечивает высокую степень разделения компонентов в вашем приложении.
Возможно наиболее важной концепцией, которую необходимо понять при использовании этого паттерна — это понятие сервис-компонента (service component). Вместо того, чтобы думать о сервисах в рамках архитектуры микросервисов, лучше думать о сервис-компонентах, которые могут варьироваться по гранулярности от одного модуля до большой части приложения. Сервис-компоненты содержат один или несколько модулей (например, классы Java), которые представляют функцию с одним единственным назначением (например, предоставление прогноза погоды для города или посёлка), либо независимую часть большого бизнес-приложения (например, размещение акций или расчёт тарифов автострахования). Разработка правильного уровня гранулярности сервис-компонентов является одной из самых больших проблем в архитектуре микросервисов. Более подробно эта проблема обсуждается в следующем подразделе, посвящённом оркестрации сервис-компонентов.

Рисунок 4-1. Базовый паттерн архитектуры микросервисов

Другая ключевая концепция в структуре архитектуры микросервисов заключается в том, что она представляет собой распределённую архитектуру. Это означает, что все компоненты архитектуры полностью автономны друг от друга и доступны через какой-либо протокол удалённого доступа (например, JMS, AMQP, REST, SOAP, RMI, и т. д.). Распределённый характер этого архитектурного паттерна позволяет добиться превосходных характеристик масштабируемости и развёртывания.
Одна из интересных особенностей архитектуры микросервисов состоит в том, что она возникла из проблем, существующих в других распространённых архитектурных паттернах, а не создавалась как самостоятельное концептуальное решение со своими собственными возможными проблемами реализации. Стиль архитектуры микросервисов развился из двух основных источников: монолитных приложений, разработанных с использованием паттерна слоистой архитектуры, и распределённых приложений, разработанных с использованием паттерна сервис-ориентированной архитектуры.
Эволюционный путь от монолитных приложений к микросервисному архитектурному стилю был вызван, прежде всего, развитием культуры непрерывной доставки (Continuous Delivery), а именно — появления конвейера непрерывной доставки, от разработки кода к его эксплуатации, который упрощает развёртывание приложений. Монолитные приложения обычно состоят из тесно-связанных компонентов, которые являются частью единого развёртываемого модуля, что делает изменение, тестирование и развёртывание приложения громоздким и сложным. Из этой ситуации в своё время сначала родилась культура циклов «ежемесячного развёртывания» (monthly deployment), обычно встречающихся в большинстве крупных ИТ-компаний. Эти факторы обычно приводят к созданию хрупких приложений, которые выходят из строя каждый раз, когда релизится что-то новое. Архитектурный паттерн микросервисов решает эти проблемы путём разделения приложения на несколько разворачиваемых модулей (сервис-компонентов), которые могут быть индивидуально разработаны, протестированы и развёрнуты независимо от других сервис-компонентов.
Другой путь эволюции, который привёл к появлению архитектуры микросервисов, связан с проблемами в приложениях, реализующих паттерн сервис-ориентированной архитектуры (Service-Oriented Architecture). Несмотря на то, что сервис-ориентированная архитектура очень мощная и предлагает беспрецедентные уровни абстракции, разнородные возможности подключения, оркестрацию сервисов и обещает согласование бизнес-целей с возможностями ИТ, она, тем не менее, остаётся сложной, дорогой, часто используемой, трудной для понимания и реализации, избыточной для большинства приложений. Стиль архитектуры микросервисов решает эту проблему путём упрощения понятия сервиса, устранения необходимости оркестрации и упрощения подключения и доступа к сервис-компонентам.
Топологии паттерна
Хотя существуют десятки способов реализации паттерна архитектуры микросервисов, три основные топологии выделяются как наиболее распространённые и популярные:
— топология на основе REST API (API REST-based),
— топология на основе приложений REST (application REST-based) и
— топология централизованного обмена сообщениями (centralized messaging).
Топология на основе REST API (API REST-based) полезна для веб-сайтов, которые предоставляют небольшие, самодостаточные отдельные сервисы через API (application programming interface). Эта топология, показанная на Рисунке 4-2, состоит из очень мелких сервис-компонентов (отсюда и название микросервисы), которые содержат один или два модуля, выполняющих определенные бизнес-функции независимо от остальных сервисов. В этой топологии доступ к микросервисам обычно осуществляется с помощью REST интерфейса, реализованного через отдельно развёрнутый веб-интерфейс API. Примеры этой топологии включают в себя распространённые облачные RESTful веб-сервисы, которые предназначены для выполнения одной цели, созданные такими компаниями, как Yahoo, Google и Amazon.

Рисунок 4-2. Топология на основе REST API

Топология на основе приложений REST отличается от топологии на основе REST API тем, что клиентские запросы принимаются через традиционные веб-интерфейсы или через «толстые клиенты» (толстый клиент — это приложение, обеспечивающее расширенную функциональность независимо от центрального сервера, часто сервер в этом случае является лишь хранилищем данных, а вся работа по обработке и представлению этих данных переносится на машину клиента), а не по простому API.
Как показано на Рисунке 4-3, слой пользовательского интерфейса развёртывается как отдельное веб-приложение, которое дистанционно обращается к отдельно развёрнутым сервис-компонентам (бизнес-функциональность) через простые интерфейсы на основе REST. Сервис-компоненты в этой топологии отличаются от компонентов в топологии на основе REST API тем, что эти сервис-компоненты более крупны в своих размерах и представляют собой небольшую часть бизнес-приложения, в отличие от мелких сервисов, служащих только одной узкой цели. Такая топология характерна для бизнес-приложений размером от малого до среднего и которые имеют относительно низкую степень сложности.

Рисунок 4-3. Топология на основе приложений REST

Другим распространённым подходом в рамках паттерна архитектуры микросервисов является топология централизованного обмена сообщениями. Эта топология (показана на Рисунке 4-4) аналогична топологии предыдущего приложения на основе REST, за исключением того, что вместо использования REST для дистанционного доступа используется облегчённый централизованный брокер сообщений (например, ActiveMQ, HornetQ и т.д.). Очень важно при рассмотрении этой топологии не путать её с паттерном сервис-ориентированной архитектуры или считать её «SOA-Lite». Облегчённый брокер сообщений в этой топологии не выполняет никакой оркестрации, преобразования или сложной маршрутизации. Скорее это просто облегчённый транспорт для доступа к дистанционным сервис-компонентам.
Топология централизованного обмена сообщениями обычно встречается в крупных бизнес-приложениях или приложениях, требующих более сложного контроля над транспортным уровнем между пользовательским интерфейсом и сервис-компонентами. Преимущества этой топологии по сравнению с простой топологией на основе REST, рассмотренной ранее, заключаются в расширенных механизмах организации очередей, асинхронном обмене сообщениями, мониторинге, обработке ошибок и лучшей балансировке нагрузки и масштабируемости. Проблемы единой точки отказа и узких мест в архитектуре, обычно связанные с централизованным брокером, решаются с помощью кластеризации и объединения брокеров (разделение одного брокера на несколько экземпляров для распределения проходящего потока сообщений в зависимости от функциональных областей системы).

Рисунок 4-4. Топология централизованного обмена сообщениями

Избегайте зависимостей и оркестрации
Одной из основных проблем архитектурного паттерна микросервисов является определения правильного уровня гранулярности сервис-компонентов. Если сервис-компоненты получаются слишком крупными, то вы не сможете воспользоваться преимуществами этого архитектурного шаблона (такими как высокие показатели развёртывания, масштабируемости, тестируемости и слабые зависимости). Однако, слишком мелкие сервис-компонеты приведут к необходимости оркестрации сервисов, что превратит вашу лёгкую, стройную и бережливую архитектуру микросервисов в тяжеловесную сервис-ориентированную архитектуру, вместе со всей сложностью, путаницей, расходами и оплошностями, обычно присущими приложениям на основе сервис-ориентированной архитектуре.
Если вы обнаружите, что вам нужна оркестрация сервис-компонентов из пользовательского интерфейса или слоя API приложения, то ваши сервис-компоненты слишком мелкие. Аналогично, если вы обнаружите, что для обработки одного запроса вам необходимо проводить межсервисное взаимодействие между сервис-компонентами, то они либо слишком мелкие, либо неправильно разделены с точки зрения бизнес-функциональности.
Межсервисное взаимодействие, которое может привести к нежелательным соединениям между компонентами, может быть проведено через общую базу данных. Например, если сервис-компоненту, обрабатывающему интернет-заказы нужна информация о клиенте, он может обратиться к базе данных, чтобы извлечь необходимые данные, а не вовлекать функционал сервис-компонента клиента.
Общая база данных может удовлетворить потребности в данных, но как насчет общей функциональности? Если сервис-компонент нуждается в функциональных возможностях, содержащихся в другом сервис-компоненте, или общих для всех сервис-компонентов, иногда можно скопировать общую функциональность между сервис-компонентами. Такими действиями вы нарушаете принцип «DRY» (don’t repeat yourself) — «не повторяйся». Это довольно распространённая практика в большинстве бизнес-приложений, реализующих архитектурный паттерн микросервисов, когда приходится соглашаться на избыточность повторения небольших частей бизнес-логики ради сохранения независимости сервис-компонентов и разделения их развёртывания. В эту категорию повторяющегося кода могут попасть небольшие обслуживающие классы (utility services).
Если вы обнаружите, что независимо от уровня гранулярности сервис-компонентов вам всё равно не удаётся избежать оркестрации сервис-компонентов, то это явный признак того, что этот архитектурный паттерн не подходит для вашего приложения. Из-за распределённого характера этого шаблона очень трудно поддерживать единую транзакционную работу между сервис-компонентами. Такая практика потребует использования фреймворка компенсации транзакций с целью отката транзакций (rolling back transactions), что значительно усложняет этот простой и элегантный архитектурный паттерн.
Рекомендации
Архитектурный паттерн микросервисов решает многие общие проблемы, встречающиеся как в монолитных приложениях, так и сервис-ориентированных архитектурах. Поскольку основные компоненты приложения разделяются на более мелкие, отдельно развёртываемые единицы, приложения, построенные с использованием архитектурного паттерна микросервисов, обычно более устойчивые, обеспечивают повышение характеристики масштабируемости и могут легче поддерживать непрерывную доставку (Continuous Delivery).
Ещё одним преимуществом этого паттерна является то, что он обеспечивает возможность развёртывания в режиме реального времени, тем самым значительно снижая потребность в традиционных ежемесячных развёртываниях или развёртываниях «большого взрыва» (big bang) по выходным. Поскольку изменения, как правило, изолированы на уровне конкретных сервис-компонентов, необходимо развёртывать только те сервис-компоненты, которые изменяются. Если у вас есть только один сервис-компонент, вы можете написать специализированный код в приложении пользовательского интерфейса, чтобы обнаружить активное оперативное развёртывание (hot-deployment) и перенаправить пользователей на страницу с ошибкой или страницу ожидания. Кроме того, можно заменить несколько экземпляров сервис-компонентов во время развёртывания в реальном времени. Это обеспечивает непрерывную доступность во время циклов развёртывания (что очень сложно сделать с помощью паттерна слоистой архитектуры).
Последняя рекомендация, которую стоит принять во внимание про паттерн архитектуры микросервисов — так как этот паттерн является распределённой архитектурой он наследует некоторые из тех же самых сложных проблем, которые встречаются в паттерне событийно-ориентированной архитектуре. В частности, проблемы создания, обслуживания и управления контрактами, контроля и обеспечения доступности распределённых узлов, дистанционной аутентификации и авторизации.
Анализ паттерна
Далее приведены оценки и общий анализ характеристик паттерна архитектуры микросервисов. Оценка для каждой из характеристик основана на естественном проявлении данной характеристики, как способности, качества (архитектуры), проявляющейся при реализации паттерна, а также на том, чем в целом известен этот паттерн. Для сопоставительного сравнения и получения представления о том, как этот паттерн соотносится с остальными паттернами нашего обзора, обратитесь к Приложению А в конце книги.
Общая адаптивность (Overall agility)
Оценка: Высокая
Анализ: Общая адаптивность — это способность быстро реагировать на постоянно меняющуюся среду. Благодаря понятию отдельно развёрнутых единиц, внесение изменений обычно изолировано в отдельных сервис-компонентах, что обеспечивает быстрое и простое развёртывание. Также, приложения, созданные с использованием этого паттерна, очень слабо связаны друг с другом, что помогает облегчить внесение изменений.
Простота развёртывания (Ease of deployment)
Оценка: Высокая
Анализ: Характеристики развёртывания шаблона микросервисов оцениваются очень высоко благодаря крупным и независимым дистанционным сервисам. Сервисы обычно развёртываются как отдельные единицы ПО, что даёт возможность выполнять «оперативные развёртывания» (hot deployments) в любое время дня и ночи. Общий риск развёртывания также значительно снижается, так как сбои при развёртывании могут быть устранены быстрее и влияют только на функционирование обновляемого сервиса. Поэтому все остальные функции системы продолжают работать как ни в чём ни бывало.

Тестируемость
Оценка: Высокая
Анализ: Благодаря разделению и изоляции функциональных возможностей бизнес на независимые приложения, тестирование может быть ограничено, что позволяет проводить его более целенаправленно. Регрессионное тестирование для конкретного сервис-компонента намного проще и целесообразнее, чем регрессионное тестирование для всего монолитного приложения. Кроме того, поскольку сервис-компоненты в этом шаблоне слабо связаны, то с точки зрения разработки, гораздо меньше шансов внести изменение, которое нарушит работу другой части приложения. Это облегчает нагрузку на тестирование, исключая необходимость перепроверять всё приложение целиком из-за одного небольшого изменения.
Производительность
Оценка: Низкая
Анализ: Хотя вы можете создавать приложения, реализованные на основе этого шаблона, которые будут работать очень хорошо, в целом этот шаблон не подходит для высокопроизводительных приложений. Причина этому — распределённый характер паттерна архитектуры микросервисов.
Масштабируемость
Оценка: Высокая
Анализ: Поскольку приложение разделено на отдельно развёрнутые единицы, каждый сервис-компонент может быть масштабирован по отдельности. Это обеспечивает возможность точной настройки масштабирования всего приложения. Например, область администрирования приложения для торговли акциями может не нуждаться в масштабировании из-за низкого количества пользователей этой функциональности. И наоборот, сервис-компонент размещения сделок может нуждаться в масштабировании из-за высокой пропускной способности, необходимой большинству приложений для торговли.
Простота разработки
Оценка: Высокая
Анализ: Поскольку функциональные возможности выделены в отдельные сервис-компонеты, их разработка становится более простой из-за меньшего и изолированного объёма. Гораздо меньше вероятность того, что разработчик внесёт изменения в один сервис-компонент, которые повлияют на другие сервис-компонеты, соответственно это снижает необходимость координации между разработчиками и командами разработки.