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

Виды архитектуры

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

Отчего возникают следующие проблемы:

Отчего возникают следующие проблемы:

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

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

Это частично решает проблемы отказоустойчивости, м

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

Микросервисная архитектура – не новая идея, а разновидность сервис-ориентированной архитектуры. Сервис-ориентированная архитектура предусматривает модульность разработки и слабую связанность компонентов, поэтому получаем изолированную и распределённую систему.

Главный минус – общая шина данных Enterprise Servi

Главный минус – общая шина данных Enterprise Service Bus с огромными спецификациями и сложностями работы с абстракциями и фасадами.

Видео

Базы данных

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

Вот возможные решения проблемы:

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

Представьте другой случай: при регистрации создаётся 3 сущности (пользователь, профиль и счёт), но на полпути что-то падает. Как откатить изменения, если данные распределяются по разным микросервисам? Думайте об этом на этапе проектирования.

Управление командой

«Two Pizza Rule» — это своего рода правило, ограничивающее количество участников в команде разработчиков микросервисов. Согласно этому правилу, количество членов команды одного приложения должно быть настолько маленьким, чтобы их можно было кормить двумя пиццами. Как правило, это число не должно превышать 8. Поскольку микросервис имеет полный стек по своей природе, команда также имеет полный стек по своей природе. Чтобы повысить производительность, нам нужно собрать одну команду, состоящую максимум из 8 человек, со всеми видами опыта, необходимыми для этой услуги.

Насколько большими должны быть микросервисы?

Хотя термин «Микросервис» стал популярным названием для этого архитектурного стиля, само имя приводит к чрезмерному фокусу на размере сервисов и спорам о том, что означает приставка «микро». В наших разговорах с теми, кто занимался разбиением ПО на микросервисы, мы видели разные размеры. Наибольший размер был у компаний, следовавших правилу «Команда двух пицц» (команда, которую можно накормить двумя пиццами), т.е. не более 12 человек (прим. перев.: следуя этому правилу, я в команде должен быть один). В других компаниях мы видели команды, в которых шестеро человек поддерживали шесть сервисов.

Это приводит к вопросу о том, есть ли существенная разница в том, сколько человек должно работать на одном сервисе. На данный момент мы считаем, что оба этих подхода к построению команд (1 сервис на 12 человек и 1 сервис на 1 человека) подходят под описание микросервисной архитектуры, но возможно мы изменим свое мнение в будущем. (со времен статьи появилось множество других статей, развивающих эту тему; наиболее популярным сейчас считается мнение о том, что сервис должен быть настолько большим, чтобы он мог полностью «уместиться в голове разработчика», независимо от количества строк кода).

Микросервисы и SOA

Когда мы разговариваем о микросервисах, обычно возникает вопрос о том, не является ли это обычным Service Oriented Architecture (SOA), который мы видели десять лет назад. В этом вопросе есть здравое зерно, т.к. стиль микросервисов очень похож на то, что продвигают некоторые сторонники SOA. Проблема, тем не менее, в том, что термин SOA имеет сликом много разных значений и, как правило, то, что люди называют «SOA» существенно отличается от стиля, описанного здесь, обычно из-за чрезмерного фокуса на ESB, используемом для интеграции монолитных приложений.

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

Безусловно, многие практики, используемые в микросервисах, пришли из опыта интеграции сервисов в крупных организациях. Шаблон Tolerant Reader — один из примеров. Другой пример — использование простых протоколов — возник как реакция на централизованные стандарты, сложность которых просто захватывает дух.

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

Интеграция

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

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

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

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

Варианты интеграции

При проектировании архитектуры нужно определиться, будет ли сервис работать синхронно или асинхронно. Это накладывает ограничения на доступные варианты интеграции сервисов. Синхронное общение сервисом основывается на механизме «запрос-ответ», асинхронное общение опирается на события.

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

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

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

Общий код

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

Управление версиями

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

Первый вариант — обеспечить функционирование конечных точек двух версий (например 1.5.3 и 2.0.0) в рамках одного сервиса и постепенно мигрировать клиентов на новую версию. В этом случае версию можно сообщать внутри URI (например, /v1/createUser/, /v2/createUser/), или, если используется HTTP, сообщать версию в заголовке запроса.

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

Пользовательские интерфейсы

Чтобы микросервисами пользоваться, нужен пользовательский интерфейс. Есть три пути.

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

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

Третий путь — backend-for-frontend (BFF). Каждая команда разработки интерфейса (например, мобильного приложения) создаёт для себя API-шлюз и поддерживает его.

При двух последних вариантах, следует тщательно контролировать отсутствие логики в API-шлюзе.

Интеграция сторонних систем

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

Микросервисы и облачные услуги

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

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

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

Тестирование

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

Для не-сквозных тестов нужно как-то заменять реальные вызовы других компонентов системы — можно делать имитацию работы (моки), а можно просто возвращать одно и тоже значение (стабы). Есть и другие разновидности заглушек. Крутое решения для генерации таких тестовых дублеров — Mountebank.

Сквозные (e2e) тесты хороши. Их нужно запускать на самом последнем этапе CI, потому что они медленные и не дают понимания, что именно сломалось. Иногда их можно заменить на CDC (контракты определяемые потребителем сервиса).

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

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

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

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

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

Организация работы в команде

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

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

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

А что объединит разнородные по языкам команды? Останется только CI/CD, глобально стандартизованный прокол и подход к документированию.

Конфигурация и настройка системы

В этом разделе мы создадим пример приложения CRUD, которое будет возвращать объект JSON в качестве ответа при каждом вызове нашего сервиса. Мы будем использовать структуру Джерси, чтобы разработать то же самое. Ниже приведены шаги по настройке среды вашей локальной системы.

Разработка приложения CRUD

Шаг 1. Мы будем использовать NetBeans в качестве среды разработки. Загрузите и установите последнюю версию, доступную на официальном веб-сайте NetBeans .

Шаг 2. Откройте свою среду IDE NetBeans. Перейдите в «Файл -> Новый проект». Появится следующий скриншот. Выберите «Maven» в качестве категории и выберите «Project from ArchType» в качестве проекта и нажмите «Далее».

  Это загрузит все необходимые файлы jar для созда

Это загрузит все необходимые файлы jar для создания вашего первого проекта Maven и веб-службы RESTful.

Шаг 3 — При нажатии кнопки «Далее» на предыдущем шаге появляется следующий снимок экрана. Здесь вам нужно будет указать Maven Archetype.

  В окне поиска выполните поиск по запросу «Jersey

В окне поиска выполните поиск по запросу «Jersey-archType-Webapp (2.16)» и установите флажок «Показать более старый».

Шаг 4 — Как только вы выбрали то же самое, вы будете перенаправлены на следующий экран. Выберите предпочитаемую банку из списка и нажмите «Далее», чтобы продолжить.

  Шаг 5 — На этом шаге вам необходимо указат

Шаг 5 — На этом шаге вам необходимо указать название вашего проекта и идентификатор группы, а также сведения о пакете. После предоставления всей этой информации нажмите «Готово», чтобы продолжить.

  Шаг 6 — Вы закончили настройку рабочего пр

Шаг 6 — Вы закончили настройку рабочего пространства. Каталог проекта будет выглядеть следующим образом.

  Проверьте папку «Зависимости», и вы обнаружите,

Проверьте папку «Зависимости», и вы обнаружите, что Maven автоматически загрузил все необходимые файлы JAR для этого проекта.

  Шаг 7 — Ваше рабочее пространство настроен

Шаг 7 — Ваше рабочее пространство настроено, и вы можете начать с кодирования. Создайте четыре класса и пакеты, как указано на следующем снимке экрана. Вы можете обнаружить, что MyResource.java уже создан Maven, поскольку Maven достаточно умен, чтобы определить, что вы собираетесь создать свой собственный веб-сервис.

  Шаг 8. После выполнения вышеуказанного шага мы с

Шаг 8. После выполнения вышеуказанного шага мы создадим наш класс POJO, который будет UserProfile.java, следующим образом.

Шаг 9 — Теперь мы создадим наш класс базы данных. Поскольку это часть учебного материала, мы не будем использовать БД в качестве нашей базы данных. Мы будем использовать встроенную память Java, чтобы работать как наша временная память. Как вы можете видеть в следующем наборе кода, мы будем использовать MAP в качестве нашей базы данных. Все операции веб-службы, которые мы выполняем, мы будем работать с этой MAP, определенной в классе.

Шаг 10 — Теперь давайте построим наш класс обслуживания. Затем скопируйте и вставьте следующий код в класс «ProfileService.java». Это класс, в котором мы будем объявлять все методы наших веб-сервисов, которые будут доступны для внешнего мира. Нам нужно создать одну ссылку на наш DatabaseClass, чтобы наша временная база данных была доступна в этом классе.

Шаг 11 — На этом шаге мы создадим наш класс Resource, который будет связан с URL, и будет вызвана соответствующая служба.

Шаг 12 — Чистая сборка проекта и запуск его. Если все идет хорошо, вы должны получить следующий вывод в браузере, имея доступ к URL http: // localhost: 8080 / UserProfile / webapi / Profile .

Вы можете видеть, что различные записи заполняются с использованием представления XML.

  Другой метод может быть протестирован с помощью

Другой метод может быть протестирован с помощью Postman, применяя правильный метод URL.

Метод @GET — на следующем снимке экрана показано, как мы можем получить желаемый результат для запроса get, который возвращает все данные пользователя.

  @POST — следующий запрос может быть исполь

@POST — следующий запрос может быть использован для проверки нашего метода Post. Обратите внимание, как proId был сгенерирован автоматически.

  @PUT — этот метод обновит записи.   На сле

@PUT — этот метод обновит записи. На следующем снимке экрана показано, как Джерси берет proId из URL-адреса запроса и обновляет тот же ответ профиля пользователя.

  Таким же образом вы можете проверить другие мето

Таким же образом вы можете проверить другие методы, доступные в ваших веб-сервисах.

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

  Актер должен быть отправной точкой нашего сервис

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

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

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

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

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

Event Driven Architecture архитектура, управляемая событиями

Когда наши сервисы взаимодействуют в стиле RPC, все понятно: у нас есть сервис, который связывает всю бизнес-логику, собирает данные из других сервисов и возвращает их, но что делать в случае архитектуры, управляемой событиями? Мы не знаем, куда идти, — у нас есть только сообщения.

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

У этой проблемы есть два решения.

У этой проблемы есть два решения.

Примеры кода

Не забывайте, что Node.js – асинхронный язык. Рассмотрим пример: приходит запрос, синхронно берёт HolyJSService, асинхронно работает с маппером, выполняющим запись в базу данных, и процесс повторяется для последующих запросов. Никакого порядка в этом не наблюдается, поэтому выделяют два архитектурных подхода:

  • stateless – не хранить состояние; 
  • stateful – добавлять объекту класса поля, которые нужны для выполнения запроса.

Когда используете stateful, при получении запроса вы создаёте новый объект HolyjSService и заполняете необходимыми для выполнения данными. Дальше этот сервис асинхронно идёт в базу данных. Следующий запрос берёт новый экземпляр сервиса, и процесс повторяется.

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

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

При stateless-подходе используется контекст, который создаётся для каждого запроса отдельно, и единственный экземпляр сервиса, поскольку выполнение запросов от него не зависит.

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

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

Для обработчика добавляем контекст вторым параметром – его возвращает метод createContextFromHttpRequest:

Сами по себе хэндлеры предельно простые:

Поскольку для выполнения запросов часто нужны другие сервисы, контекст приходится прокидывать в них. Допустим, на первый микросервис поступил запрос. Присваивайте ему идентификатор X-Request-Id, с которым он пройдётся по всем микросервисам. Благодаря логированию вы легко отследите путь внешнего запроса. X-Trace-Id используется для обозначения единой бизнес-операции, состоящей из нескольких запросов. 

Для создания идентификаторов используют OpenTracing.

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

Теперь отдельные микросервисы используют созданный сервис и общий интерфейс.

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

Немного о мониторинге и тестировании мироксервисной системы

Есть инструменты, которые позволяют развертывать такую систему и следить за ней, например, ZooKeeper, который решает проблему конфигурирования за нас. Также есть такие инструменты типа Logstash, Kibana, Elastic, Serilog, Amazon Cloud Watch. Все они следят за вашими сервисами.

Как тестировать микросервисную систему? Я вижу это следующим образом. У вас есть сервис, который решает какую-то бизнес-задачу. Ваша цель — протестировать его бизнес-контракты. Большинство тестов, которые вы делаете — модульные, которые для кода, написанного в рамках этого сервиса. Это — низ пирамиды тестирования. Следующий уровень — интеграционное тестирование, которое проверяет, как этот сервис отвечает на стандартные запросы. Тут у вас огромное пространство для интеграционного тестирования кода, написанного изолированно. Следующий этап — использование разных инструментов, чтобы гарантировать, что контракт не поменялся. В нашем проекте мы использовали Swagger, который позволяет зафиксировать контракт.

Теги