Из этого руководства вы узнаете что такое микросервисы Java, как их проектировать и создавать. Также здесь затронуты вопросы о библиотеках микросервисов Java и целесообразности применения микросервисов. Перевод и адаптация Java Microservices: A Practical Guide. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 1

Микросервисы Java: основы

Чтобы понять микросервисы, нужно сначала определить, что ими не является. А не является ими “монолит” — Java monolith: что это такое и каковы его преимущества или недостатки?

Что такое Java-монолит?

Представьте, что вы работаете в банке или финтех-стартапе. Вы предоставляете пользователям мобильное приложение, которое можно использовать для открытия нового банковского счета. В Java-коде это приведет к наличию класса-контроллера. Упрощенно он выглядит следующим образом:
@Controller
class BankController {

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        riskCheck(form);
        openBankAccount(form);
        // etc..
    }
}
Вам нужно, чтобы контроллер:
  1. Подтверждал форму регистрации.
  2. Проверял риски по адресу пользователя, чтобы решить, предоставлять ли ему банковский счет.
  3. Открывал банковский счет.
Класс BankController будет упакован вместе с остальными вашими исходниками в файл bank.jar или bank.war для развертывания — это и есть старый добрый монолит, содержащий весь код, необходимый для работы вашего банка. По грубым прикидкам, изначальный размер .jar (или .war) файла составит от 1 до 100 МБ. Теперь вы можете просто запустить файл .jar на своём сервере… и это всё, что нужно сделать для развертывания Java-приложения. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 2Картинка, левый прямоугольник сверху: развёртывание моно(литного) банка java -jar bank.jar (cp .war/.ear into appserver). Правый прямоугольник: открытый браузер.

В чем проблема с Java-монолитами?

По своей сути, в Java-монолитах нет ничего плохого. Однако опыт показал, что если у вас в проекте:
  • Работает много программистов /команд /консультантов ...
  • ...над одним и тем же монолитом под давлением заказчиков с весьма невнятными требованиями…
  • в течение пары лет...
… то в таком случае ваш маленький файлик bank.jar превращается в необозримый гигабайт одного только кода, к которому даже подступиться страшно, не говоря уже о развёртывании.

Как уменьшить размер монолита Java?

Возникает естественный вопрос: как сделать монолит меньше? Сейчас ваш bank.jar работает на одной JVM, один процесс на одном сервере. Ни больше, ни меньше. И вот именно сейчас в голову может прийти логичная мысль: «А ведь служба проверки рисков может использоваться и другими отделами в моей компании! Она не имеет непосредственного отношения к моему монолитному банковскому приложению! Возможно, стоит вырезать её из монолита и развернуть, как отдельный продукт? То есть, если говорить технически, запустить его как отдельный процесс Java”.

Что такое микросервис Java?

На практике такое словосочетание означает, что теперь вызов метода riskCheck() будет производиться не из BankController: этот метод или bean-компонент со всеми его вспомогательными классами будет перемещён в собственный Maven- или Gradle-проект. Также он будет развёрнут и помещён под систему контроля версий независимо от банковского монолита. Однако весь этот процесс извлечения не превращает ваш новый модуль RiskCheck в микросервис как таковой, поскольку определение микросервиса открыто для интерпретации. Это приводит к частым дискуссиям внутри команд и компаний.
  • 5-7 классов в проекте — это микро или как?
  • 100 или 1000 классов... всё еще микро?
  • Микросервис вообще связан с количеством классов или нет?
Давайте оставим теоретические рассуждения, а вместо них будем придерживаться прагматических соображений и сделаем вот что:
  1. Назовём все отдельно развертываемые сервисы микросервисами, независимо от их размеров или границ домена.
  2. Подумаем, как устроить межсервисное общения. Нашим микросервисам нужны способы общения друг с другом.
Итак, подведем итоги: раньше у вас был один JVM-процесс, цельный монолит для работы банка. Теперь у вас есть JVM-процесс банковского монолита и отдельный микросервис RiskCheck, который работает в рамках собственного JVM-процесса. И теперь для проверки рисков ваш монолит должен вызывать этот микросервис. Каким образом это сделать?

Как наладить коммуникацию между микросервисами Java?

В целом и общем есть два варианта — синхронная и асинхронная коммуникация.

Синхронная коммуникация: (HTTP)/REST

Обычно синхронизированная коммуникация между микросервисами осуществляется через HTTP и REST-подобные сервисы, которые возвращают XML или JSON. Разумеется, могут быть и другие варианты — взять хотя бы Google Protocol Buffers. Если вам нужен немедленный ответ, лучше использовать REST-коммуникацию. В нашем примере именно так и нужно делать, поскольку проверка рисков обязательна перед открытием счета. Если нет проверки рисков, нет и счёта. Инструменты обсудим ниже, в разделе “Какие библиотеки лучше всего подходят для синхронных вызовов Java REST”.

Обмен сообщениями — асинхронная коммуникация

Асинхронная микросервисная связь обычно осуществляется посредством обмена сообщениями с реализацией JMS и/или с помощью протокола, например, AMQP. Мы здесь написали “обычно” не просто так: скажем, количество интеграций по электронной почте/SMTP нельзя недооценивать. Используйте его тогда, когда вам не нужен незамедлительный ответ. Например, пользователь нажимает кнопку «купить сейчас», а вы в свою очередь хотите сгенерировать счет-фактуру. Этот процесс, безусловно, не должен происходить в рамках цикла запроса-ответа пользователя на покупку. Ниже мы опишем, какие инструменты лучше всего подходят для асинхронного обмена сообщениями Java.

Пример: вызов REST API в Java

Предположим, мы избрали синхронную микросервисную коммуникацию. В таком случае наш Java-код (тот, что мы приводили выше) на низком уровне будет выглядеть примерно так. (под низким уровнем здесь мы понимаем тот факт, что для микросервисной коммуникации обычно создаются клиентские библиотеки, которые абстрагируют вас от реальных HTTP-вызовов).
@Controller
class BankController {

    @Autowired
    private HttpClient httpClient;

    @PostMapping("/users/register")
    public void register(RegistrationForm form) {
        validate(form);
        httpClient.send(riskRequest, responseHandler());
        setupAccount(form);
        // etc..
    }
}
Исходя из кода, становится ясно, что теперь нам нужно развернуть два Java-(микро) сервиса, Bank и RiskCheck. В итоге у нас будет запущено два JVM-процесса. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 3Вот и все, что вам нужно для разработки проекта с микросервисами Java: просто создавайте и развёртывайте меньшие фрагменты (файлы .jar или .war) вместо одного монолитного. Остаётся неясным ответ на вопрос, каким образом нам следует разрезать монолит на микросервисы? Насколько мелкими должны быть эти кусочки, как определить правильный размер? Давайте проверим.

Архитектура Java Microservices

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

От монолита к микросервисам

Одна из самых логичных идей — извлечь микросервисы из существующего монолита. Обратите внимание, что приставка «микро» здесь на самом деле не означает, что извлеченные сервисы будут действительно небольшими, это совершенно не обязательно. Давайте рассмотрим теоретические основы.

Идея: разбить монолит на микросервисы

К легаси-проектам можно применить микросервисный подход. И вот почему:
  1. Чаще всего такие проекты сложно поддерживать /изменять/расширять.
  2. Все, начиная от разработчиков и заканчивая руководством, хотят упрощения.
  3. У вас (относительно) четкие границы домена, то есть вы знаете, что именно должно делать ваше программное обеспечение.
Возвращаясь к нашему примеру, это означает, что вы можете взглянуть на свой банковский Java-монолит и попытаться разбить его по границам домена.
  • Так, будет разумно выделить в отдельный микросервис «Управление учетными записями», обработку пользовательских данных (таких как имена, адреса, номера телефонов).
  • Или вышеупомянутый «Модуль проверки рисков, который проверяет уровни риска пользователя и может использоваться многими другими проектами или даже отделами компании.
  • Или модуль выставления счетов, который отправляет счета в формате PDF или по почте.

Воплощение идеи: пусть это сделает кто-то другой

Описанный выше подход отлично смотрится на бумаге и UML-подобных диаграммах. Однако всё не так просто. Для его практической реализации нужна серьезная техническая подготовка: пропасть между понимаем того, что было бы неплохо извлечь из монолита и самим процессом извлечения — огромна. Большинство корпоративных проектов доходят до стадии, когда разработчики опасаются, скажем, обновить 7-летнюю версию Hibernate до более новой. Вместе с ней обновятся библиотеки, однако существует неиллюзорная опасность что-нибудь поломать. И вот, те же разработчики теперь должны копаться в древнем легаси-коде с неясными границами транзакций базы данных и извлекать четко определенные микросервисы? Чаще всего эта задача очень сложна, её не получится “решить” на доске или на совещаниях по архитектуре. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 4Процитирую Twitter разработчика @simonbrown: Я буду повторять это снова и снова ... если люди не могут правильно строить монолиты, микросервисы не помогут. Саймон Браун

Проект “с нуля” на базе микросервисной архитектуры

В случае новых Java-проектов три нумерованных пункта из предыдущей части выглядят несколько иначе:
  1. Вы начинаете с чистого листа, так что нет “багажа” для обслуживания.
  2. Разработчики хотели бы, чтобы в будущем все было просто.
  3. Проблема: у вас гораздо более туманная картина границ доменов: вы не знаете, что на самом деле должно делать ваше программное обеспечение (подсказка: agile;))
Это приводит к тому, что компании пытаются использовать новые проекты с микросервисами Java.

Техническая микросервисная архитектура

Первый пункт кажется наиболее очевидным для разработчиков, однако находятся и те, кто крайне не рекомендует его. Хади Харири рекомендует рефакторинг «Extract Microservice» в IntelliJ. И хотя следующий пример очень упрощён, реализации, наблюдаемые в реальных проектах, к сожалению, не слишком далеко от него ушли. До микросервисов
@Service
class UserService {

    public void register(User user) {
        String email = user.getEmail();
        String username =  email.substring(0, email.indexOf("@"));
        // ...
    }
}
С substring-микросервисом Java
@Service
class UserService {

    @Autowired
    private HttpClient client;

    public void register(User user) {
        String email = user.getEmail();
        //теперь вызываем substring microservice via http
        String username =  httpClient.send(substringRequest(email), responseHandler());
        // ...
    }
}
Таким образом, вы, по сути, включаете вызов метода Java в вызов HTTP, без очевидных причин для этого. Одна из причин, однако, заключается в следующем: отсутствие опыта и попытка форсировать подход на основе микросервисов Java. Рекомендация: не делайте этого.

Workflow-ориентированная микросервисная архитектура

Следующим распространенным подходом является разделение микросервисов Java на модули на базе workflow. Пример из реальной жизни: в Германии, когда вы обращаетесь к (общедоступному) врачу, он должен записать ваше посещение в своей медицинской CRM-системе. Чтобы получить оплату от страховки, он отправит данные о вашем лечении (и лечении других пациентов) посреднику через XML. Посредник рассмотрит этот XML-файл и (упрощенно):
  1. Проверит, правильный ли XML-файл получен.
  2. Проверит правдоподобие процедур: скажем, годовалый ребёнок, получивший три процедуры чистки зубов за один день от врача-гинеколога выглядит несколько подозрительно.
  3. Объединит XML с некоторыми другими бюрократическими данными.
  4. Перешлёт XML-файл в страховую компанию, чтобы инициировать платежи.
  5. И перешлёт результат врачу, снабдив его сообщением «успех» или «пожалуйста, отправьте эту запись еще раз, как только это будет иметь смысл».
Примечание. В этом примере коммуникация между микросервисами не играет роли, но вполне может быть выполнена асинхронно брокером сообщений (например, RabbitMQ), поскольку врач все равно не получает немедленной обратной связи. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 5Снова таки, это отлично выглядит на бумаге, но возникают закономерные вопросы:
  • Есть ли необходимость в развертывании шести приложений для обработки одного XML-файла?
  • Действительно ли эти микросервисы независимы друг от друга? Могут ли они быть развернуты независимо друг от друга? С разными версиями и схемами API?
  • Что делает микросервис правдоподобия информации, если микросервис проверки не работает? Система всё еще работает?
  • Разделяют ли эти микросервисы одну и ту же базу данных (им, безусловно, нужны некоторые общие данные в таблицах БД), или у каждого есть своя собственная?
  • … и многое другое.
Интересно, что приведенная выше диаграмма выглядит проще, потому что у каждого сервиса теперь есть свое точное, четко определенное назначение. Раньше это выглядело примерно как этот страшный монолит: Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 6Несмотря на то, что можно спорить о простоте этих диаграмм, теперь вам определенно нужно решить эти дополнительные операционные задачи.
  • Вам нужно не просто развернуть одно приложение, а как минимум шесть.
  • Возможно, даже потребуется развернуть несколько баз данных, в зависимости от того, как далеко вы хотите углубиться в микросервисную архитектуру.
  • Нужно следить за тем, чтобы каждая система работала в режиме онлайн, причём работала нормально.
  • Необходимо убедиться, что ваши вызовы между микросервисами действительно устойчивы (см. Как сделать микросервис Java устойчивым?).
  • И все остальное, что подразумевает эта настройка — от локальных настроек разработки до интеграционного тестирования.
Так что рекомендация будет следующей:
  • Если вы не Netflix (скорее всего, вы не Netflix) ...
  • Если вы не обладаете сверхсильными навыками работы, при которых вы открываете среду разработки, а она вызывает хаосную обезьяну, которая отбрасывает вашу производственную базу данных, которая легко восстанавливается через 5 секунд.
  • или вы чувствуете себя как @monzo и готовы опробовать 1500 микросервисов просто потому, что можете.
→ Не делайте этого. А теперь менее гиперболизировано. Попытка моделировать микросервисы после доменных границ кажется вполне разумной. Но это не означает, что нужно взять один рабочий процесс и разделить его на крошечные отдельные части (получить XML, проверить XML, переслать XML). Следовательно, всякий раз, когда вы начинаете новый проект с микросервисам Java, и границы домена все еще очень расплывчаты, старайтесь поддерживать размер ваших микросервисов на нижнем уровне. Позднее вы всегда сможете добавить больше модулей. И убедитесь, что у вас найдётся продвинутый DevOps в команде/компании/подразделении для поддержки вашей новой инфраструктуры. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 7

Полиглот или командно-ориентированная микросервисная архитектура

Существует третий, почти либертарианский, подход к разработке микросервисов: предоставление командам или даже отдельным лицам возможности реализовывать пользовательские истории с применением любого количества языков или микросервисов (маркетологи называют такой подход “полиглотским программированием”). Так, описанная выше служба проверки XML может быть написана на Java, а микросервис валидации в то же самое время — на языке Haskell (чтобы сделать его математически обоснованным). Для микросервиса пересылки страховки можно применить язык Erlang (потому что он действительно должен масштабироваться;)). То, что может показаться забавным с точки зрения разработчика (разработка идеальной системы с вашим идеальным языком в изолированной среде), в сущности, никогда не является тем, чего хочет организация: гомогенизация и стандартизация. Это означает относительно стандартизированный набор языков, библиотек и инструментов, чтобы другие разработчики могли продолжать поддерживать ваш микросервис Haskell в будущем, когда вы перейдете на более экологичные пастбища. Руководство по микросервисам Java. Часть 1: основы микросервисов и их архитектура - 8История показывает, что обычно стандартизация укореняется слишком глубоко. Скажем, разработчикам больших компаний из списка Fortune 500 иногда даже не позволяли использовать Spring, поскольку это «не входит в план компании по технологиям». Впрочем, полный переход на подход полиглота — это почти то же самое, другая сторона той же монеты. Рекомендация: если вы собираетесь использовать полиглот-программирование, попробуйте меньшее разнообразие в одной и той же экосистеме языка программирования. Так, лучше применять вместе Kotlin и Java (оба языка основаны на JVM и 100% совместимы друг с другом), а не Java и, скажем, Haskell. В следующей части вы узнаете о развертывании и тестировании микросервисов Java.