Skip to content

Microservices Patterns (With examples in Java) - Kris Richardson

Refs:

Chapter 1: Escaping monolithic hell

Преимущества монолитной архитектуры:

  • Простота разработки — IDE и другие инструменты разработки сосредоточены на построении единого приложения.
  • Легкость внесения радикальных изменений — вы можете поменять код и структуру базы данных, а затем собрать и развернуть полученный результат.
  • Простота тестирования — разработчики написали сквозные тесты, которые запускали приложение, обращались к REST API и проверяли пользовательский интерфейс с помощью Selenium.
  • Простота развертывания — разработчику достаточно было скопировать WAR-файл на сервер с установленной копией Tomcat.
  • Легкость масштабирования — компания FTGO запускала несколько экземпляров приложения, размещенных за балансировщиком нагрузки.

Недостатки монолитной архитектуры:

  • Высокая сложность пугает разработчиков
  • Медленная разработка
  • Длинный и тяжелый путь от сохранения изменений до их развертывания
  • Трудности с масштабированием
  • Сложно добиться надежности монолитного приложения
  • Зависимость от постепенно устаревающего стека технологий

Scale cube (Куб масштабирования) - Martin Abbott and Michael Fisher’s excellent book, The Art of Scalability (Addison-Wesley, 2015)

  • Масштабирование по оси X распределяет запросы между несколькими экземплярами
  • Масштабирование по оси Z направляет запросы в зависимости от их атрибутов
  • Масштабирование по оси Y разбивает приложение на сервисы с разными функциями

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

Сравнение SOA и микросервисов

Параметр SOA Микросервисы
Межсервисное взаимодействие Умные каналы, такие как сервисная шина предприятия, с использованием тяжеловесных протоколов вроде SOAP и других веб-сервисных стандартов Примитивные каналы, такие как брокер сообщений, или прямое взаимодействие между сервисами с помощью легковесных протоколов наподобие REST или gRPC
Данные Глобальная модель данных и общие БД Отдельные модель данных и БД для каждого сервиса
Типовой сервис Крупное монолитное приложение Небольшой сервис

SOA использует Enterprise Service Bus (сервисная шина предприятия, ESB) — умный канал, который интегрирует сервисы с помощью бизнес-логики и кода для обработки сообщений.

Достоинства микросервисной архитектуры:

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

Недостатки микросервисной архитектуры:

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

В 1986 году Фред Брукс (Fred Brooks), автор книги The Mythical Man-Month (Addison-Wesley Professional, 1995), высказал мнение, что при разработке программного обеспечения не существует универсальных решений. Это означает, что нет таких методик или технологий, применение которых повысит вашу продуктивность в десять раз.

Во многих дискуссиях такого рода мнения слишком расходятся. Для этого феномена даже есть специальный термин — Suck/Rock Dichotomy Нил Форд (Neal Ford), который иллюстрирует ситуацию, когда в мире программного обеспечения все либо очень хорошо, либо очень плохо.

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

Pattern (Шаблон проектирования) — это многоразовое решение проблемы, возникающей в определенном контексте. Это идея, которая возникает как часть реальной архитектуры и затем показывает себя с лучшей стороны при проектировании программного обеспечения. Концепцию шаблонов предложил Кристофер Александер (Christopher Alexander), практикующий архитектор программных систем. Ему также принадлежит идея pattern language (языка шаблонов) — набора шаблонов проектирования, которые решают проблемы в определенной области. В его книге A Pattern Language: Towns, Buildings, Construction (Oxford University Press, 1977) описывается язык для архитектуры, состоящей из 253 шаблонов.

Распространенная структура шаблонов включает в себя три особо важных раздела:

  • forces (причины)
  • resulting context (итоговый контекст)
  • related patterns (родственные шаблоны)

related patterns описывает связь между действующим и другими шаблонами проектирования. Связь бывает пяти типов.

  1. Predecessor (Предшественник)
  2. Successor (Преемник)
  3. Alternative (Альтернатива)
  4. Generalization (Обобщение)
  5. Specialization (Специализация)

Microservice architecture pattern language (Язык шаблонов микросервисной архитектуры) — это набор методик, которые помогают в проектировании приложений на основе микросервисов.

Application architecture patterns (шаблоны архитектуры приложения):

  • Monolithic architecture pattern
  • Microservice architecture pattern

Microservice architecture pattern:

  • Infrastructure patterns — решают проблемы, в основном касающиеся инфраструктуры и не относящиеся к разработке.
  • Application infrastructure — предназначены для инфраструктурных задач, влияющих на разработку.
  • Application patterns — решают проблемы, с которыми сталкиваются разработчики.

Джез Хамбл (Jez Humble) предлагает следующее определение непрерывной доставки: «Непрерывная доставка — это возможность доставлять изменения всех типов (включая новые возможности, обновления конфигурации, заплатки и экспериментальные функции) в продукт или пользователям безопасным, быстрым и устойчивым способом».

Резюме

  • В соответствии с монолитной архитектурой приложение структурируется в виде единой развертываемой сущности.
  • В микросервисной архитектуре система разбивается на независимо развертываемые сервисы, каждый со своей базой данных.
  • Монолитная архитектура подходит для простых приложений, а микросервисы обычно являются лучшим решением для крупных, сложных систем.
  • Микросервисная архитектура ускоряет темп разработки программного обеспечения, позволяя небольшим автономным командам работать параллельно.
  • Микросервисная архитектура не панацея. У нее есть существенные недостатки, такие как повышенная сложность.
  • Язык шаблонов микросервисной архитектуры — это набор методик, которые облегчают проектирование приложений на основе микросервисов. Он помогает решить, следует ли использовать микросервисную архитектуру, и, если она вам подходит, эффективно ее применять.
  • Для ускорения доставки программного обеспечения одной микросервисной архитектуры недостаточно. Чтобы разработка оказалась успешной, вам также нужно задействовать DevOps и сформировать небольшие автономные команды.
  • Не забывайте о человеческом факторе перехода на микросервисы. Чтобы он стал успешным, следует учитывать эмоциональный настрой работников.

Chapter 2: Decomposition strategies

Программная архитектура вычислительной системы — это набор структур, необходимых для ее обсуждения и состоящих из программных элементов, связей между ними и свойств, присущих этим элементам и связям (Bass L. et al. Documenting Software Architectures).

Филлип Кратчен (Phillip Krutchen) написал классический документ, посвященный модели представлений программной архитектуры вида 4 + 1 Architectural Blueprints — The “4 + 1” View Model of Software Architecture

Модель 4 + 1 определяет четыре разных представления архитектуры программного обеспечения. Каждое из них описывает определенный аспект архитектуры и состоит из конкретного набора программных элементов и связей между ними.

  • Logical view (Логическое представление) — программные элементы, создаваемые разработчиками. В объектно-ориентированных языках это классы и пакеты. Связи между ними соответствуют отношениям между классами и пакетами, включая наследование, взаимосвязи и зависимости.
  • Implementation view (Представление реализации) — результат работы системы сборки. Это представление состоит из модулей, представляющих упакованный код, и компонентов, которые являются исполняемыми или развертываемыми единицами и содержат один или несколько модулей. В Java модуль имеет формат JAR, а компонентом обычно выступает WAR- или исполняемый JAR-файл. Связь между ними определяется зависимостями между модулями и тем, какие компоненты объединены в тот или иной модуль.
  • Process view (Представление процесса) — компоненты на этапе выполнения. Каждый элемент является процессом, а отношения между процессами представляют межпроцессное взаимодействие.
  • Deployment (Развертывание) — то, как процессы распределяются по устройствам. Элементы в этом представлении состоят из серверов (физических или виртуальных) и процессов. Связи между серверами представляют сеть. Это представление также описывает отношение между процессами и устройствами.

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

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

  • Presentation layer (уровень представления) — содержит код, реализующий пользовательский интерфейс или внешние API;
  • Business logic layer (уровень бизнес-логики) — содержит бизнес-логику;
  • Persistence layer (уровень хранения данных) — реализует логику взаимодействия с базой данных.

Многоуровневая архитектура — отличный пример архитектурного стиля, но у нее есть некоторые существенные недостатки.

  • Single presentation layer (Единый уровень представления) — не учитывает того, что приложение, скорее всего, будет вызываться более чем одной системой.
  • Single persistence layer (Единый уровень хранения данных) — не учитывает того, что приложение, скорее всего, будет взаимодействовать более чем с одной базой данных.
  • Defines the business logic layer as depending on the persistence layer (Уровень бизнес-логики зависит от уровня хранения данных) — теоретически эта зависимость не позволяет тестировать бизнес-логику отдельно от базы данных.

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

Сервис — это автономный, независимо развертываемый программный компонент, который реализует определенные полезные функции

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

Этапы декомпозиции монолита:

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

Определение системных операций

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

  • Создание обобщенной доменной модели
  • Определение системных операций

Системные операции бывают двух видов:

  • команды — системные операции для создания, обновления и удаления данных;
  • запросы — системные операции для чтения (запрашивания) данных.

Системная операция представляет собой запрос, который приложение должно обработать.

Разбиение на сервисы по бизнес-возможностям

Бизнес-возможности организации описывают то, чем она является. Обычно они стабильны, в отличие от того, как организация ведет свой бизнес (этот аспект со временем меняется, иногда до неузнаваемости).

Трудности при разбиении приложения на сервисы:

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

Резюме

  • Архитектура определяет качественные характеристики приложения, влияющие непосредственно на темпы разработки: поддерживаемость, тестируемость и развертываемость.
  • Микросервисная архитектура — это архитектурный стиль, который делает приложение хорошо поддерживаемым, тестируемым и развертываемым.
  • Сервисы в микросервисной архитектуре организованы вокруг бизнес-аспектов (бизнес-возможностей или поддоменов), а не технических характеристик.
  • Существует два вида декомпозиции.
    • Декомпозиция по бизнес-возможностям, которая берет начало в бизнес-архитектуре.
    • Декомпозиция по поддоменам, основанная на предметно-ориентированном проектировании.
  • Вы можете избавиться от божественных классов, которые приводят к запутанным зависимостям и препятствуют декомпозиции, применяя DDD и определяя отдельную доменную модель для каждого сервиса.

Chapter 3: Interprocess communication in a microservice architecture

Inter-process communication (IPC)

Interaction styles

one-to-one one-to-many
Synchronous Request/response
Asynchronous Asynchronous request/response, One-way notifications Publish/subscribe, Publish/async responses

Первый уровень определяет выбор между отношениями «один к одному» и «один ко многим»:

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

Второй уровень определяет выбор между синхронным и асинхронным взаимодействием:

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

Далее перечислены разные виды взаимодействия «один к одному».

  • Запрос/ответ — клиент отправляет сервису запрос и ждет ответа. Он рассчитывает на то, что ответ придет своевременно, и может даже заблокироваться на время ожидания. Этот стиль взаимодействия обычно приводит к жесткой связанности сервисов.
  • Асинхронный запрос/ответ — клиент отправляет запрос, а сервис отвечает асинхронно. Клиент не блокируется на время ожидания, поскольку сервис может долго не отвечать.
  • Однонаправленные уведомления — клиент отправляет сервису запрос, не ожидая (и не получая) ничего в ответ.

Далее перечислены виды взаимодействия «один ко многим».

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

API

Независимо от того, какой механизм IPC вы выберете, важно создать четкое определение API сервиса, используя некий язык описания интерфейсов (interface definition language, IDL). Кроме того, существуют хорошие аргументы в пользу того, что описание сервиса должно начинаться с его API (см. https://programmableweb.com/news/how-to-design-great-apis-api-first-design-and-raml/how-to/2015/07/10). Первым делом вы описываете интерфейс. Затем рассматриваете полученный результат с клиентскими разработчиками. И только после окончания работы над API реализуете сам сервис. Такой подход повышает шансы на то, что ваш сервис будет удовлетворять требованиям клиентов.

Semantic Versioning (Спецификация семантического версионирования) — хорошее руководство по нумерации версий API. Это набор правил, регламентирующих, как использовать и увеличивать номера версий. Семантическое версионирование изначально создавалось для программных пакетов, но вы можете задействовать его для нумерации версий API в распределенных системах.

Backward-compatible changes are additive changes to an API:

  • Adding optional attributes to request
  • Adding attributes to a response
  • Adding new operations

Robustness principle (Принцип устойчивости), который гласит: «Будь консервативен в собственных действиях и либерален к тому, что принимаешь от других».

Суть IPC состоит в обмене сообщениями. Сообщения обычно содержат данные. Форматы сообщений делятся на две категории:

  • текстовые
  • двоичные

В первую категорию входят текстовые форматы, такие какJSON и XML.

Структура XML-документов определяется их спецификацией. Со временем сообщество разработчиков пришло к тому, что JSON тоже нуждается в подобном механизме. Одно из популярных решений состоит в использовании стандарта JSON Schema. JSON schema определяет имена и типы свойств внутри сообщения и то, обязательны ли они. JSON schema может предоставлять не только полезную документацию, но и механизм для проверки входящих сообщений.

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

Вы можете выбрать из нескольких двоичных форматов. К самым популярным относятся Protocol Buffers и Avro. Оба эти формата предоставляют типизированный язык IDL для описания структуры ваших сообщений.

REST

В наши дни стало модным разрабатывать API в стиле RESTful. REST — это механизм IPC, который задействует HTTP (почти всегда). Рой Филдинг (Roy Fielding), создатель REST, дает следующее определение этой технологии: REST предоставляет набор архитектурных ограничений, которые, если их применять как единое целое, делают акцент на масштабируемости взаимодействия между компонентами, обобщенности интерфейсов, независимом развертывании компонентов и промежуточных компонентах, чтобы снизить латентность взаимодействия, обеспечить безопасность и инкапсулировать устаревшие системы.

Ресурс является ключевой концепцией в REST. Он представляет отдельный бизнес-объект или коллекцию бизнес-объектов. Для работы с ресурсами REST использует HTTP-команды, которые указываются с помощью URL. Например, GET-запрос возвращает представление ресурса, часто в виде XML-документа или объекта JSON, хотя допускаются и другие форматы, в том числе двоичные. POST-запрос создает новый ресурс, а PUT-запрос обновляет существующий.

Леонард Ричардсон (Leonard Richardson) предлагает крайне полезную модель зрелости для REST, которая состоит из следующих уровней.

  • Уровень 0, Клиенты обращаются к сервису этого уровня путем выполнения HTTP-запроса типа POST к его единственной конечной точке (URL). Каждый запрос указывает выполняемое действие, цель этого действия (например, бизнес- объект) и различные параметры.
  • Уровень 1. Сервисы этого уровня поддерживают концепцию ресурса. Чтобы выполнить какое-то действие с ресурсом, клиент выполняет POST-запрос, указывая действие и различные параметры.
  • Уровень 2. Для выполнения действий сервисы второго уровня используют НТТР- команды: GET для извлечения, POST для создания и PUT — для обновления. Для задания параметров действия служат параметры и тело запроса, если таковые имеются. Это позволяет сервисам задействовать инфраструктуру протокола HTTP, включая кэширование GET-запросов.
  • Уровень 3. Архитектура сервисов третьего уровня основана на принципе с ужасным названием — HATEOAS (Hypertext as the Engine of Application State — гипертекст в качестве ядра для состояния приложения). Основной его смысл в том, что представление ресурса, возвращаемое GET-запросом, содержит ссылки для выполнения действий с этим ресурсом. Например, клиент может отменить заказ с помощью ссылки в представлении, полученном в результате GET-запроса, который этот заказ извлек. В число преимуществ HATEOAS входит то, что вам больше не нужно встраивать URL-адреса в клиентский код.

Самым популярным REST IDL является спецификация Open API Specification. Она берет начало в открытом проекте Swagger, который представляет собой набор инструментов для разработки и документирования интерфейсов REST API.

Трудности извлечения нескольких ресурсов за один запрос

REST-ресурсы обычно ориентируются на бизнес-объекты, такие как Consumer или Order. Следовательно, при проектировании REST API часто возникает проблема с тем, как позволить клиенту извлекать несколько родственных объектов за один запрос. Представьте, например, что REST-клиент хочет извлечь информацию о заказе и его заказчике. Если строго следовать стандарту REST API, для этого потребуется как минимум два запроса: один для Order, другой — для Consumer. В более сложном сценарии нам пришлось бы передавать данные туда и обратно больше двух раз, что сказалось бы на времени отклика. Чтобы решить эту проблему, можно позволить клиенту извлекать не только сам ресурс, но и все объекты, которые с ним связаны. Например, клиент мог бы извлечь заказ и информацию о заказчике с помощью запроса GET /orders/1345?expand=consumer. В параметре запроса указан ресурс, который нужно вернуть вместе с заказом. Этот подход хорош во многих сценариях, но в более сложных ситуациях его обычно не хватает. К тому же на его реализацию может потребоваться слишком много времени. Все это сделало популярными альтернативные технологии построения API, такие как GraphQL и Netflix Falcor, изначально рассчитанные на эффективное извлечение данных.

Трудности с привязкой операций к НТТР-командам

Еще одна распространенная проблема проектирования REST API связана с тем, как привязать операции, которые вы хотите выполнять с бизнес-объектом, к НТТР- команде. REST API должен использовать команду PUT для обновления, но обновить заказ можно разными способами, например отменить, отредактировать и т. д. К тому же обновление может оказаться неидемпотентным, чем нарушит правила применения PUT. Одно из решений состоит в определении подресурса для обновления отдельных аспектов объекта. Например, у сервиса Order может существовать конечная точка POST /orders/{orderld}/cancel для отмены заказов и POST /orders/{orderld}/revise для их редактирования. Еще одним решением может стать задание команды в параметре запроса. К сожалению, ни одно из них до конца не отвечает принципам RESTful.

Преимущества и недостатки REST

Стандарт REST обладает множеством положительных качеств.

  • Он простой и привычный.
  • API на основе HTTP можно тестировать в браузере, используя, к примеру, расширение Postman, или в командной строке с помощью curl (при условии, что вы применяете JSON или другой текстовый формат).
  • Он имеет встроенную поддержку стиля взаимодействия вида «запрос/ответ».
  • Протокол HTTP, естественно, дружествен к брандмауэрам.
  • Он не нуждается в промежуточном брокере, что упрощает архитектуру системы.

Однако использование REST имеет и недостатки.

  • Он поддерживает лишь стиль взаимодействия вида «запрос/ответ».
  • Степень доступности снижена. Поскольку клиент и сервис взаимодействуют между собой напрямую, без промежуточного звена для буферизации сообщений, они оба должны работать на протяжении всего обмена данными.
  • Клиенты должны знать местонахождение (URL) экземпляра(-ов) сервиса. Это нетривиальная проблема для современных приложений. Для определения местонахождения экземпляров сервисов клиентам приходится использовать так называемый механизм обнаружения сервисов.
  • Извлечение нескольких ресурсов за один запрос связано с определенными трудностями.
  • Иногда непросто привязать к HTTP-командам несколько операций обновления.

gRPC

Как упоминалось в предыдущем разделе, одна из трудностей применения REST связана с тем, что HTTP поддерживает ограниченный набор команд, из-за чего процесс проектирования REST API с поддержкой нескольких операций обновления не всегда оказывается простым. Одна из технологий, которой удается избежать этой проблемы, — gRPC. Это фреймворк для написания многоязычных клиентов и серверов (см. https://ru.Wikipedia.org/wiki/Удалённый-ВызоВ-Процедур). gRPC представляет собой двоичный протокол на основе сообщений. Как вы помните из обсуждения двоичных форматов, это означает, что проектирование сервиса должно начинаться с его API. API в gRPC описывается с помощью языка IDL на основе Protocol Buffers — многоязычного механизма сериализации структурированных данных от компании Google. Компилятор Protocol Buffer генерирует клиентские заглушки и серверные каркасы. Он поддерживает разные языки, включая Java, С#, NodeJS и GoLang. Клиенты и серверы обмениваются сообщениями в формате Protocol Buffers, используя НТТР/2.

Протокол gRPC обладает несколькими преимуществами.

  • Он позволяет легко спроектировать API с богатым набором операций обновления.
  • Он имеет эффективный компактный механизм IPC, что особенно явно проявляется при обмене крупными сообщениями.
  • Поддержка двунаправленных потоков делает возможными стили взаимодействия на основе RPI и обмена сообщениями.
  • Он позволяет сохранять совместимость между клиентами и сервисами, написанными на совершенно разных языках.

У gRPC есть и несколько недостатков.

  • Процесс работы с API, основанным на gRPC, оказывается для JavaScript-клиентов более трудоемким, чем с API, основанным на REST/JSON.
  • Старые брандмауэры могут не поддерживать НТТР/2.

Протокол gRPC — это полноценная альтернатива REST, хотя оба они представляют собой синхронные коммуникационные механизмы и, как следствие, страдают из-за проблем с частичным отказом.

Circuit breaker

  • Pattern: Circuit breaker
    An RPI proxy that immediately rejects invocations for a timeout period after the number of consecutive failures exceeds a specified threshold.

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

  • Network timeouts
    Никогда не блокируйтесь бессрочно, всегда отсчитывайте время ожидания запроса. Это гарантирует, что когда-нибудь ресурсы освободятся.
  • Limiting the number of outstanding requests from a client to a service (Ограничение количества неудачных запросов от клиента к сервису)
    Установите лимит максимального количества неудавшихся запросов, которые клиент может послать определенному сервису. При исчерпании этого лимита выполнение дальнейших запросов, скорее всего, будет бессмысленным, поэтому такие попытки должны сразу завершаться ошибкой.
  • Circuit breaker pattern
    Отслеживайте количество успешных и неудавшихся запросов. Если частота ошибок превысит некий порог, разомкните предохранитель, чтобы дальнейшие попытки сразу же завершались. Большое количество неудачных запросов говорит о том, что сервис недоступен и обращаться к нему не имеет смысла. По истечении какого-то периода клиент должен предпринять новую попытку и, если она окажется успешной, замкнуть предохранитель.

Service discovery

Клиент нельзя сконфигурировать статически, предоставив ему IP-адреса сервисов. Приложение должно задействовать механизм динамического обнаружения. По своей сути Service discovery (обнаружение сервисов) является довольно простым: его ключевым компонентом выступает service registry (реестр сервисов) — база данных с информацией о том, где находятся экземпляры сервисов приложения.

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

Есть два основных способа реализации механизма обнаружения сервисов.

  • Сервисы и их клиенты напрямую взаимодействуют с реестром
  • За обнаружение сервисов отвечает инфраструктура развертывания

APPLICATION-LEVEL SERVICE DISCOVERY PATTERNS

PLATFORM-PROVIDED SERVICE DISCOVERY PATTERNS

Communicating using the Asynchronous messaging pattern

ABOUT MESSAGES

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

Существует несколько разных видов сообщений:

  • Document — обобщенное сообщение, содержащее только данные. Получатель сам решает, как его интерпретировать. Пример документного сообщения — ответ на команду.
  • Command — сообщение, эквивалентное RPC-запросу. В нем указываются вызываемая операция и ее параметры.
  • Event — сообщение о том, что с отправителем произошло нечто заслуживающее внимания. Событие часто принадлежит к домену и представляет изменение состояния доменного объекта, такого как Order или Customer.

ABOUT MESSAGE CHANNELS

Сообщения передаются по каналам. Бизнес-логика отправителя обращается к интерфейсу исходящего порта, который инкапсулирует внутренний механизм взаимодействия. Исходящий порт реализуется классом-адаптером отправителя, который передает сообщение получателю через канал. Канал сообщений — это инфраструктурная абстракция. Для обработки сообщения вызывается класс-адаптер обработчика сообщений, который обращается к интерфейсу входящего порта, реализованному бизнес-логикой потребителя. Записывать сообщения в канал может любое количество отправителей. Точно так же любое количество получателей может их оттуда читать.

Существует два вида каналов:

  • Канал типа point-to-point
    доставляет сообщения ровно одному потребителю, который считывает их оттуда. Сервисы используют такие каналы для взаимодействия вида «один к одному», описанного ранее. Например, по каналам «точка — точка» часто передают command message.
  • Канал типа publish-subscribe
    доставляет каждое сообщение всем подключенным потребителям. Сервисы применяют такие каналы для взаимодействия вида «один ко многим», описанного ранее. Например, по каналам «издатель — подписчик» обычно рассылают event message.

Implementing the interaction styles using messaging

  • REQUEST/RESPONSE AND ASYNCHRONOUS REQUEST/RESPONSE
  • ONE-WAY NOTIFICATIONS
  • PUBLISH/SUBSCRIBE
  • PUBLISH/ASYNC RESPONSES

DOCUMENTING ASYNCHRONOUS OPERATIONS

service’s operations можно вызывать с помощью одного из двух interaction styles:

  • Request/async response-style API
    состоит из канала команд сервиса, типов и форматов командных сообщений, которые сервис принимает, а также типов и форматов ответных сообщений, отправляемых сервисом.
  • One-way notification-style API
    состоит из канала команд сервиса, а также типов и форматов командных сообщений, которые принимает сервис.

Using a message broker

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

BROKERLESS MESSAGING

В архитектуре без брокера сервисы могут обмениваться сообщениями напрямую. Проект ZeroMQ — популярная реализация этого подхода. Он представляет собой как спецификацию, так и набор библиотек для разных языков. Проект поддерживает разнообразные механизмы передачи данных, включая TCP, доменные сокеты в стиле UNIX и многоадресное вещание.

Отсутствие брокера дает несколько преимуществ.

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

Но какими бы заманчивыми ни были эти преимущества, отсутствие брокера сообщений чревато существенными недостатками.

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

OVERVIEW OF BROKER-BASED MESSAGING

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

Существует много разных брокеров сообщений. Далее перечислены популярные проекты с открытым исходным кодом:

Есть также облачные решения для обмена сообщениями, такие как:

При выборе брокера сообщений следует учитывать различные факторы, включая следующие:

  • Supported programming languages
    Лучше выбрать брокер с поддержкой широкого диапазона языков программирования
  • Supported messaging standards
    Поддерживает ли брокер сообщений стандарт вроде AMQP или STOMP? Использует ли он свой закрытый протокол?
  • Messaging ordering
    Сохраняет ли брокер порядок следования сообщений?
  • Delivery guarantees
    Какие гарантии доставки дает брокер сообщений?
  • Persistence
    Сохраняются ли сообщения на диск? Могут ли они пережить сбой брокера?
  • Durability
    Если потребитель переподключиться к брокеру, получит ли он сообщения, отправленные, пока он был отключен?
  • Scalability
    Насколько масштабируем брокер сообщений?
  • Latency
    Какова сквозная латентность?
  • Competing consumers
    Поддерживает ли брокер сообщений конкурирующих потребителей?

IMPLEMENTING MESSAGE CHANNELS USING A MESSAGE BROKER

Брокеры сообщений реализуют концепцию каналов по-разному

Message broker Point-to-point channel Publish-subscribe channel
JMS Queue Topic
Apache Kafka Topic Topic
AMQP-based brokers, such as RabbitMQ Exchange
Queue
Fanout exchange and a queue per consumer
AWS Kinesis Stream Stream
AWS SQS Queue

BENEFITS AND DRAWBACKS OF BROKER-BASED MESSAGING

  • Loose coupling
  • Message buffering
    The message broker buffers messages until they can be processed. With a synchronous request/response protocol such as HTTP, both the client and service must be available for the duration of the exchange. With messaging, though, messages will queue up until they can be processed by the consumer. This means, for example, that an online store can accept orders from customers even when the order-fulfillment system is slow or unavailable. The messages will simply queue up until they can be processed.
  • Flexible communication
    Messaging supports all the interaction styles described earlier.
  • Explicit interprocess communication
    RPC-based mechanism attempts to make invoking a remote service look the same as calling a local service. But due to the laws of physics and the possibility of partial failure, they’re in fact quite different.

3.3.7 Transactional messaging

3.4 Using asynchronous messaging to improve availability

Summary

  • Микросервисная архитектура является distributed (распределенной), поэтому interprocess communication (межпроцессное взаимодействие) играет в ней ключевую роль.
  • К развитию API сервиса необходимо подходить тщательно и осторожно. Легче всего вносить Backward-compatible (обратно совместимые) изменения, поскольку они не влияют на работу клиентов. При внесении ломающих изменений в API сервиса обычно приходится поддерживать как старую, так и новую версию, пока клиенты не обновятся.
  • Существует множество технологий IPC, каждая со своими trade-offs (достоинствами и недостатками). Ключевое решение на стадии проектирования — выбор между synchronous remote procedure invocation pattern (синхронным удаленным вызовом процедур) и asynchronous Messaging pattern (асинхронными сообщениями). Самыми простыми в использовании являются синхронные протоколы вроде REST, основанные на вызове удаленных процедур. Но в идеале, чтобы повысить уровень доступности, сервисы должны взаимодействовать с помощью асинхронного обмена сообщениями.
  • Чтобы предотвратить лавинообразное накопление сбоев в системе, клиент, использующий синхронный протокол, должен быть способен справиться с частичными отказами — тем, что вызываемый сервис либо недоступен, либо проявляет высокую латентность. В частности, при выполнении запросов следует отсчитывать время ожидания, ограничивать количество просроченных запросов и применять Circuit breaker pattern (шаблон «Предохранитель»), чтобы избежать обращений к неисправному сервису.
  • Архитектура, использующая синхронные протоколы, должна содержать service discovery mechanism (механизм обнаружения), чтобы клиенты могли определить сетевое местонахождение экземпляров сервиса. Проще всего остановиться на механизме обнаружения, который предоставляет платформа развертывания: на шаблоне Server-side discovery («Обнаружение на стороне сервера») и 3rd party registration («Сторонняя регистрация»). Альтернативный подход — реализация обнаружения сервисов на уровне приложения: шаблоны Client-side discovery («Обнаружение на стороне клиента») и Self registration («Саморегистрация»). Этот способ требует больших усилий, но подходит для ситуаций, когда сервисы выполняются на нескольких платформах развертывания.
  • Модель messages (сообщений) и channels (каналов) инкапсулирует детали реализации системы messaging-based (обмена сообщениями() и становится хорошим выбором при проектировании архитектуры этого вида. Позже вы сможете привязать свою архитектуру к конкретной инфраструктуре обмена сообщениями, в которой обычно используется broker (брокер).
  • Ключевая трудность при обмене сообщениями связана с их публикацией и обновлением базы данных. Удачным решением является применение шаблона Transactional outbox («Публикация событий»): сообщение в самом начале записывается в базу данных в рамках транзакции. Затем отдельный процесс извлекает сообщение из базы данных, используя шаблон Polling publisher («Опрашивающий издатель») или Transaction log tailing («Отслеживание транзакционного журнала»), и передает его брокеру.

Chapter 4: Managing transactions with sagas

Summary

  • Некоторым системным операциям нужно обновлять данные, разбросанные по разным сервисам. XA/2PC-based distributed transactions (Распределенные транзакции, основанные на ХА/2РС), — не самый подходящий выбор для современных приложений. Вместо них лучше использовать Saga- Pattern (шаблон «Повествование»). Saga — это последовательность локальных транзакций, которые координируются с помощью сообщений. Каждая локальная транзакция обновляет данные лишь в одном сервисе. При этом все изменения фиксируются, поэтому, если Saga нужно откатить из-за нарушения бизнес-правила, оно должно выполнить compensating transactions (компенсирующие транзакции**), чтобы явно отменить внесенные изменения.
  • Для координации этапов saga можно применять либо choreography (хореографию), либо orchestration (оркестрацию). В choreography-based saga (повествованиях, основанных на хореографии), локальная транзакция публикует события, которые заставляют других участников выполнить свои локальные транзакции. При использовании orchestration-based saga (оркестрации) централизованный оркестратор рассылает участникам сообщения с инструкциями, какие локальные транзакции нужно выполнить. Вы можете упростить разработку и тестирование, смоделировав оркестратор в виде state machines (конечного автомата). Choreography подходит для простых saga, но в сложных случаях лучше применять orchestration.
  • Проектирование бизнес-логики, основанной на saga, может оказаться проблематичным, поскольку saga, в отличие от ACID-транзакций, не изолированы друг от друга. В связи с этим часто приходится задействовать контрмеры — стратегии проектирования, которые предотвращают аномалии конкурентного выполнения, присущие транзакционной модели ACD. Иногда для упрощения бизнес-логики необходимо использовать блокировки, которые сами по себе чреваты взаимным блокированием.

Chapter 5: Designing business logic in a microservice architecture

Шаблон Aggregate («Агрегат») из состава DDD структурирует бизнес-логику приложения в виде набора агрегатов. Aggregate — это кластер объектов, с которыми можно обращаться как с единым целым. Есть две причины, почему агрегаты могут пригодиться при разработке бизнес-логики в микросервисной архитектуре.

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

Благодаря этому ACID-транзакция никогда не выйдет за пределы одного сервиса.

5.1 Business logic organization patterns

Существует два основных шаблона для организации бизнес-логики:

  • процедурный Transaction script pattern («Сценарий транзакции»)
  • объектно-ориентированный Domain model pattern («Доменная модель»).

5.1.1 Designing business logic using the Transaction script pattern

  • Pattern: Transaction script
    Organize the business logic as a collection of procedural transaction scripts, one for each type of request.

5.1.2 Designing business logic using the Domain model pattern

  • Pattern: Domain model
    Organize the business logic as an object model consisting of classes that have state and behavior.

5.1.3 About Domain-driven design

Domain-driven design (DDD, Предметно-ориентированное проектирование), описанное в книге Эрика Эванса Domain-Driven Design, — это более узкая разновидность Object-oriented design (OOD, Объектно-ориентированный дизайн), предназначенная для разработки сложной бизнес-логики.

Subdomains and the associated concept of Bounded Context are two of the strategic DDD patterns.

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

  • Entity (Сущность) — объект, обладающий устойчивой идентичностью. Две сущности, чьи атрибуты имеют одинаковые значения, — это все равно разные объекты. В приложении Java ЕЕ классы, которые сохраняются с помощью аннотации @Entity из JPA, обычно представляют собой сущности DDD.
  • Value object (Объект значений) — объект, представляющий собой набор значений. Два объекта значений с одинаковыми атрибутами взаимозаменяемы. Примером таких объектов может служить класс Money, который состоит из валюты и суммы.
  • Factory (Фабрика) — объект или метод, реализующий логику создания объектов, которую ввиду ее сложности не следует размещать прямо в конструкторе. Фабрика также может скрывать конкретные классы, экземпляры которых создает. Она реализуется в виде статического метода или класса.
  • Repository (Репозиторий) — объект, предоставляющий доступ к постоянным сущностям и инкапсулирующий механизм доступа к базе данных.
  • Service (Сервис) — объект, реализующий бизнес-логику, которой не место внутри сущности или объекта значений.

5.2 Designing a domain model using the DDD aggregate pattern

5.2.2 Aggregates have explicit boundaries

  • Pattern: Aggregate
    Organize a domain model as a collection of aggregates, each of which is a graph of objects that can be treated as a unit.

5.3 Publishing domain events

  • Pattern: Domain event
    An aggregate publishes a domain event when it’s created or undergoes some other significant change.

Summary

  • Процедурный шаблон Transaction script («Сценарий транзакции») часто является хорошим решением для реализации простой бизнес-логики. Но, когда бизнес-логика усложняется, стоит подумать об использовании объектно-ориентированном шаблоне Domain model (доменной модели).
  • Хороший способ организации бизнес-логики сервиса — ее разделение на агрегаты по принципу DDD. Агрегаты делают доменную модель более модульной, исключают возможность применения объектных ссылок между сервисами и гарантируют, что каждая ACID-транзакция выполняется в рамках одного сервиса.
    • При создании или обновлении агрегат должен публиковать domain events (доменные события). Эти события имеют множество сфер применения. В главе 4 вы могли видеть, как они реализуют saga (повествования) с использованием хореографии. А в главе 7 мы поговорим о том, как с их помощью можно обновлять реплицированные данные. Подписчики на доменные события могут уведомлять пользователей и другие приложения, а также публиковать сообщения в клиентском браузере через WebSocket.

Chapter 6: Developing business logic with event sourcing

6.1 Developing business logic using event sourcing

6.1.1 The trouble with traditional persistence

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

  • Object-Relational impedance mismatch (Объектно-реляционный разрыв)
  • Lack of aggregate history (Отсутствие истории агрегатов)
  • Implementing audit logging is tedious and error prone (Реализация журналирования для аудита требует много усилий и чревата ошибками)
  • Event publishing is bolted on to the business logic (Публикация событий является лишь дополнением к бизнес-логике)

6.1.2 Overview of event sourcing

Event sourcing (Порождение событий) — это событийный подход к реализации бизнес-логики и сохранению агрегатов. Агрегат хранится в базе данных в виде цепочки событий. Каждое событие представляет изменение его состояния. Бизнес-логика агрегата структурирована вокруг требования о генерации и потреблении этих событий.

6.1.8 Benefits of event sourcing

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

6.1.9 Drawbacks of event sourcing

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

6.2 Implementing an event store

Event store (Хранилище событий) — это гибрид базы данных и брокера сообщений. Оно ведет себя как БД, потому что у него есть API для вставки и извлечения событий агрегата по первичному ключу. Но оно похоже и на брокер сообщений, потому что у него есть API, который позволяет подписываться на события.

Summary

  • Event sourcing (Порождение событий) сохраняет агрегат в виде последовательности events (событий). Каждое событие описывает либо создание агрегата, либо изменение его состояния. Для воссоздания состояния агрегата приложение воспроизводит события. Этот шаблон сохраняет историю доменного объекта, предоставляет точный журнал аудита и делает возможной надежную публикацию доменных событий.
  • Snapshots (Снимки) улучшают производительность, уменьшая количество событий, которые нужно воспроизводить.
  • События размещаются в event store (хранилище событий) — гибриде базы данных и брокера сообщений. Когда сервис помещает событие в event store, он тем самым доставляет его подписчикам.
  • Eventuate Local — это event store с открытым исходным кодом, основанное на MySQL и Apache Kafka. Разработчики используют фреймворк Eventuate Client для написания агрегатов и event handlers (обработчиков событий).
  • Одна из проблем event sourcing связана с их развитием. При воспроизведении событий приложение может обрабатывать разные их версии. Хорошим решением является приведение к базовому типу, когда события обновляются до последней версии во время загрузки из event store.
  • Удаление данных в приложении на основе event sourcing связано с определенными трудностями. Существуют нормативно-правовые требования, например European Union’s GDPR (Общий регламент по защите данных в Европейском союзе). Для их соблюдения вы должные использовать такие методики, как шифрование и псевдоанонимизация, чтобы иметь возможность удалять данные пользователей.
  • Event sourcing упрощает реализацию choreography-based sagas. У сервисов есть обработчики, которые отслеживают события, публикуемые агрегатами.
  • Event sourcing — хороший подход к реализации saga orchestrators. Благодаря ему вы можете писать приложения, использующие исключительно event store**.

Chapter 7: Implementing queries in a microservice architecture

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

  • API composition pattern (Объединение API)
    Это самый простой подход, который следует использовать везде, где возможно. Он заключается в том, что за обращение к сервисам и объединение результатов отвечают клиенты.
  • Command query responsibility segregation pattern (CQRS, Разделение ответственности командных запросов)
    У этого шаблона, известного как CQRS, больше возможностей по сравнению с предыдущим, но при этом он сложнее. Он требует наличия одной или нескольких баз данных, единственное назначение которых заключается в поддержке запросов.

7.1 Querying using the API composition pattern

7.1.2 Overview of the API composition pattern

  • API composer (API-композитор) — реализует операцию запроса, обращаясь к сервисам-провайдерам;
  • provider service (сервис-провайдер) — сервис, которому принадлежат данные, возвращаемые запросом.

  • Pattern: API composition
    Implement a query that retrieves data from several services by querying each service via its API and combining the results.

7.1.4 API composition design issues

Есть три кандидата на роль API composer:

  • client of the services
  • API gateway, which implements the application’s external API
  • standalone service

7.1.5 The benefits and drawbacks of the API composition pattern

API composition — простой и интуитивно понятный способ реализации запросов в микросервисной архитектуре. Но у него есть некоторые недостатки:

  • дополнительные накладные расходы;
  • риск снижения доступности;
  • нехватка транзакционной согласованности данных.

7.2 Using the CQRS pattern

7.2.2 Overview of CQRS

Существует три проблемы, которые часто встречаются при реализации запросов в микросервисной архитектуре:

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

Шаблон CQRS решает все эти проблемы.

7.2.3 The benefits of CQRS

CQRS имеет как сильные, так и слабые стороны. Начнем с сильных.

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

7.2.4 The drawbacks of CQRS

Наряду с преимуществами CQRS имеет и существенные недостатки:

  • более сложную архитектуру;
  • отставание репликации.

7.3 Designing CQRS views

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

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

7.3.1 Choosing a view datastore

Не так давно СУРБД на основе SQL были единственным видом баз данных, которые использовались для всего на свете. Но с ростом популярности Интернета разные компании начали замечать, что СУРБД больше не отвечают их требованиям к масштабируемости. Это привело к созданию так называемых NoSQL-хранилищ. Базы данных NoSQL обычно поддерживают ограниченный набор транзакций и запросов. В некоторых сценариях они имеют определенные преимущества перед БД на основе SQL, включая более гибкую модель данных, а также лучшие производительность и масштабируемость.

Summary

  • Реализация запросов, извлекающих данные из разных сервисов, сопровождается определенными затруднениями, так как данные каждого сервиса приватные.
  • Для реализации такого рода запросов можно использовать одну из методик: API composition (объединение API) или Command query responsibility segregation (CQRS, разделение ответственности командных запросов).
  • Шаблон API composition (объединения API) собирает данные от разных сервисов. Это самый простой способ реализации запросов. Его следует применять везде, где это возможно.
  • Ограничение шаблона API composition (объединения API) связано с тем, что некоторые сложные запросы требуют неэффективного слияния в памяти больших наборов данных.
  • Шаблон CQRS, реализующий запросы с помощью БД представлений, более мощный, но его не так просто создать.
  • Модуль представлений CQRS должен поддерживать конкурентные обновления, а также обнаружение и отклонение повторяющихся событий.
  • CQRS способствует разделению ответственности за счет того, что сервисы могут реализовывать запросы, которые возвращают данные других сервисов.
  • Клиенты должны быть готовы к отложенной согласованности CQRS-представлений.

Chapter 8: External API patterns

8.2 The API gateway pattern

  • Pattern: API gateway
    Implement a service that’s the entry point into the microservices-based application from external API clients.

API gateway (API-шлюз) — это сервис, который служит точкой входа в приложение из внешнего мира. Он отвечает за маршрутизацию запросов, объединение API и другие возможности, например аутентификацию.

8.2.1 Overview of the API gateway pattern

Analog OOP pattern Facade.

API-шлюз отвечает за:

  • request routing (маршрутизацию запросов), аналогично как reverse proxy
  • API composition (объединение API)
  • protocol translation (преобразование протоколов)

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

API gateway может реализовывать edge functions (граничные функции). Граничная функция, как понятно из названия, — это операция обработки запросов на границе приложения. Такие как:

  • Authentication (аутентификация) — проверка подлинности клиента, который делает запрос;
  • Authorization (авторизация) — проверка того, что клиенту позволено выполнять определенную операцию;
  • Rate limiting (ограничение частоты запросов) — контроль над тем, сколько запросов в секунду могут выполнять определенный клиент и/или все клиенты вместе;
  • Caching—Cache (кэширование) — кэширование ответов для снижения количества запросов к сервисам;
  • Metrics collection (сбор показателей) — сбор показателей использования API для анализа, связанного с биллингом;
  • Request logging (ведение журнала запросов) — запись запросов в журнал.

Выделение API-шлюза для каждого клиента. Это шаблон проектирования BFF (backends for frontendsсерверы для клиентов), который был предложен Филом Кальсадо Phil Calado и его коллегами из SoundCloud.

8.2.4 API gateway design issues

Трудности, которые могут возникнуть при его проектировании API gateway:

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

Summary

  • Обычно внешние клиенты приложения обращаются к его сервисам через API gateway. API gateway предоставляет каждому клиенту отдельный API. Он отвечает за маршрутизацию запросов, объединение API, преобразование протоколов и реализацию таких граничных функций, как аутентификация.
  • У вашего приложения может быть один или несколько API gateways, по одному для каждого типа клиентов. В последнем случае применяется шаблон BFF. Основное его преимущество в том, что он делает команды клиентской разработки более автономными, поскольку каждая из них пишет, развертывает и администрирует собственный API gateway.
  • Существует целый ряд технологий, используемых для реализации API gateway, включая готовые решения. Но вы можете разработать собственный API gateway с помощью фреймворка.
  • Spring Cloud Gateway — это простой в применении фреймворк, который хорошо подходит для разработки API gateways. Для маршрутизации запросов он может задействовать любые их атрибуты, включая метод и путь. Он может направлять запросы напрямую к внутренним сервисам или к пользовательскому методу-обработчику. Этот проект основан на масштабируемых реактивных фреймворках Spring Framework 5 и Project Reactor. Вы можете писать пользовательские обработчики запросов в реактивном стиле на основе таких абстракций, как Mono из состава Project Reactor.
  • Еще одна отличная основа для разработки API gateways — фреймворк GraphQL, предоставляющий графовый язык запросов. Для описания модели серверных данных и запросов, которые она поддерживает, используется схема в виде графа. Она накладывается на ваши сервисы путем написания функций сопоставления, которые извлекают данные. Клиенты, основанные на GraphQL, обращаются к схеме, указывая серверу, какие именно данные он должен вернуть. В итоге API gateway, построенный по этой технологии, поддерживает разные виды клиентов.

Chapter 9: Testing microservices: Part 1

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

  • Ручное тестирование чрезвычайно неэффективно
    Никогда не просите человека сделать то, что у компьютера получается намного лучше. По сравнению с компьютерами люди работают с низкой производительностью и не способны делать это круглосуточно. Если вы полагаетесь на ручное тестирование, забудьте о быстрой и безопасной доставке программного обеспечения. Написание автоматических тестов — это очень важно.
  • Тестирование производится на слишком позднем этапе процесса доставки
    Безусловно, тестирование уже написанного приложения имеет право на существование, но, как показывает опыт, этого недостаточно. Написание автоматических тестов намного лучше сделать частью разработки. Это повысит продуктивность, поскольку разработчики смогут видеть результаты тестирования во время редактирования кода.

9.1 Testing strategies for microservice architectures

9.1.1 Overview of testing

Целью теста является проверка поведения System Under Test (SUT, тестируемой системы). Под system здесь подразумевается элемент программного обеспечения, к которому применяется тест. Это может быть всего лишь класс, или целое приложение, или нечто среднее, например набор классов или отдельный сервис. Коллекция взаимосвязанных тестов формирует test suite (тестовый набор).

WRITING AUTOMATED TESTS

Типичный автоматический тест состоит из четырех этапов:

  1. Setup (Подготовка)
    инициализирует среду тестирования, состоящую из самой системы и ее зависимостей, приводя ее в нужное состояние. Например, создает тестируемый класс и приводит его в состояние, необходимое для демонстрации желаемого поведения.
  2. Exercise (Выполнение)
    запускает тестируемую систему, например вызов метода из тестируемого класса.
  3. Verify (Проверка)
    делает выводы о результате выполнения и состоянии тестируемой системы. Например, проверяет значение, возвращаемое методом, и новое состояние тестируемого класса.
  4. Teardown (Очистка)
    удаляет среду тестирования, если это необходимо. Многие тесты пропускают этот этап, но, например, при тестировании БД иногда нужно откатить транзакции, инициированные на этапе подготовки.
TESTING USING MOCKS AND STUBS

Test double (Дублер) — это объект, который симулирует поведение зависимости.

Существует два вида дублеров: заглушки (stubs) и макеты (mocks). Эти термины часто считают взаимозаменяемыми, хотя они немного различаются. Заглушка — это дублер, который возвращает значения тестируемой системе. Макет — это дублер, используемый тестом для проверки того, что тестируемая система корректно вызывает свои зависимости. Во многих случаях макет является заглушкой.

THE DIFFERENT TYPES OF TESTS

Четыре разных типа тестов:

  • Unit tests (модульные тесты), которые тестируют небольшую часть сервиса, такую как класс;
  • Integration tests (интеграционные тесты), которые проверяют, может ли сервис взаимодействовать с инфраструктурными компонентами, такими как базы данных и другие сервисы приложения;
  • Component tests (компонентные тесты) — приемочные тесты для отдельного сервиса;
  • End-to-end tests (сквозные тесты) — приемочные тесты для целого приложения.

9.1.2 The challenge of testing microservices

CONSUMER-DRIVEN CONTRACT TESTING

9.1.3 The deployment pipeline

Jez Humble’s book, Continuous Delivery (Addison-Wesley, 2010) describes a deployment pipeline as the automated process of getting code from the developer’s desktop into production.

The example deployment pipeline consists of the following stages:

  • Pre-commit tests stage — Runs the unit tests. This is executed by the developer before committing their changes.
  • Commit tests stage — Compiles the service, runs the unit tests, and performs static code analysis.
  • Integration tests stage — Runs the integration tests.
  • Component tests stage — Runs the component tests for the service.
  • Deploy stage — Deploys the service into production.

9.2 Writing unit tests for a service

There are two types of unit tests:

  • Solitary unit test — Tests a class in isolation using mock objects for the class’s dependencies
  • Sociable unit test — Tests a class and its dependencies

Summary

  • Автоматическое тестирование лежит в основе быстрой и безопасной доставки программного обеспечения. К тому же микросервисная архитектура сложна по своей природе, поэтому, чтобы воспользоваться всеми ее преимуществами, вы должны автоматизировать свои тесты.
  • Тест нужен для того, чтобы проверить поведение System Under Test (SUT, тестируемой системы). В данном случае под system понимается тестируемый элемент программного обеспечения. Это может быть как отдельный класс, так и приложение целиком. Или же что-то среднее, например коллекция классов или отдельный сервис. Связанные между собой тесты объединяются в test suite (тестовый набор).
  • Хороший способ упрощения и ускорения тестов — задействование test doubles (дублеров). Test double — это объект, который симулирует поведение зависимости тестируемой системы. Существует два типа дублеров: stubs (заглушки) и mocks (макеты). Заглушка возвращает значение тестируемой системе. Макет используется тестом для проверки, корректно ли система вызывает свои зависимости.
  • Применяйте пирамиду тестов, чтобы определить, где следует приложить усилия при тестировании сервисов. Большинство ваших тестов должны быть unit-tests (модульными), то есть быстрыми, надежными и простыми в написании. Вы должны минимизировать количество end-to-end (сквозных) тестов, поскольку они медленные и нестабильные, а их написание занимает много времени.

Chapter 10: Testing microservices: Part 2

10.2 Developing component tests

Summary

  • Используйте contracts (контракты) (примеры сообщений) для тестирования взаимодействия между сервисами. Пишите тесты, которые проверяют адаптеры сервисов на соответствие contracts, не запуская сами сервисы и их зависимости.
  • Для проверки поведения сервиса через его API пишите component tests. Для их упрощения и ускорения сервисы следует тестировать отдельно друг от друга, задействуя заглушки вместо зависимостей.
  • End-to-end tests (сквозные тесты) медлительны и ненадежны, а поэтому отнимают много времени. Чтобы свести их количество к минимуму, пишите их на основе user journey (пользовательских путешествий). User journey симулирует перемещения пользователя по приложению и проверяет высокоуровневое поведение довольно крупных аспектов функциональности. Чем меньше тестов, тем меньше накладных расходов (например, на их подготовку), что ускоряет тестирование.

Chapter 11: Developing production-ready services

Сервис можно считать готовым к развертыванию только в том случае, если ему присущи три критически важные качественные характеристики:

  • безопасность
  • конфигурируемость
  • наблюдаемость

11.1 Developing secure services

Разработчик в первую очередь несет ответственность за то, как реализованы четыре аспекта безопасности.

  • Authentication (Аутентификация)
    Проверяет подлинность программы или человека (субъекта безопасности), которые пытаются получить доступ к приложению. Приложение обычно проверяет учетные данные субъекта, такие как ID и пароль или API-ключ и секретный токен.
  • Authorization (Авторизация)
    Проверяет, позволено ли субъекту выполнять запрошенную опе рацию с заданными данными. Приложения часто применяют безопасность на основе ролей в сочетании со списками управления доступом (Access Control List, ACL**). Каждый пользователь получает одну или несколько ролей, которые дают им право вызывать определенные операции. Списки ACL разрешают пользователям или ролям выполнять операции с определенным бизнес-объектом или агрегатом.
  • Auditing (Аудит)
    Отслеживает операции, выполняемые субъектом, чтобы обнаруживать проблемы с безопасностью, помогать службе поддержки и обеспечивать соблюдение нормативно-правовых норм.
  • Secure interprocess communication (Безопасное межпроцессное взаимодействие)
    В идеале любое взаимодействие внутри сервисов и за их пределами должно производиться поверх TLS (Transport Layer Security — протокол защиты транспортного уровня). Для межпроцессного взаимодействия может даже понадобиться аутентификация.

11.1.2 Implementing security in a microservice architecture

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

  • In-memory security context (Контекст безопасности в оперативной памяти)
    Хранение контекста безопасно сти в оперативной памяти, например внутри потока, для раздачи данных о пользователе. Сервисы не способны разделять память, поэтому они не могут исполь зовать подобного рода механизм. Микросервисная архитектура требует другого подхода к передаче пользовательских данных от одного сервиса к другому.
  • Centralized session (Централизованный сеанс)
    Поскольку контекст безопасности нельзя размещать в памяти, это ограничение распространяется и на сеанс. Теоретически разные сервисы могли бы получать доступ к сеансу, который хранится в базе данных, но это нарушило бы принцип слабой связанности. Для микросервисной архитекту** ры нужен другой механизм сеансов.
HANDLING AUTHENTICATION IN THE API GATEWAY
  • Pattern: Access token
    The API gateway passes a token containing information about the user, such as their identity and their roles, to the services that it invokes.
USING JWTS TO PASS USER IDENTITY AND ROLES

Существует два типа токенов, из которых вы можете выбрать:

  • opaque (непрозрачный) token (example: UUID)
  • transparent (прозрачный) token (example: JWT)
USING OAUTH 2.0 IN A MICROSERVICE ARCHITECTURE

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

Стандарт OAuth 2.0 основан на следующих концепциях:

  • Authorization Server (Сервер авторизации)
    предоставляет API для аутентификации пользователей и получения токенов доступа и обновления. Spring OAuth — хороший пример фреймворка для построения сервера авторизации OAuth 2.0.
  • Access Token (Токен доступа)
    токен, дающий доступ к серверу ресурсов. Его формат зависит от реализации. Но некоторые фреймворки, например Spring OAuth, используют для этого токены JWT.
  • Refresh Token (Токен обновления)
    долгоживущий токен с возможностью отзыва, с помощью которого клиент получает токен доступа.
  • Resource Server (Сервер ресурсов)
    задействует токен доступа для авторизации. В микросервисной архитектуре серверами ресурсов выступают сами сервисы.
  • Client (Клиент)
    хочет получить доступ к серверу ресурсов. В микросервисной архитектуре роль клиента OAuth 2.0 играет API-шлюз.

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

  • API gateway ответственен за аутентификацию клиентов.
  • API gateway и service задействуют прозрачные токены, такие как JWT, для обмена информацией о субъекте безопасности.
  • Service использует токен для получения учетных данных и ролей субъекта.

11.2 Designing configurable services

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

  • Push model (Пассивная модель)
    Инфраструктура развертывания передает экземпляру сервиса конфигурационные свойства, используя, к примеру, переменные системного окружения или конфигурационный файл.
  • Pull model (Активная модель)
    Экземпляр сервиса сам берет конфигурационные свойства с сервера конфигурации.

11.2.2 Using pull-based externalized configuration

Использование сервера конфигурации дает несколько преимуществ.

  • Centralized configuration (Централизованная конфигурация)
    Все конфигурационные свойства хранятся в одном месте, благодаря чему ими легче управлять. Кроме того, чтобы не допустить дублирования свойств, некоторые реализации позволяют определять значения по умолчанию, которые переопределяются на уровне отдельных сервисов.
  • Transparent decryption of sensitive data (Прозрачная расшифровка конфиденциальных данных)
    Информацию особого рода, такую как учетные данные для доступа к БД, рекомендуется шифровать. Но при этом может возникнуть проблема: экземпляру сервиса обычно приходится ее расшифровывать. Это означает, что ему нужны ключи шифрования. Некоторые серверы конфигурации автоматически расшифровывают свойства перед тем, как вернуть их сервису.
  • Dynamic reconfiguration (Динамическое изменение конфигурации)
    Сервис потенциально может отслеживать обновления своих свойств, например периодически проверяя их, и изменять свою конфигурацию.

Основной недостаток использования сервера конфигурации — то, что это еще один инфраструктурный компонент, который нужно настраивать и обслуживать (разве что он предоставляется самой инфраструктурой). К счастью, открытые фреймворки, такие как Spring Cloud Config, упрощают работу с конфигурационным сервером.

11.3 Designing observable services

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

  • Health check API (API проверки работоспособности) — предоставляет конечную точку, которая возвращает данные о работоспособности сервиса.
  • Log aggregation (Агрегация журналов) — ведет журналы активности сервисов и сохраняет их на центральном журнальном сервере с поддержкой поиска и оповещений.
  • Distributed tracing (Распределенная трассировка) — назначает каждому внешнему запросу уникальный идентификатор и отслеживает запросы по мере их перемещения между сервисами.
  • Exception tracking (Отслеживание исключений) — за исключениями следит отдельный сервис, который избавляется от дубликатов, оповещает разработчиков и отслеживает обработку каждого исключения.
  • Application metrics (Метрики приложения) — сервисы собирают метрики, такие как счетчики и оценочные показатели, и делают их доступными серверу метрик.
  • Audit logging (Ведение журнала аудита) — ведет журнал действий пользователей.

11.3.1 Using the Health check API pattern

  • Pattern: Health check API
    A service exposes a health check API endpoint, such as GET /health, which returns the health of the service.

11.3.2 Applying the Log aggregation pattern

  • Pattern: Log aggregation
    Aggregate the logs of all services in a centralized database that supports searching and alerting.
THE LOG AGGREGATION INFRASTRUCTURE

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

  • Elasticsearch — база данных типа NoSQL, ориентированная на текстовый поиск. Используется в качестве журнального сервера;
  • Logstash — конвейер, который агрегирует журналы сервисов и записывает их в Elasticsearch;
  • Kibana — инструмент визуализации для Elasticsearch.

В качестве примеров других открытых решений для работы с журналами можно привести Fluentd и Apache Flume. В число журнальных сервисов можно включить как облачные сервисы, такие как AWS CloudWatch Logs, так и многочисленные коммерческие продукты. Агрегация журналов служит полезным инструментом отладки в микросервисной архитектуре.

11.3.3 Using the Distributed tracing pattern

  • Pattern: Distributed tracing
    Assign each external request a unique ID and record how it flows through the system from one service to the next in a centralized server that provides visualization and analysis.

11.3.4 Applying the Application metrics pattern

DELIVERING METRICS TO THE METRICS SERVICE

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

  • push model (пассивным)
    В пассивной модели экземпляр сервиса сам шлет показатели серверу, вызывая его API. Эта модель реализована, к примеру, в AWS Cloudwatch.
  • pull model (активным)
    В активной модели сервер показателей или его агент, запущенный локально, обращается к API сервиса, чтобы извлечь собранную им информацию. Этот подход применяется в Prometheus — популярной системе для мониторинга и рассылки оповещений с открытым исходным кодом.

11.3.5 Using the Exception tracking pattern

  • Pattern: Exception tracking
    Services report exceptions to a central service that de-duplicates exceptions, generates alerts, and manages the resolution of exceptions.

  • Exception tracking services There are several exception tracking services. Some, such as Honeybadger, are purely cloud-based. Others, such as Sentry.io, also have an open source version that you can deploy on your own infrastructure. These services receive exceptions from your application and generate alerts. They provide a console for viewing exceptions and managing their resolution. An exception tracking service typically provides client libraries in a variety of languages.

11.3.6 Applying the Audit logging pattern

  • Pattern: Audit logging
    Record user actions in a database in order to help customer support, ensure compliance, and detect suspicious behavior.

There are a few different ways to implement audit logging:

  • Add audit logging code to the business logic.
  • Use aspect-oriented programming (AOP).
  • Use event sourcing.

11.4 Developing services using the Microservice chassis pattern

  • Pattern: Microservice chassis
    Build services on a framework or collection of frameworks that handle cross-cutting concerns, such as exception tracking, logging, health checks, externalized configuration, and distributed tracing.

11.4.2 From microservice chassis to service mesh

  • Pattern: Service mesh
    Route all network traffic in and out of services through a networking layer that implements various concerns, including circuit breakers, distributed tracing, service discovery, load balancing, and rule-based traffic routing.

  • The current state of service mesh implementations
    There are various service mesh implementations, including the following:

As of the time of writing, Linkerd is the most mature, with Istio and Conduit still under active development. For more information about this exciting new technology, take a look at each product’s documentation.

Summary

  • Важно, чтобы сервис соответствовал функциональным требованиям, но вместе с тем он должен быть secure, configurable, и observable (безопасным, конфигурируемым и наблюдаемым).
  • В том, что касается безопасности, микросервисная и монолитная архитектуры имеют много общего. Но есть и некоторые различия, например передача учетных данных между API gateway (API-шлюзом) и сервисами или выбор компонента, ответственного за authentication (аутентификацию) и authorization (авторизацию). authentication клиентов обычно занимается API gateway. В каждый запрос к сервису он добавляет transparent (прозрачный) токен, такой как JWT. Он содержит учетные данные субъекта и его роли. Сервис использует эту информацию для авторизации доступа к ресурсам. Хорошей основой для безопасности в микросервисной архитектуре может быть стандарт OAuth 2.0.
  • Сервис обычно общается с одним или несколькими внешними компонентами, такими как брокер сообщений или база данных. Их сетевое размещение и учетные данные часто зависят от среды, в которой запущен сервис. Вы должны применить шаблон Externalized configuration (вынесения конфигурации вовне() и реализовать механизм, который предоставляет сервису конфигурационные свойства на этапе выполнения. Во многих случаях инфраструктура развертывания делает эти свойства доступными через переменные системного окружения или конфигурационный файл во время создания экземпляра сервиса. В качестве альтернативы экземпляр сервиса может сам извлекать свою конфигурацию из configuration properties server (специального сервера).
  • Системные администраторы и разработчики разделяют ответственность за реализацию шаблонов observability (наблюдаемости). Администраторы отвечают за соответствующую инфраструктуру, такую как handle log aggregation, metrics, exception tracking, и distributed tracing (серверы для агрегации журналов, сбора показателей, отслеживания исключений и распределенной трассировки). Разработчики должны сделать так, чтобы их сервисы были observable (наблюдаемыми). Сервис должен иметь health check API endpoints (конечные точки для проверки работоспособности), генерировать журнальные записи, собирать и «выставлять наружу» metrics (показатели), отправлять отчеты серверу для отслеживания исключений и поддерживать distributed tracing (распределенную трассировку).
  • Чтобы упростить и ускорить разработку, вы должны писать свои сервисы поверх microservices chassis микросервисного шасси. microservices chassis — это фреймворк или набор фреймворков для реализации различных общих функций, включая те, что были описаны в этой главе. Хотя, скорее всего, со временем многие сетевые обязанности перейдут от microservices chassis к service mesh (сети сервисов) — инфраструктурной прослойке, через которую проходит весь сетевой трафик сервисов.

Chapter 12: Deploying microservices

Deployment — это сочетание двух взаимосвязанных концепций — process and architecture (процесса и архитектуры). Deployment process (процесс развертывания) заключается в доставке кода в промышленную среду и состоит из этапов, которые должны выполнить люди — разработчики или системные администраторы. Deployment architecture (архитектура развертывания) определяет структуру среды, в который этот код будет выполняться.

Промышленная среда должна поддерживать четыре ключевые возможности:

  • Service management interface (Интерфейс управления сервисами) позволяет разработчикам создавать, обновлять и конфигурировать сервисы. В идеале это интерфейс REST API, с которым работают консольные и графические инструменты развертывания.
  • Runtime service management (Управление запущенными сервисами) пытается следить за тем, чтобы в промышленной среде всегда выполнялось желаемое количество экземпляров сервиса. Если экземпляр сервиса вышел из строя или по какой-то причине больше не может обслуживать запросы, промышленная среда должна его перезапустить. Если отказал целый сервер, все его сервисы должны быть перезапущены на другом компьютере.
  • Monitoring (Мониторинг) предоставляет разработчикам сведения о работе их сервисов, включая журнальные файлы и показатели. В случае возникновения проблем промышленная среда должна оповестить разработчиков. Мониторинг (или наблюдаемость) описывается в главе 11.
  • Request routing (Маршрутизация) направляет запросы от пользователей к сервисам.

Четыре основных варианта развертывания:

  • Deploying services as language-specific packages (Развертывание сервиса в виде пакетов для определенных языков), таких как JAR-или WAR-файлы в Java. Этот подход заслуживает внимания в качестве демонстрации недостатков, из-за которых я рекомендую использовать один из других вариантов.
  • Deploying services as virtual machines (Применение виртуальных машин) упрощает процесс развертывания, так как сервисы упаковываются в виде образов для ВМ, которые инкапсулируют их технологический стек.
  • Deploying services as containers (Развертывание сервисов в виде контейнеров), более легковесных по сравнению с виртуальными машинами. Я покажу, как развернуть сервис Restaurant из приложения FTGO с помощью популярного фреймворка для оркестрации Docker под названием Kubernetes.
  • Deploying services using serverless deployment (Бессерверное развертывание) является даже более современным, чем контейнеры. Мы посмотрим, как развернуть сервис Restaurant с помощью популярной бессерверной платформы AWS Lambda.

12.1 Deploying services using the Language-specific packaging format pattern

12.1.1 Benefits of the Service as a language-specific package pattern

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

12.1.2 Drawbacks of the Service as a language-specific package pattern

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

12.2 Deploying services using the Service as a virtual machine pattern

  • Pattern: Deploy a service as a VM
    Deploy services packaged as VM images into production. Each service instance is a VM.

  • About Elastic Beanstalk
    Elastic Beanstalk, which is provided by AWS, is an easy way to deploy your services using VMs. You upload your code, such as a WAR file, and Elastic Beanstalk deploys it as one or more load-balanced and managed EC2 instances. Elastic Beanstalk is perhaps not quite as fashionable as, say, Kubernetes, but it’s an easy way to deploy a microservices-based application on EC2. Interestingly, Elastic Beanstalk combines elements of the three deployment patterns described in this chapter. It supports several packaging formats for several languages, including Java, Ruby, and .NET. It deploys the application as VMs, but rather than building an AMI, it uses a base image that installs the application on startup. Elastic Beanstalk can also deploy Docker containers. Each EC2 instance runs a collection of one or more containers. Unlike a Docker orchestration framework, covered later in the chapter, the unit of scaling is the EC2 instance rather than a container.

12.2.1 The benefits of deploying services as VMs

Развертывание сервисов в виде ВМ имеет ряд преимуществ:

  • Образ ВМ инкапсулирует стек технологий.
  • Экземпляры сервиса изолированы.
  • Используется зрелая облачная инфраструктура.

12.2.2 The drawbacks of deploying services as VMs

Развертывание сервисов в виде ВМ имеет и недостатки:

  • Менее эффективно используются ресурсы.
  • Развертывание протекает довольно медленно.
  • Требуются дополнительные расходы на системное администрирование.

12.3 Deploying services using the Service as a container pattern

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

12.3.1 Deploying services using Docker

Чтобы развернуть сервис в виде container, его необходимо упаковать в container image (образ). Container image (образ контейнера) — это слепок файловой системы, состоящий из приложения и любого ПО, которое требуется для его работы. Часто для этого берут полноценную корневую файловую систему Linux, хотя встречаются и более легковесные образы.

BUILDING A DOCKER IMAGE

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

docker build -t ftgo-restaurant-service .

У команды docker build есть два аргумента: ключ -t, определяющий имя образа, и . — то, что в терминологии Docker называется context (контекстом). Context в данном случае является текущим каталогом и состоит из Dockerfile и файлов для сборки образа. Команда docker build передает контекст демону Docker, который выполняет сборку.

PUSHING A DOCKER IMAGE TO A REGISTRY

Последний шаг в процессе сборки — загрузка свежесозданного Docker container image в так называемый registry (реестр). Docker registry — это эквивалент репозитория Maven для библиотек Java или реестра NPM для пакетов NodeJS. Docker Hub служит примером публичного реестра Docker и эквивалентом Maven Central и NpmJS.org.

12.3.2 Benefits of deploying services as containers

  • Инкапсуляция стека технологий, благодаря которой API для управления сервисом превращается в API контейнера.
  • Экземпляры сервиса изолированы.
  • Ресурсы экземпляров сервиса ограничены.

12.3.3 Drawbacks of deploying services as containers

Существенный недостаток контейнеров состоит в том, что вы должны постоянно заниматься управлением образами контейнеров и обновлением операционной системы вместе со средой выполнения. Кроме того, если вы не применяете облачные контейнерные решения наподобие Google Container Engine или AWS ECS, на вас ложится администрирование контейнерной инфраструктуры и, возможно, инфраструктуры виртуальных машин, поверх которой она работает.

12.4 Deploying the FTGO application with Kubernetes

12.4.1 Overview of Kubernetes

Kubernetes — это фреймворк оркестрации Docker, который обращается с набором серверов под управлением Docker, как с пулом ресурсов. Вы лишь указываете, сколько экземпляров сервиса нужно запустить, а фреймворк делает все остальное.

Фреймворк оркестрации Docker, такой как Kubernetes, имеет три основные функции.

  • Resource management (Управление ресурсами)
    Обращается с кластером серверов как с пулом процессоров, памяти и томов хранилища, объединяя их в единый абстрактный компьютер.
  • Scheduling (Планирование)
    Выбирает сервер для выполнения вашего контейнера. По умолчанию планировщик учитывает требования к ресурсам, предъявляемые контейнером, и свободные ресурсы на каждом узле. Он также поддерживает принципы принадлежности (affinity) и непринадлежности (anti-affinity), которые позволяют размещать контейнеры на одном и том же или на разных узлах.
  • Service management (Управление сервисом)
    Реализует концепцию именованных и версионных сервисов, которые накладываются непосредственно на сервисы в микросервисной архитектуре. Фреймворк оркестрации постоянно поддерживает желаемое количество работоспособных экземпляров и распределяет между ними запросы. Он также выполняет плавающие обновления сервиса и позволяет откатиться к предыдущей версии.
KUBERNETES ARCHITECTURE

Kubernetes выполняется на кластере компьютеров. Каждый компьютер в этом кластере является либо master (ведущим), либо node (служебным/рабочим узлом). Обычно ведущих узлов очень мало (иногда один), а рабочих — много. Ведущий узел отвечает за управление кластером. Рабочий узел выполняет одну или несколько pod. Pod (pod-оболочка) — это единица развертывания в Kubernetes, состоящая из набора контейнеров.

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

  • API server (API-сервер) — интерфейс REST API для развертывания и администрирования сервисов. Используется, к примеру, утилитой командной строки kubectl;
  • Etcd — база данных NoSQL типа «ключ — значение», хранящая данные кластера;
  • Scheduler (планировщик() — выбирает узел для запуска pod-оболочки;
  • Controller manager (диспетчер контроллеров) — запускает контроллеры, которые следят за тем, чтобы кластер находился в нужном состоянии. Например, replication controller (контроллер репликации) (это лишь один из типов контроллеров) обеспечивает выполнение желаемого количества экземпляров сервиса, отвечая за их запуск и удаление.

Рабочий узел тоже выполняет несколько компонентов, включая следующие:

  • Kubelet — создает pod-оболочки и управляет их выполнением на рабочем узле;
  • Kube-proxy — управляет сетевыми функциями, включая балансирование нагрузки между pod-оболочками;
  • Pods — сервисы приложения.
KEY KUBERNETES CONCEPTS
  • Pod (Pod-оболочка)
    Это базовая единица развертывания в Kubernetes. Она состоит из одного или нескольких контейнеров с общими IP-адресом и storage volumes (томами хранения). Pod-оболочка экземпляра сервиса часто содержит лишь один контейнер, который, к примеру, выполняет JVM. Но в некоторых случаях в ее состав может входить несколько дополнительных контейнеров, реализующих вспомогательные функции. Например, у сервера NGINX может быть дополнительный контейнер, который периодически выполняет команду git pull, загружая последнюю версию веб-сайта. Pod-оболочка является временной, поскольку ее контейнер или узел, на котором она выполняется, могут выйти из строя.
  • Deployment (Развертывание)
    Декларативная спецификация pod-оболочки. Это controller (контроллер), который постоянно обеспечивает нужное количество запущенных экземпляров сервиса (pod-оболочки). Для поддержки версионирования он использует rolling upgrades and rollbacks (плавающие обновления и откаты). В подразделе 12.4.2 вы увидите, что в терминологии Kubernetes каждый сервис в микросервисной архитектуре является развертыванием.
  • Service (Сервис)
    Предоставляет клиентам сервиса статический/стабильный сетевой адрес. Это разновидность механизма infrastructure-provided service discovery (обнаружения сервисов на уровне инфраструктуры), описанного в главе 3. Сервис имеет IP-адрес и DNS-имя, которое на него указывает, TCP- и UDP-трафик распределяются между несколькими pod-оболочками, если их больше одной. IP-адрес и DNS-имя доступны только внутри Kubernetes.
  • ConfigMap
    Именованный набор пар «имя — значение», который описывает externalized configuration (внешнюю конфигурацию) для одного или нескольких сервисов приложения (краткий обзор вынесения конфигурации вовне сделан в главе 11). Определение контейнера pod-оболочки может ссылаться на ConfigMap для перечисления переменных окружения. Оно может использовать ConfigMap также для создания конфигурационных файлов внутри контейнера. Вы можете хранить конфиденциальную информацию, например пароли, в варианте ConfigMap под названием Secret.

12.4.4 Zero-downtime deployments

Развертывания в Kubernetes позволяют доставлять сервисы без простоя. Но что если ошибка проявится уже после того, как pod-оболочка запустится и начнет принимать промышленный трафик? В этом случае Kubernetes продолжит выкатывать новые версии, что будет отражаться на все большем числе пользователей. И хотя ваша система мониторинга, будем надеяться, обнаружит проблему и быстро откатит развертывание, некоторые пользователи все же пострадают. Чтобы этого избежать и сделать новую версию сервиса более надежной, необходимо отделить deploying (развертывание) (запуск сервиса в промышленной среде) от releasing (выпуска сервиса), в результате которого тот может начать обрабатывать промышленный трафик.

12.4.5 Using a service mesh to separate deployment from release

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

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

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

  • deploying (развертывание) — выполнение в промышленной среде;
  • releasing (выпуск сервиса() — открытие доступа к нему конечным пользователям.

Развертывание сервиса в промышленной среде состоит из следующих шагов.

  1. Развертывание новой версии в промышленной среде без перенаправления к ней запросов конечных пользователей.
  2. Тестирование ее в реальных условиях.
  3. Выпуск сервиса для небольшого количества пользователей.
  4. Постепенный выпуск сервиса для все более широкой аудитории, пока он не станет обрабатывать весь промышленный трафик.
  5. Если на каком-либо этапе появится проблема, можно откатиться к старой версии. Если же вы уверены в том, что все работает как следует, старую версию можно удалить.

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

OVERVIEW OF THE ISTIO SERVICE MESH

На официальном веб-сайте Istio описывается как «открытая платформа для объединения, администрирования и защиты микросервисов». Это сетевой слой, через который проходит весь трафик ваших сервисов. Istion - это одна из реализаций service mesh. У Istio богатый набор возможностей, которые делятся на четыре основные категории:

  • Traffic management (управление трафиком) — включает в себя service discovery, load balancing, routing rules, and circuit breakers (обнаружение сервисов, балансирование нагрузки, правила маршрутизации и предохранители)
  • Security (безопасность) — безопасное межсервисное взаимодействие на основе протокола защиты транспортного уровня (Transport Layer Security, TLS);
  • Telemetry (телеметрия) — собирает metrics (сведения) о сетевом трафике и реализует distributed tracing (распределенную трассировку);
  • Policy enforcement (соблюдение политики) — обеспечивает соблюдение квот и лимитов на частоту запросов.

  • Pattern: Sidecar
    Implement cross-cutting concerns in a sidecar process or container that runs alongside the service instance.

12.5 Deploying services using the Serverless deployment pattern

Language-specific packaging (Пакеты, рассчитанные на определенные языки), Service as a VM (виртуальные машины), и Service as a container (контейнеры) довольно сильно различаются, но у всех них есть и общее. Во-первых, все эти шаблоны развертывания должны заранее выделять определенные вычислительные ресурсы — физические серверы, виртуальные машины или контейнеры. Некоторые платформы развертывания поддерживают автомасштабирование, динамически регулируя количество ВМ или контейнеров в зависимости от нагрузки. Тем не менее вам постоянно нужно платить за какие-то ресурсы, даже если они простаивают.

Еще одной общей чертой является то, что ответственность за системное администрирование ложится на вас. С каким бы сервером вы ни работали, его операционную систему нужно обновлять. Если это физические серверы, их необходимо распределять по стойкам. Вы также отвечаете за обслуживание среды выполнения языка программирования. Это то, что компания Amazon называет undifferentiated heavy lifting (неразделяемой рутинной работой). С первых дней возникновения компьютерной индустрии системное администрирование было тем, без чего нельзя обойтись. Но, как оказывается, у этого правила есть исключение — serverless (бессерверные платформы).

12.5.1 Overview of serverless deployment with AWS Lambda

  • Serverless deployment technologies
    The main public clouds all provide a serverless deployment option, although AWS Lambda is the most advanced. Google Cloud has Google Cloud functions, which as of the time writing is in beta. Microsoft Azure has Azure functions. There are also open source serverless frameworks, such as Apache Openwhisk and Fission for Kubernetes, that you can run on your own infrastructure. But I’m not entirely convinced of their value. You need to manage the infrastructure that runs the serverless framework—which doesn't exactly sound like serverless. Moreover, as you’ll see later in this section, serverless provides a constrained programming model in exchange for minimal system administration. If you need to manage infrastructure, then you have the constraints without the benefit.

lambda function (Лямбда-функция) — это сервис, лишенный состояния.

12.5.3 Invoking lambda functions

There are four ways to invoke a lambda function:

  • HTTP requests
  • Events generated by AWS services
  • Scheduled invocations
  • Directly using an API call

12.5.4 Benefits of using lambda functions

Развертывание сервисов в виде лямбда-функций имеет несколько преимуществ.

  • Integrated with many AWS services (Интеграция со многими сервисами AWS)
    Вы можете с невероятной легкостью писать лямбда-функции, которые потребляют события, сгенерированные такими сервисами AWS, как DynamoDB и Kinesis, и обрабатывают HTTP-запросы через API-Gateway AWS.
  • Eliminates many system administration tasks (Избавление от многих задач системного администрирования)
    Вы больше не отвечаете за низкоуровневое системное администрирование. У вас нет операционных систем и сред выполнения, которые нужно обновлять. Благодаря этому вы можете сосредоточиться на развертывании своего приложения.
  • Elasticity (Эластичность)
    AWS Lambda запускает экземпляры вашего приложения в количестве, которого достаточно, чтобы справиться с нагрузкой. Вам не нужно предсказывать необходимую пропускную способность или волноваться о недостаточном или чрезмерном выделении ВМ или контейнеров.
  • Usage-based pricing (Тарифы, основанные на потреблении)
    В отличие от типичных облаков IaaS, в которых вы платите за каждую минуту или час работы ваших ВМ или контейнеров (даже когда они простаивают), AWS Lambda берет плату только за ресурсы, потраченные на обработку каждого запроса.

12.5.5 Drawbacks of using lambda functions

Как видите, AWS Lambda — чрезвычайно удобный способ развертывания сервисов. Но эта технология не лишена некоторых существенных недостатков и ограничений.

  • Long-tail latency (Периодически возникает высокая латентность)
    Поскольку в AWS Lambda ваш код выполняется динамически, некоторые запросы будут демонстрировать высокую латентность. Это связано с тем, что AWS нужно время на создание экземпляра приложения и его запуск. Особенно остра эта проблема в сервисах, написанных на Java, поскольку они обычно запускаются как минимум несколько секунд. Например, лямбда-функция, представленная в следующем разделе, стартует довольно медленно. В связи с этим AWS Lambda может оказаться не лучшим выбором для сервисов, требующих низкой латентности.
  • Limited event/request-based programming model (Ограниченная модель программирования, основанная на событиях/запросах)
    Технология AWS Lambda не предназначена для развертывания длительное время работающих сервисов, которые, к примеру, принимают сообщения от стороннего брокера.

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

Summary

  • Вы должны выбрать наиболее легковесный шаблон развертывания, который удовлетворяет требованиям вашего приложения. Варианты следует рассматривать в таком порядке: serverless, containers, virtual machines, and language-specific packages (бессерверные платформы, контейнеры, виртуальные машины и пакеты, рассчитанные на определенные языки).
  • Из-за периодически возникающей высокой латентности и программной модели на основе событий/запросов serverless (бессерверное развертывание) подходит не для всех сервисов. Но в подходящих сценариях оно становится очень достойным решением, которое позволяет забыть об администрировании операционных систем и сред выполнения. Оно также поддерживает эластичное выделение ресурсов и тарификацию отдельных запросов.
  • Контейнеры Docker — это легковесная технология виртуализации на уровне ОС. По сравнению с serverless они обеспечивают большую гибкость и более предсказуемы в отношении латентности. Для работы с ними лучше всего использовать фреймворк оркестрации Docker наподобие Kubernetes, который управляет контейнерами в кластере серверов. Недостаток контейнеров заключается в том, что вам придется заниматься администрированием операционных систем и сред выполнения, а также, вероятно, фреймворка оркестрации Docker и виртуальных машин, в которых он выполняется.
  • Третий вариант — развертывание сервиса в виде виртуальной машины. С одной стороны, это тяжеловесное решение, поэтому по сравнению со вторым вариантом оно будет более медленным и, скорее всего, ресурсоемким. С другой стороны, современные облака, такие как Amazon ЕС2, высокоавтоматизированы и предоставляют богатый набор возможностей. Поэтому иногда проще развернуть небольшое приложение с помощью виртуальной машины, чем настраивать фреймворк оркестрации Docker.
  • Обычно стоит избегать развертывания сервисов в виде пакетов, предназначенных для конкретных языков. Исключение представляют собой случаи, когда вы имеете дело с небольшим количеством сервисов. Например, как говорится в главе 13, при переходе на микросервисы вы, скорее всего, будете использовать тот же механизм, что и в монолитной версии приложения (и чаще всего это данный вариант). О подготовке развитой инфраструктуры развертывания следует задуматься только после того, как у вас будут готовы несколько сервисов.
  • Одно из многих преимуществ service mesh (сети сервисов), т.е. сетевого слоя, который пропускает через себя весь входящий и исходящий трафик сервиса) — возможность протестировать свой код в промышленных условиях, прежде чем направлять к нему реальный трафик. Разделение deployment (развертывания) и release (выпуска сервисов) делает «выкатывание» их новых версий более надежным.

Chapter 13: Refactoring to microservices

13.1 Overview of refactoring to microservices

13.1.1 Why refactor a monolith?

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

  • Slow delivery (Медленная доставка)
    В приложении сложно разобраться, затрудняются его обслуживание и тестирование, что понижает продуктивность разработчиков. В итоге организация не может эффективно функционировать и рискует уступить конкурентам.
  • Buggy software releases (Обновления с ошибками)
    Из-за плохой тестируемости новые выпуски ПО часто содержат ошибки. Это может привести к потере недовольных клиентов и снижению прибыли.
  • Poor scalability (Плохая масштабируемость)
    Трудность масштабирования монолитного приложения связана с тем, что оно сочетает в одном исполняемом компоненте модули с кардинально разными требованиями к ресурсам. Это означает, что с какого-то момента масштабирование приложения становится либо непомерно дорогим, либо невозможным. В итоге оно не в состоянии удовлетворить текущие или запланированные потребности бизнеса.

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

13.1.2 Strangling the monolith

Преобразование монолитного приложения в микросервисы — это разновидность application modernization (модернизации ПО). Application modernization — это процесс перевода устаревшего кода на новые архитектуру и стек технологий. Разработчики десятилетиями модернизируют свои приложения. Опыт, накопленный за это время, можно применить для миграции на микросервисную архитектуру. Самый важный урок состоит в том, что не стоит делать big bang rewrite (переписывание проект с нуля).

MINIMIZE CHANGES TO THE MONOLITH

Начать с чистого листа и оставить старую кодовую базу в прошлом — звучит заманчиво. Но это чрезвычайно рискованный подход, который, скорее всего, закончится неудачей. На дублирование существующей функциональности уйдут месяцы, возможно, годы, а бизнес-нужды требуют реализации новых возможностей уже сегодня! К тому же вам все равно придется заниматься разработкой старого приложения, что будет отвлекать вас от переписывания, из-за этого сроки завершения работы будут постоянно сдвигаться. Что еще хуже, вы вполне можете потратить время на реализацию функций, которые больше не нужны. Мартину Фаулеру приписывают такое высказывание: the only thing a Big Bang rewrite guarantees is a Big Bang! (Переписывание с нуля гарантирует лишь одно — ноль!) .

DEMONSTRATE VALUE EARLY AND OFTEN
TECHNICAL DEPLOYMENT INFRASTRUCTURE: YOU DON’T NEED ALL OF IT YET

13.2 Strategies for refactoring a monolith to microservices

Существует три основные стратегии для удушения монолита и постепенной замены его микросервисами.

  1. Реализация новых возможностей в виде сервисов.
  2. Разделение уровня представления и внутренних компонентов.
  3. Разбиение монолита путем оформления функциональности в виде сервисов.

13.3 Designing how the service and the monolith collaborate

  • Pattern: Anti-corruption layer
    A software layer that translates between two different domain models in order to prevent concepts from one model polluting another.

Summary

  • Прежде чем мигрировать на микросервисы, следует убедиться в том, что проблемы с доставкой ПО вызваны недостатками монолитной архитектуры. Иногда доставку можно ускорить за счет внесения корректив в процесс разработки.
  • Переходить на микросервисы следует постепенно, разрабатывая strangler application (удушающее приложение). Оно состоит из микросервисов, которые вы создаете вокруг существующего монолита. Вы должны как можно раньше и чаще демонстрировать преимущества перехода, чтобы заручиться поддержкой руководства.
  • Отличный способ внедрения микросервисов в вашу архитектуру — реализация новых возможностей в виде сервисов. Это позволяет быстро и просто добавлять новые функции с помощью современных технологий и эффективного процесса разработки. Вы сможете быстро продемонстрировать выгоду от миграции на микросервисы.
  • Чтобы разбить монолит, можно отделить уровень представления от внутренних компонентов, в результате чего получатся два монолита поменьше. Это не очень существенное улучшение, но оно позволит развертывать каждый монолит по отдельности. Благодаря этому, например, отдельной команде будет проще развивать пользовательский интерфейс, не затрагивая внутреннюю часть приложения.
  • Основной способ разбиения монолита заключается в постепенном переносе его функций в сервисы. В первую очередь стоит сосредоточиться на наиболее полезных возможностях. Например, вы сможете ускорить процесс разработки, если реализуете в виде сервиса активно развивающийся код.
  • Новым сервисам почти всегда приходится взаимодействовать с монолитом. Например, сервису нужно часто обращаться к данным монолита и его функциям. Монолиту тоже иногда необходим доступ к данным и возможностям сервиса. Чтобы организовать такое взаимодействие, следует создать интеграционный слой, который состоит из входящих и исходящих адаптеров, размещенных в монолите.
  • Чтобы доменная модель монолита не засоряла доменную модель сервиса, интеграционный код должен использовать предохранительный слой, который производит преобразования между этими доменными моделями.
  • Чтобы извлечение сервисов как можно меньше влияло на монолит, в его БД можно реплицировать данные, вынесенные в сервис. Схема монолита остается неизменной, поэтому не нужно вносить существенные правки в его кодовую базу.
  • Разработка сервиса часто требует выполнения sagas (повествования), в котором участвует монолит. Однако реализация компенсируемых транзакций, требующих внесения масштабных изменений в данные кода монолита, может вызвать определенные трудности. Поэтому иногда следует тщательно планировать порядок извлечения сервисов, чтобы компенсируемые транзакции не попали в монолит.
  • При переходе на микросервисную архитектуру нужно одновременно поддерживать два механизма безопасности: тот, что уже реализован в монолитном приложении и обычно основан на сеансе, хранящемся в памяти, и систему токенов, использованную в сервисах. К счастью, мы можем просто модифицировать обработчик входа в систему так, чтобы он генерировал cookie с токеном безопасности, затем API-шлюз будет передавать этот токен сервисам.