Рубрики
Без рубрики

Микросервисы Java: Практическое руководство

Вы можете использовать это руководство, чтобы понять, что такое микросервисы Java, как вы их проектируете и создаете. A… Помеченный java, микросервисы.

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

[ Примечание редактора : В почти 7000 словах вы, вероятно, не захотите пытаться читать это на мобильном устройстве. Добавьте его в закладки и вернитесь позже.]

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

Чтобы получить реальное представление о микросервисах 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. Откройте банковский счет

Ваш класс банковского контроллера будет упакован вместе со всем вашим другим исходным кодом в bank.jar или банк.военный файл для развертывания: старый добрый монолит, содержащий весь код, необходимый для работы вашего банка. (В качестве приблизительного указателя, изначально ваш файл .jar/.war будет иметь размер в диапазоне от 1 до 100 МБ).

Затем на вашем сервере вы просто запускаете файл .jar – это все, что вам нужно сделать для развертывания Java-приложений.

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

По своей сути, в Java-монолите нет ничего плохого. Просто опыт проекта показал, что, если вы:

  1. Пусть много разных программистов/команд/консультантов… ​

  2. Работа над одним и тем же монолитом под высоким давлением и неясными требованиями… ​

  3. На пару лет… ​

Тогда ваш маленький bank.jar файл, превращается в гигабайтного монстра кода, которого все боятся развертывать.

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

Это, естественно, приводит к вопросу о том, как сделать монолит меньше. На данный момент ваш bank.jar выполняется в одной виртуальной машине, один процесс на одном сервере. Ни больше, ни меньше.

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

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

На практике это означает, что вместо вызова метода проверки рисков() внутри вашего банковского контроллера вы переместите этот метод/компонент со всеми его вспомогательными классами в свой собственный проект Maven/Gradle, поместите его под контроль исходного кода и разверните независимо от вашего банковского монолита.

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

  • Это микро, если внутри всего 5-7 классов?

  • Являются ли 100 или 1000 классов все еще микро-классами?

  • Имеет ли это вообще какое-то отношение к количеству классов?

Вместо того чтобы теоретизировать об этом, мы будем придерживаться прагматизма и сделаем две вещи:

  1. Вызовите все отдельно развертываемые микросервисы служб – независимо от размера или границ домена.

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

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

Как ты это делаешь?

Как взаимодействовать между микросервисами Java?

В основном у вас есть два варианта: синхронная связь или асинхронная связь .

(HTTP)/REST – Синхронная связь

Синхронная микросервисная связь обычно осуществляется через HTTP и REST-подобные сервисы, которые возвращают XML или JSON, хотя это ни в коем случае не требуется (например, посмотрите Буферы протокола Google ).

Используйте REST communication, когда вам нужен немедленный ответ, что мы и делаем в нашем случае, поскольку проверка рисков обязательна перед открытием учетной записи: нет проверки рисков, нет учетной записи.

Что касается инструментов, ознакомьтесь с #синхронные-остальные-инструменты .

Обмен сообщениями – Асинхронная связь

Асинхронная связь микросервисов обычно осуществляется посредством обмена сообщениями с помощью реализации JMS и/или с помощью протокола, такого как AMQP . Обычно, потому что на практике не следует недооценивать количество, например, интеграций, основанных на электронной почте/SMTP.

Используйте его, когда вам не нужен немедленный ответ, скажем, пользователь нажимает кнопку “купить сейчас”, и вы хотите сгенерировать счет-фактуру, что, безусловно, не обязательно должно происходить в рамках цикла запроса пользователя на покупку-ответа.

Что касается инструментов, ознакомьтесь с #asynchronous-rest-инструментами .

Пример: Вызов API-интерфейсов REST в 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 (микро) сервиса. Ваш банк и ваша служба проверки рисков. В итоге у вас будет две JVM, два процесса. Предыдущее изображение будет выглядеть следующим образом:

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

Но это оставляет вопрос: как точно вы сокращаете или настраиваете эти микросервисы? Что это за маленькие кусочки? Каков правильный размер?

Давайте проверим реальность.

Архитектура микросервиса Java

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

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

Одна довольно органичная идея состоит в том, чтобы вырвать микросервисы из существующего монолита. Обратите внимание, что “микро” здесь на самом деле не означает, что сами извлеченные сервисы действительно будут микро – они все равно могут быть довольно большими.

Давайте рассмотрим некоторую теорию.

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

Унаследованные проекты поддаются подходу микросервисов. Главным образом, по трем причинам:

  1. Их часто трудно поддерживать/изменять/расширять.

  2. Все, от разработчиков, операторов до руководства, хотят упростить работу, хотят, чтобы все было проще.

  3. У вас есть (несколько) четкие границы домена, это означает: вы знаете, что должно делать ваше программное обеспечение.

Это означает, что вы можете взглянуть на свой монолит Java bank и попытаться разделить его по границам домена – разумный подход.

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

  • Или вышеупомянутый “Модуль рисков”, который проверяет уровни рисков пользователей и который может использоваться многими другими проектами или даже отделами вашей компании.

  • Или модуль выставления счетов, который отправляет счета-фактуры в формате PDF или по электронной почте.

Реальность: Пусть это сделает кто-то другой

Хотя этот подход определенно хорошо смотрится на бумаге и UML-подобных диаграммах, у него есть свои недостатки. Главным образом, для этого вам нужны очень сильные технические навыки. Почему?

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

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

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

Это уже первый случай в этой статье, когда цитата из @simonbrown в Твиттере вписывается:

Я буду продолжать повторять это… если люди не могут правильно строить монолиты, микросервисы не помогут. Саймон Браун

Микросервисная архитектура нового проекта

При разработке новых, новых Java-проектов все выглядит немного иначе. Теперь эти три точки сверху выглядят немного по-другому:

  1. Вы начинаете с чистого листа, так что нет необходимости сохранять старый багаж.

  2. Разработчики хотели бы, чтобы в будущем все оставалось простым.

  3. Проблема: У вас гораздо более туманное представление о границах домена: вы не знаете, что на самом деле должно делать ваше программное обеспечение (подсказка: гибкое 😉 )

Это приводит к различным способам, которыми компании пытаются заниматься новыми проектами Java-микросервисов.

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

Первый подход является наиболее очевидным для разработчиков, хотя от него настоятельно рекомендуется отказаться. Спасибо Хади Харири за то, что придумал рефакторинг “Извлечь микросервис” в IntelliJ.

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

До Микросервисов

    @Service
    class UserService {

        public void register(User user) {
            String email = user.getEmail();
            String username =  email.substring(0, email.indexOf("@"));
            // ...
        }
    }

С подстрокой Java микросервис

    @Service
    class UserService {

        @Autowired
        private HttpClient client;

        public void register(User user) {
            String email = user.getEmail();
            // now calling the substring microservice via http
            String username =  httpClient.send(substringRequest(email), responseHandler());
            // ...
        }
    }

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

Рекомендация : Не делайте этого.

Архитектура Микросервиса, ориентированная на рабочий процесс

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

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

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

Посредник посмотрит на этот XML-файл и (упрощенно):

  1. Попробуйте и подтвердите, что файл является правильным XML

  2. Попробуйте проверить это на достоверность: имело ли смысл, что 1-летний ребенок трижды чистил зубы у гинеколога в день?

  3. Дополните XML некоторыми другими бюрократическими данными

  4. Отправьте XML-файл в страховую компанию для запуска платежей

  5. И смоделируйте весь обратный путь к врачу, включая сообщение “успех” или “пожалуйста, отправьте этот ввод данных еще раз – как только это будет иметь смысл”.

Если вы сейчас попытаетесь смоделировать этот рабочий процесс, то в итоге получите как минимум шесть микросервисов Java.

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

Опять же, это то, что хорошо выглядит на бумаге, но сразу же приводит к нескольким вопросам:

  • Считаете ли вы необходимым развернуть шесть приложений для обработки 1 xml-файла?

  • Являются ли эти микросервисы действительно независимыми друг от друга? Они могут быть развернуты независимо друг от друга? С разными версиями и схемами API?

  • Что делает микросервис правдоподобия, если микросервис проверки не работает? Тогда система все еще работает?

  • Используют ли эти микросервисы теперь одну и ту же базу данных (им, безусловно, нужны общие данные в таблице базы данных), или вы собираетесь сделать еще больший шаг, предоставив им собственную базу данных?

  • И множество других вопросов об инфраструктуре/операциях.

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

Хотя аргументы могут быть о простоте этих диаграмм, теперь у вас определенно есть эти дополнительные операционные проблемы, которые необходимо решить:

  • Вам нужно развернуть не просто одно приложение, а как минимум шесть.

  • Может быть, даже базы данных, в зависимости от того, как далеко вы хотите зайти.

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

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

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

Рекомендация :

Если не:

  • вы являетесь Netflix (вы не являетесь)… ​

  • у вас супер-сильные навыки работы: вы открываете свою среду разработки, которая запускает обезьяну хаоса, которая удаляет вашу производственную базу данных, которая легко восстанавливается автоматически за 5 секунд

  • или вам хочется @monzo попробовать 1500 микросервисов просто потому, что вы можете.

→ Не делай этого.

Правда, в меньшей степени гиперболе.

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

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

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

Многоязычная или Командно-ориентированная микросервисная архитектура

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

Таким образом, служба проверки XML, описанная выше, может быть написана на Java, в то время как микросервис Правдоподобия написан на Haskell (чтобы сделать его математически обоснованным), а микросервис пересылки страховки должен быть написан на Erlang (потому что он действительно нуждается в масштабировании 😉 ).

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

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

Что интересно: исторически стандартизация зашла слишком далеко. Разработчикам в крупных компаниях из списка Fortune 500 иногда даже не разрешалось использовать Spring, потому что это “не входило в технологический план компании”. Но стать полноценным полиглотом – это почти то же самое, только другая сторона той же медали.

Рекомендация : Если вы собираетесь быть полиглотом, попробуйте меньшее разнообразие на одном и том же языке программирования экосистема . Пример: Kotlin и Java (на основе JVM со 100% совместимостью друг с другом), а не Haskell и Java.

Развертывание и тестирование микросервисов Java

Это помогает быстро оглянуться назад на основы, упомянутые в начале этой статьи. Любая серверная Java-программа, а следовательно, и любой микросервис, представляет собой просто файл .jar/.war.

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

Важно понимать это, особенно когда речь заходит о таких темах, как Docker, Kubernetes или (дрожь) Облако . Почему? Давайте рассмотрим различные сценарии развертывания:

Пример минимального развертывания микросервиса Java

Продолжая пример с банком, мы закончили с нашим monobank.jar файл (монолит) и наш только что извлеченный riskengine.jar (первый микросервис).

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

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

    -r-r------ 1 ubuntu ubuntu     2476 Nov 26 09:41 application.properties
    -r-x------ 1 ubuntu ubuntu 94806861 Nov 26 09:45 monobank-384.jar

    ubuntu@somemachine:/var/www/www.monobank.com/java$ java -jar monobank-384.jar

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
    ...
    -r-r------ 1 ubuntu ubuntu     2476 Nov 26 09:41 application.properties
    -r-x------ 1 ubuntu ubuntu 94806861 Nov 26 09:45 risk-engine-1.jar

    ubuntu@someothermachine:/var/www/risk.monobank.com/java$ java -jar risk-engine-1.jar

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
    ...

Это оставляет открытым вопрос: как вы загружаете свои файлы .properties и .jar на сервер?

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

Как использовать инструменты сборки, SSH и Ansible для развертывания микросервисов Java

Скучный, но совершенно прекрасный ответ на развертывание микросервисов Java заключается в том, как администраторы развернули любой Java-серверная программа в компаниях за последние 20 лет. Со смесью:

  • Ваш любимый инструмент сборки (Maven, Gradle)

  • Старый добрый SSH/SCP для копирования ваших файлов .jars на серверы

  • Сценарии Bash для управления сценариями развертывания и серверами

  • Или еще лучше: некоторые Доступные скрипты.

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

Старая школа, скучная, но работающая.

Как использовать Docker для развертывания микросервисов Java

Вернемся к заманчивым выборам. Пару лет назад Докер или тема контейнеризации вышли на сцену.

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

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

  2. Контейнер гарантирует вам, что он переносим, он работает где угодно. Звучит ли это знакомо?

Интересно, что с учетом переносимости и обратной совместимости JVM это не похоже на серьезные преимущества. Вы могли бы просто скачать JVM.zip на любом сервере, Raspberry Pi (или даже на мобильном телефоне) разархивируйте его и запустите любой файл .jar, который вы хотите.

Это выглядит немного иначе для таких языков, как PHP или Python, где несовместимость версий или настройки развертывания исторически были более сложными.

Или, если ваше приложение Java зависит от множества других установленных служб (с правильными номерами версий): подумайте о базе данных, такой как Postgres, или хранилище значений ключей, такое как Redis.

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

  • Настройка однородных тестовых или интеграционных сред с помощью таких инструментов, как Тестовые контейнеры .

  • Упрощение установки сложных развертываемых приложений. Возьмите программное обеспечение Дискурс форум. Вы можете установить его с помощью одного образа Docker, который содержит все необходимое: от программного обеспечения Discourse, написанного на Ruby, до базы данных Postgres, Redis и кухонной раковины.

Если ваши развертываемые объекты выглядят одинаково или вы хотите запустить небольшую базу данных Oracle на своей машине разработки, попробуйте Docker.

Итак, подводя итог, вместо того, чтобы просто копировать файл .jar, теперь вы будете:

  • Объедините свой файл jar в изображение Docker

  • Перенесите этот образ docker в частный реестр docker

  • Извлеките и запустите это изображение на вашей целевой платформе

  • Или скопируйте образ Docker непосредственно в вашу производственную систему и запустите его

Как использовать Docker Swarm или Kubernetes для развертывания микросервисов Java

Допустим, вы даете Докеру попробовать. Каждый раз, когда вы развертываете свой микросервис Java, вы теперь создаете образ Docker, который объединяет ваш файл .jar. У вас есть пара таких микросервисов Java, и вы хотите развернуть эти службы на нескольких машинах: a кластер .

Теперь возникает вопрос: как вы управляете этим кластером, то есть запускаете свои контейнеры Docker, выполняете проверки работоспособности, развертываете обновления, масштабируете (брррр)?

Два возможных ответа на этот вопрос – Docker Swarm и Kubernetes .

Подробное описание обоих вариантов невозможно в рамках данного руководства, но вывод из реальности таков: оба варианта в конечном итоге зависят от того, что вы пишете файлы YAML (см. #yaml-tales ) для управления вашим кластером. Сделайте быстрый поиск в Твиттере, если хотите узнать, какие чувства это вызывает на практике.

Таким образом, процесс развертывания ваших микросервисов Java теперь выглядит примерно так:

  • Настройка и управление Docker Swarm/Kubernetes

  • Все, начиная с шагов докера выше

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

Как протестировать микросервисы Java

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

На практике вы найдете три разных способа:

  1. С небольшой дополнительной работой (и если вы используете фреймворки, такие как Spring Boot), вы можете объединить все свои микросервисы в один класс запуска и загрузить все микросервисы с помощью одного Wrapper.java класс – зависит от того, достаточно ли у вас памяти на вашем компьютере для запуска всех ваших микросервисов.

  2. Вы можете попытаться воспроизвести настройки Docker Swarm или Kubernetes локально.

  3. Просто больше не проводите интеграционные тесты локально. Вместо этого создайте специальную среду разработки/ТЕСТИРОВАНИЯ. Это то, что на самом деле делает значительное количество команд, поддаваясь боли локальных микросервисных установок.

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

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

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

Общие вопросы о микросервисах Java

Давайте рассмотрим проблемы микросервисов, специфичных для Java, от более абстрактных вещей, таких как устойчивость, до конкретных библиотек.

Как сделать микросервис Java устойчивым?

Напомним, что при создании микросервисов вы, по сути, заменяете вызовы методов JVM синхронными HTTP-вызовами или асинхронными сообщениями.

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

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

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

Шаблоны устойчивости HTTP/REST

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

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

    @Service
    class BillingService {

        @Autowired
        private HttpClient client;

         public void bill(User user, Plan plan) {
            Invoice invoice = createInvoice(user, plan);
            httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
            // ...
        }
    }

Подумайте о том, какие возможные результаты может иметь этот HTTP-вызов. Обобщая, вы получите три возможных результата:

  1. ХОРОШО : Вызов прошел, и счет был успешно создан.

  2. отложенный : Звонок прошел, но для этого потребовалось необычно много времени.

  3. ОШИБКА : Вызов не прошел, возможно, из-за того, что вы отправили несовместимый запрос или система была отключена.

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

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

Интересным случаем “предупреждения” является случай с задержкой. Возможно, жесткий диск микросервиса респондента заполнен, и вместо 50 мс для ответа требуется 10 секунд. Это может стать еще более интересным, когда вы испытываете определенную нагрузку, так что невосприимчивость вашего БиллингСервиса начинает каскадировать через вашу систему. Подумайте о медленной кухне, медленно начинающей блокировать всех официантов ресторана.

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

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

Шаблоны устойчивости обмена сообщениями

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

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

    @Service
    class BillingService {

        @Autowired
        private RabbitTemplate rabbitTemplate;

         public void bill(User user, Plan plan) {
            Invoice invoice = createInvoice(user, plan);
            // converts the invoice to,for example, json and uses it as the message's body
            rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
            // ...
        }
    }

Теперь потенциальные случаи ошибок выглядят немного иначе, так как вы больше не получаете немедленных ответов “ОК” или “ОШИБКА”, как это было при синхронной HTTP-связи. Вместо этого у вас примерно будут эти три случая ошибок:

  1. Было ли мое сообщение доставлено и воспринято работником? Или он потерялся? (Пользователь не получает счет-фактуру).

  2. Было ли мое сообщение доставлено только один раз? Или доставлено более одного раза и обработано только один раз? (Пользователь получит несколько счетов-фактур).

  3. Конфигурация: От “Правильно ли я использовал ключи маршрутизации/имена обмена” до “мой брокер сообщений настроен и поддерживается правильно или его очереди переполнены?” (Пользователь не получает счет-фактуру).

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

  • Если вы используете реализации JMS, такие как ActiveMQ , вы можете захотеть обменять скорость на гарантии двухфазных (XA) фиксаций .

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

  • А также у кого-нибудь есть опыт настройки, например, серверов Active или RabbitMQ и их правильной настройки, особенно при использовании в сочетании с кластеризацией и докером (разделение сети, кто-нибудь? 😉 )

Какой фреймворк микросервиса Java является лучшим?

С одной стороны, у вас есть установленные и очень популярные варианты, такие как Spring Boot , что позволяет очень легко создавать файлы .jar, которые поставляются со встроенным веб-сервером, таким как Tomcat или Jetty, и которые вы можете немедленно запускать в любом месте.

Однако недавно и частично вдохновленный параллельными разработками, такими как реактивное программирование, Kubernetes или GraalVM ,

Чтобы назвать несколько: Кварки , Микронавт , Верт.х , Хелидон .

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

За исключением Spring Boot, все фреймворки микросервисов обычно позиционируют себя как невероятно быстрые , монументально быстрое время запуска , низкий объем памяти , способные масштабироваться неограниченно , с впечатляющими графиками, сравнивающими себя с бегемотом Spring Boot или друг с другом.

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

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

Вам придется взглянуть на это с другой стороны.

Если до сих пор:

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

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

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

Затем добавление дополнительных задач микросервиса (подумайте: устойчивость, сеть, обмен сообщениями, DevOps, инфраструктура) сверху окажет гораздо более сильное влияние на ваш проект, чем загрузка пустого hello world. А для быстрого повторного развертывания во время разработки вы, наконец, можете захотеть изучить такие решения, как JRebel или DCEVM .

Возвращаясь к цитате Саймона Брауна: Если люди не могут создавать (быстрые и эффективные) монолиты, им будет трудно создавать (быстрые и эффективные) микросервисы – независимо от структуры.

Итак, выбирайте свой фреймворк с умом.

Какие библиотеки лучше всего подходят для синхронных вызовов Java REST?

Перейдем к более практическим аспектам вызова HTTP REST API. С технической стороны низкого уровня вы, вероятно, получите одну из следующих клиентских библиотек HTTP:

Собственный HttpClient Java (начиная с Java 11), HttpClient Apache или OkHttp .

Обратите внимание, что я говорю “вероятно” здесь, потому что существует множество других способов, от старых добрых клиентов JAX-RS до современных WebSocket клиенты.

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

Какие брокеры лучше всего подходят для асинхронного обмена сообщениями Java?

Начиная с асинхронного обмена сообщениями, вы, скорее всего, в конечном итоге получите либо ActiveMQ (классический или Artemis) , RabbitMQ или Кафка . Опять же, это просто популярный выбор.

Однако вот пара случайных моментов:

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

  • ActiveMQ исторически обладал преимуществом простого встраивания (для тестирования), которое можно смягчить с помощью настроек RabbitMQ/Docker/Testcontainer

  • Кафка не является традиционным брокером. Это совсем наоборот, по сути, относительно “тупое” хранилище сообщений (например, файл журнала), нуждающееся в более умных потребителях для обработки.

Чтобы лучше понять, когда использовать RabbitMQ (или традиционные брокеры сообщений в целом) или Кафку, взгляните на соответствующий пост в блоге Pivotal в качестве отправной точки .

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

Теперь у вас те же аргументы в пользу того, что RabbitMQ работает медленно с просто последовательными 20-30 тыс. сообщений каждую секунду. Кафка цитируется со скоростью 100 тысяч сообщений в секунду. Во-первых, такого рода сравнения удобно упускают из виду, что вы на самом деле сравниваете яблоки и апельсины.

Но даже более того: оба показателя пропускной способности могут быть ниже или ниже среднего для Alibaba Group , но ваш автор никогда не видел проектов такого размера ( миллионы сообщений каждую минуту) в реальном мире. Они определенно существуют, но об этих цифрах не стоит беспокоиться для остальных 99% обычных бизнес-проектов Java.

Так что не обращайте внимания на шумиху и выбирайте с умом.

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

В зависимости от вашего стека вы можете использовать специальные инструменты Spring (Весенняя экосистема) или что-то вроде Аркиллиан (экосистема JavaEE).

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

Для издевательства над целыми HTTP-серверами взгляните на Wiremock . Для тестирования асинхронных сообщений попробуйте внедрить (ActiveMQ) или закрепить (RabbitMQ), а затем написать тесты с помощью Awaitility DSL .

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

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

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

Ведение журнала с помощью микросервисов – интересная и довольно сложная тема. Вместо одного файла журнала, который вы можете удалить или просмотреть, теперь у вас есть n файлов журнала, которые вы хотели бы видеть объединенными.

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

На практике вы найдете различные подходы:

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

  • Запускайте комбинации cat/grep/unig/sort в параллельных сеансах SSH. Вы можете сказать своему менеджеру: это то, что Amazon AWS делает внутри компании .

  • Используйте такой инструмент, как Graylog или стек ELK (Elasticsearch, Logstash, Kibana)

Как мои микросервисы находят друг друга?

До сих пор мы как бы предполагали, что все наши микросервисы знают друг друга, знают свои соответствующие IP-адреса. Скорее статическая установка. Итак, наш банковский.168.200.1] знает, что он должен поговорить с риском.168.200.2], который жестко закодирован в файле свойств.

Однако вы можете сделать все гораздо более динамичным:

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

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

  • И теперь, поскольку все динамично, у вас возникают новые проблемы, такие как автоматическое избрание лидера: кто является мастером , который работает над определенными задачами, чтобы, например, не обрабатывать их дважды? Кто заменяет лидера, когда он терпит неудачу? С кем?

В общих чертах, это то, что называется оркестровка микросервисов и еще одна огромная тема сама по себе.

Библиотеки, такие как Eureka или Zookeeper , пытаются “решить” эти проблемы, например, клиенты или маршрутизаторы, знающие, какие службы где доступны. С другой стороны, они вносят множество дополнительных сложностей.

Просто спросите любого, кто когда-либо управлял установкой ZooKeeper.

Как выполнить авторизацию и аутентификацию с помощью микросервисов Java?

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

Как мне убедиться, что все мои среды выглядят одинаково?

То, что верно для развертываний, не связанных с микросервисами, также верно и для развертываний микросервисов. Вы попробуете комбинацию контейнеров Docker/Test, а также сценариев/Ansible.

Старайтесь, чтобы все было просто.

Не вопрос: Отступы Yaml.

Отрываясь от конкретных библиотечных вопросов, давайте кратко рассмотрим Yaml. Это формат файла, используемый де-факто в качестве формата файла для “записи конфигурации в виде кода”. От более простых инструментов, таких как Ansible, до мощных Kubernetes.

Чтобы самостоятельно испытать боль с отступами YAML, попробуйте написать простые файлы Ansible и посмотрите, как часто вам нужно повторно редактировать файл, чтобы отступы работали правильно, несмотря на различные уровни поддержки IDE. А затем возвращайтесь, чтобы закончить это руководство.

    Yaml:
      - is:
        - so
        - great

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

К сожалению, эти темы не вошли в эту редакцию данного руководства. Оставайтесь с нами, чтобы узнать больше.

Концептуальные проблемы Микросервиса

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

Несоответствие интерфейса и Бэкэнда

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

Что в старых добрых монолитах у разработчиков интерфейсов был один конкретный источник для получения данных. В микросервисных проектах у разработчиков интерфейсов внезапно появляется n-источников для получения данных.

Представьте, что вы создаете какой-то проект микросервисов Java-IoT. Допустим, вы наблюдаете за машинами, такими как промышленные печи по всей Европе. И эти печи регулярно отправляют вам обновления статуса с указанием их температуры и т.д.

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

Для этого разработчикам интерфейсов, возможно, придется выполнить один или n дополнительных вызовов (в зависимости от вашей реализации подкачки) для микросервиса “получить сведения о печи” с идентификаторами, которые они получили от первого микросервиса.

И хотя это всего лишь простой (но взятый из реального проекта (!)) пример, он демонстрирует следующую проблему:

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

Это проще и быстрее. То же самое относится к разработчикам интерфейсов и микросервисам.

Ожидания руководства

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

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

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

Меньшие кусочки не означают лучшие кусочки

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

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

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

Однако у этого есть простая причина: потому что разработчики Java обычно не заинтересованы в том, чтобы должным образом не обучаться устойчивости, сетям и другим смежным темам.

Более мелкие детали приводят к более техническим деталям

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

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

    @Controller
    class LoginController {

        // ...

        @PostMapping("/login")
        public boolean login(String username, String password) {
            User user = userDao.findByUserName(username);
            if (user == null) {
                // handle non existing user case
                return false;
            }
            if (!user.getPassword().equals(hashed(password))) {
                // handle wrong password case
                return false;
            }
            // 'Yay, Logged in!';
            // set some cookies, do whatever you want
            return true;
        }
    }

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

И поскольку Java в настоящее время вышла из моды, давайте напишем микросервис UserStateChanged на Erlang. И давайте попробуем где-нибудь использовать красно-черные деревья, потому что Стив Йегге написал, что вам нужно знать их изнутри, чтобы подать заявку в Google.

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

Сфабрикованный и чрезмерный пример? Да.

К сожалению, это тоже не редкость в реальной жизни.

Меньшие фрагменты приводят к меньшему пониманию

Тогда есть эта тема понимания всей системы, ее процессов и рабочих процессов, если вы, как разработчик, несете ответственность только за работу с изолированным микросервисом [95:логин-101:updateUserProfile].

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

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

Связь и техническое обслуживание

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

  • Кто работает над микросервисом № 47?

  • Они только что развернули новую, несовместимую версию микросервиса? Где это было задокументировано?

  • С кем мне нужно поговорить по поводу запроса на новую функцию?

  • Кто будет поддерживать этот микросервис Erlang после того, как Макс покинул компанию?

  • Все наши команды микросервисов работают не только на разных языках программирования, но и в разных часовых поясах! Как нам правильно координировать свои действия?

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

Плавник

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

Микросервисы находятся на маятнике

Переход на полноценные микросервисы Java – это один конец маятника. Другой конец был бы чем-то вроде сотен старых добрых модулей Maven в Монолите. Вам придется найти правильный баланс.

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

Микросервисы создают массу дополнительных сложностей

Имейте в виду, что чем больше у вас микросервисов и чем меньше у вас действительно сильных талантов DevOps (нет, выполнение нескольких сценариев Ansible или развертывание на Heroku не считается), тем больше проблем у вас возникнет позже в процессе производства.

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

Шива прекрасно подытожил это в своем блоге :

Я не могу объяснить, как ужасно это ощущается, когда команда тратит 70% времени на борьбу с этой современной инфраструктурой и 30% времени на реальную бизнес-логику. Шива Прасад Редди

Должны ли вы создавать микросервисы Java?

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

Сценарий

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

И давайте также предположим, что ваш Java-монолит может обрабатывать такие рабочие процессы, как регистрация пользователей, и вы не создаете сотни запросов к базе данных для каждого рабочего процесса, а только разумную горстку (< 10).

Вопрос

Сколько подключений к базе данных должен открывать ваш Java-монолит (пул подключений) к вашему серверу базы данных?

Почему? И на сколько одновременно активных пользователей, по вашему мнению, может (примерно) масштабироваться ваш монолит?

Ответ

Опубликуйте свой ответ на эти вопросы в разделе комментариев. Я с нетерпением жду всех ответов.

А теперь решай сам.

Если вы все еще здесь, со мной: Спасибо за чтение!

Там, Откуда Это Взялось, Есть Еще Кое-Что

Эта статья первоначально появилась по адресу https://www.marcobehler.com/guides/java-microservices-a-practical-guide как часть серии руководств по современному программированию на Java. Чтобы найти больше руководств, посетите веб-сайт или подпишитесь на рассылку новостей, чтобы получать уведомления о новых опубликованных руководствах: https://bit.ly/2K0Ao4F .

Оригинал: “https://dev.to/marcobehler/java-microservices-a-practical-guide-29kn”