Начало работы с микросервисами в Spring Boot
В этой статье мы продемонстрируем основные компоненты для создания RESTful микросервисов, используя реестр служб Consul, Spring Boot для всего скаффолдинга, инжекции зависимостей, Maven для сборки, а также Spring REST и Jersey/JaxRS API Java RESTful.
Основные преимущества микросервисов:
За последние два десятилетия предприятие стало очень гибким в нашем SDLC-процессе, но наши приложения, как правило, по-прежнему остаются монолитными, с огромными jar-ами, поддерживающими все разнообразные API и версии на рынке. Но в настоящее время существует стремление к более Lean, DevOps-ным процессам, а функциональность становится «безсерверной». Рефакторинг в микросервисы может уменьшить зацепленность кода и ресурсов, сделать сборки меньше, релизы безопаснее, а API более стабильными.
В этой статье мы создадим простое приложение для управления портфелем на фондовом рынке, которое клиенты могут вызывать для оценки своего портфеля акций (биржевые тикеры и величины). Микросервис портфеля будет извлекать портфель клиента, отправлять его в микросервис ценообразования для применения последних цен, а затем возвращать полностью оцененный и субтотализированный портфель, демонстрируя все это посредством rest-вызова.
Прежде чем мы начнем работу над созданием наших микросервисов, давайте подготовим нашу среду, настроив Consul.
Загрузите Consul
Мы будем использовать Hashicorp Consul для обнаружения сервисов, поэтому перейдите на www.consul.io/downloads.html и загрузите Consul для Windows, Linux, Mac и т.д. Это предоставит вам исполняемый файл, который нужно добавить к своему пути.
Запустите Consul
В командной строке запусктие Consul в режиме dev:
Чтобы убедиться, что он запущен, перейдите в браузер и получите доступ к интерфейсу консула http://localhost:8500. Если все будет хорошо, консул должен сообщить, что он жив и здоров. Нажав на службу консула (слева), вы получите дополнительную информацию (справа).
Если на данный момент есть какие-либо проблемы, убедитесь, что вы добавили Consul к пути выполнения, и доступны порты 8500 и 8600.
Создайте приложение SpringBoot
Мы будем использовать Spring Initializr, который интегрирован в большинство IDE, для скаффолдинга наших SpringBoot приложений. Скриншоты ниже используют IntelliJ IDEA.
Выберите «File/New Project», чтобы открыть новый шаблон проекта, и затем «Spring Initializr».
Вообще, вы можете настроить скаффолдинг без IDE, заполнив онлайн-форму через веб-страницу SpringBoot Initializr start.spring.io, которая создаст zip-файл вашего пустого проекта, готовый для загрузки.
Нажмите «Next» и заполните метаданные проекта. Используйте следующую конфигурацию:
Нажмите «Next», чтобы выбрать зависимости, и введите «Jersey» и «Consul Discovery» в поиске зависимостей. Добавьте эти зависимости:
Нажмите «Next», чтобы указать название проекта и его расположение. Сохраните имя по умолчанию «portfolio» и укажите предпочтительное расположение проекта, затем нажмите «finish», чтобы создать и открыть проект:
Мы можем использовать сгенерированные application.properties, но SpringBoot также распознает формат YAML, что немного легче визуализировать, поэтому давайте переименуем его в application.yml.
Назовем микросервис «portfolio-service». Мы можем указать порт или использовать порт 0, чтобы приложение использовало доступный порт. В нашем случае мы будем использовать 57116. Если вы разместите эту службу в качестве контейнера Docker, вы сможете сопоставить ее с любым выбранным вами портом. Назовите приложение и укажите наш порт, добавив следующее к нашему application.yml:
Чтобы сделать наш сервис доступным, добавим аннотацию к нашему классу приложений SpringBoot. Откройте приложение PortfolioApplication и добавьте @EnableDiscoveryClient над объявлением класса.
Подтвердите импорты. Класс должен выглядеть следующим образом:
(Чтобы продемонстрировать, как микросервисы могут состоять из независимых платформ, мы будем использовать Jersey для этого сервиса и Spring REST для следующего).
Чтобы настроить веб-службу RESTful на Jersey, нам нужно указать класс конфигурации ResourceConfig. Добавьте класс JerseyConfig (для демонстрации мы сохраним его в том же пакете, что и наш класс приложения). Это должно выглядеть так, плюс правильный пакет и импорт:
Обратите внимание, что он наследуется от ResourceConfig, чтобы обозначить его как класс конфигурации Jersey. Атрибут @ApplicationPath («portfolios») определяет контекст вызова, а это означает, что вызовы должны начинаться с элемента пути «portfolios». (Если вы его опустите, контекст по умолчанию «/»).
Класс PortfolioImpl будет обслуживать два запроса: portfolios/customer/
Ваша IDE попросит вас создать PortfolioImpl; сделайте это сейчас. Для демонстрации добавим его в тот же пакет. Введите код ниже и подтвердите все импорты:
Аннотация Component обозначает это как класс компонента Spring и предоставляет его как эндпоинт. Аннотации Path о объявлении класса объявляют, что к классу обращаются через элемент пути “/”, а два поддерживаемых вызова api доступны через portfolios/customer/
(Для нашего демо мы возвращаем захардкоженные значения. Конечно, на практике реализация будет запрашивать базу данных или другую службу или источник данных вместо хардкода.)
Теперь создайте проект и запустите его. Если вы используете IntelliJ, он создаст исполняемый файл по умолчанию, поэтому просто нажмите зеленую стрелку «run». Вы также можете использовать
Теперь мы должны увидеть этот сервис в Consul, поэтому давайте вернемся к нашему браузеру, загрузите http://localhost:8500/ui/#/dc1/services (или обновите, если вы уже там).
Хм, мы видим там наш сервис, но он отображен как неудавшийся. Это потому, что Consul ожидает «здорового» heartbeat-сигнала от нашей службы.
Чтобы генерировать heartbeat-сигналы, мы можем добавить зависимость от службы Spring «Actuator» к pom нашего приложения.
В то время как мы находимся в pom-е, обратите внимание, что существует конфликт версий с Jersey между Consul-стартером и Jersey-стартером. Чтобы сгладить это, назначьте Jersey-стартер первой зависимостью.
Теперь ваш pom должен содержать следующие зависимости:
Перезапустив Consul, служба Portfolio отображает счастливое:
Теперь в portfolio-service есть два передающих узла: один из них — наша реализация портфельного сервиса, а другой — heartbeat.
Давайте проверим порт, который был назначен. Вы можете видеть, что в выводе приложения:
Вы также можете увидеть порт непосредственно в пользовательском интерфейсе Consul. Нажмите «customer-service», затем выберите ссылку «Service ‘customer-service’ check link», в которой отображается служебный порт, в данном случае 57116.
Наш первый микросервис открыт для бизнеса!
Сервис ценообразования
Далее мы создадим наш сервис ценообразования, на этот раз используя Spring RestController вместо Jersey.
Служба ценообразования будет принимать в качестве параметров идентификатор клиента и идентификатор портфеля и будет использовать RestTemplate для запроса услуги портфеля, получения тикеров и акций и возврата текущих цен. (Мне не нужно говорить вам, что эти значения — это поддельные новости, поэтому не используйте их для принятия торговых решений!)
Создайте новый проект, используя следующую информацию:
На этот раз выберите зависимости Web, Consul Discovery и Actuator:
Оставьте название проекта по умолчанию «pricing» и создайте проект в выбранном вами каталоге.
На этот раз мы будем использовать application.properties вместо application.yml.
Задайте имя и порт в application.properties как:
Аннотируйте PricingApplication с @EnableDiscoveryClient. Класс должен выглядеть так, плюс пакет и импорт.
Затем мы создадим класс PricingEndpoint. Здесь я приведу более подробный пример, поскольку он демонстрирует несколько важных функций, включая обнаружение сервисов (поиск портфельной службы) и использование RestTemplate для запроса:
Чтобы найти портфельный сервис, нам необходимо иметь доступ к DiscoveryClient. Его легко получить с помощью аннотации Spring’s Autowired.
Этот экземпляр DiscoveryClient затем используется для поиска службы в вызове:
После того, как служба найдена, мы можем использовать ее для выполнения нашего запроса, который мы составляем в соответствии с вызовом api, созданным в нашем портфельном сервисе.
Наконец, мы используем RestTemplate для выполнения нашего GET-запроса.
Обратите внимание, что для Rest-контроллеров (как и для Spring MVC Request Controller) переменные пути извлекаются с помощью аннотации PathVariable, в отличие от Jersey, который, как мы видели, использует PathParam.
На этом мы завершаем наше ценообразование с помощью Spring RestController.
Документация
Мы решили все эти проблемы, чтобы создать наши микросервисы, но они не будут приносить достаточно пользы, если мы не дадим миру знание о том, как их использовать.
Для этого мы используем удобный и простой в использовании инструмент Swagger, который не только документирует наши вызовы API, но также предоставляет удобный веб-клиент для их вызова.
Во-первых, давайте укажем Swagger в нашем pom:
Затем нам нужно указать Swagger, какой из наших классов мы хотим документировать. Давайте представим новый класс SwaggerConfig, содержащий спецификацию Swagger.
Посмотрим, что делает этот класс. Сначала мы обозначили это как конфигурацию Swagger с аннотацией @ EnableSwagger2.
Затем мы создали компонент Docket, который сообщает Swagger, какие API-интерфейсы должны отображаться. В приведенном выше примере мы сказали Swagger продемонстрировать любой путь, начинающийся с «/pricing». Альтернативой было бы указать классы для документирования, а не для путей:
Перезапустите ценовой микросервис и вызовите из браузера http://localhost:57216/swagger-ui.html
Нажмите «List Operations», чтобы подробно просмотреть операции сервиса.
Нажмите «Expand Operations», чтобы создать запрос на основе формы. Задайте некоторые параметры, нажмите «Try it out!» и дождитесь ответа:
Вы можете добавить намного больше цветов, добавив аннотации Swagger к вашим методам.
Например, украсьте существующий метод PricingImpl.getPricedPortfolio, используя аннотацию @ApiOperation, как показано ниже:
Перезагрузите и обновите swagger-ui, чтобы увидеть новую уточненную документацию:
И это далеко не все, что вы можете сделать с Swagger, поэтому ознакомьтесь с документацией.
Еще больше о работе Spring Boot вам расскажет Юрий Дворжецкий, преподаватель нашего курса «Разработчик на Spring Framework»:
Конфигурационные файлы в Spring Boot
Содержание
Значения параметров системы удобно отделять от программного кода, чтобы можно было их менять без перекомпиляции всего приложения. Spring Boot предоставляет нам удобный способ работы с конфигурационными файлами. Ниже мы рассмотрим несколько случаев, начиная с самого простого.
Исходники доступны на github.
Одиночные параметры
Отдельное свойство можно внедрить в любой компонент Spring при помощи аннотации @Value.
Предположим, у нас есть простейшее Spring Boot приложение, в котором есть rest-контроллер с методами.
Добавим метод, который в ответ возвращает приветственный текст для пользователя, а имя пользователя будем брать из конфига.
@RestController
@RequestMapping ( «/value» )
public class ValueController <
Здесь аннотация @Value подставляет значение для поля name из конфигурационного файла application.yml. Сам файл может выглядеть следующим образом (в формате yaml):
Формат yaml является наиболее современным и компактным, однако вы можете использовать и другие форматы (текст, xml).
Чтобы увидеть результат работы метода, запустите приложение и откройте в браузере http://127.0.0.1:8080/value/hello.
Вы можете подставлять не только текстовые, но и числовые значения. Добавим в наш контроллер ещё одно поле с параметром и ещё один GET-метод.
@GetMapping ( «/number» )
public String getImportantNumber() <
return Integer.toString(number);
>
Расширим наш конфигурационный файл:
Обратите внимание, что оба свойства имеют общий префикс «very.important», что наглядно отображает иерархичная структура yaml.
Мы также можем указывать значения параметров по умолчанию. Сделать это можно всё в той же аннотации @Value:
Здесь значение по умолчанию отделено от имени параметра двоеточием. Значение по умолчанию будет использоваться только тогда, когда этого параметра нет в application.yml.
Группировка параметров
@Component
@ConfigurationProperties (prefix = «very.important» )
public class VeryImportantConfig <
private String name;
private int number;
// get- и set-методы для этих свойств
>
Создадим ещё один rest-контроллер, который по функционалу будет аналогичен первому, но при этом будет использовать наш VeryImportantConfig.
@RestController
@RequestMapping (value = «/grouped» )
public class GroupedValueController <
private final VeryImportantConfig veryImportantConfig;
@GetMapping ( «/number» )
public String getImportantNumber() <
return Integer.toString(veryImportantConfig.getNumber());
>
>
Обратите внимание, что для внедрения нашего бина в данный контроллер мы не пишем явно аннотацию @Autowired. Это возможно благодаря тому, что у нас имеется контруктор, принимающий этот бин и при этом конструктор единственный.
Список значений как параметр
До сих пор мы работали с одиночными значениями. А что, если нам нужно подгрузить параметр, который является списком? Например, список рабочих дней недели. В yaml список значений записывается следующим образом:
Каждый элемент списка начинается с тире и записывается в отдельной строке. Весь список в данном случае доступен по имени very.important.days. Добавить его в наш конфигурационный бин не менее проще, чем предыдущие значения:
@Component
@ConfigurationProperties (prefix = «very.important» )
public class VeryImportantConfig <
// другие поля
private List days;
// другие get- и set-методы
public List getDays() <
return days;
>
В rest-контроллер добавим такой метод:
Выполнив get-запрос по адресу http://127.0.0.1:8080/grouped/days мы увидим список рабочих дней недели в формате json.
Конвертер для произвольных типов данных
Со строками и списками разобрались, а что, если мы хотим хранить в конфигурационном файле и другие типы? Предположим, мы хотим хранить список праздничных дней 2019 года. Сначала добавим их в конфиг:
Пропишем этот список в нашем конфигурационном бине:
private List holidays;
public List getHolidays() <
return holidays;
>
Затем добавим метод в контроллер:
Если после этого запустим приложение, то при старте увидим ошибку «Failed to bind properties under ‘very.important.holidays[0]’ to java.time.LocalDate». Эта ошибка связана с тем, что Spring не умеет преобразовывать дату из строки в LocalDate. Для решения этой проблемы мы можем создать собственный конвертер.
@Component
@ConfigurationPropertiesBinding
public class MyCustomConverter implements Converter <
@Override
public LocalDate convert(String timestamp) <
return LocalDate.parse(timestamp);
>
>
Мы реализовали стандартный интерфейс Converter, типизировав его исходным типом (String) и типом, в который мы преобразуем (LocalDate). Реализация единственного метода convert() довольно проста. Конвертер также должен быть снабжён аннотациями @Component и @ConfigurationPropertiesBinding, чтобы он использовался при инициализации нашего конфигурационного бина.
После этого мы можем выполнить запрос GET http://127.0.0.1:8080/grouped/holidays и увидеть список праздничных дат 2019 года.
Выводы
Конфигурационные файлы в Spring Boot обладают довольно хорошей гибкостью при работе со стандартными типами (числа и строки). А если этой гибкости не хватает, мы всегда можем написать собственный конвертер, который будет преобразовывать значения из строки в любой другой тип данных.
При этом важно отметить, что совершенно не обязательно использовать формат yaml. В любой момент вы можете изменить формат конфигурационных файлов и при этом вам не нужно будет править код вашего приложения.
Внешние данные конфигурации в Spring
Введение
Ситуация
Большинство наших приложений зависят от внешних сервисов, например серверов баз данных, SMS-шлюзов и систем наподобие PayPal. Эти сервисы могут существовать более чем в одной среде, то есть в средах разработки и эксплуатации. Если мы хотим подключиться к эксплуатационной среде, мы должны сначала пройти через среду разработки. Таким образом, во время создания приложений нам приходится переключаться между средами. Это связано с тем, что у каждой среды своя уникальная конфигурация со своими параметрами подключения и прочими значениями.
Проблема
Предположим, что мы разрабатываем платежный сервис, который подключается к внешнему платежному провайдеру. Значения коммерческого счета в эксплуатационной среде и среде разработки не совпадают. То есть при каждом переключении среды нам приходится изменять значения и компилировать код заново, а это неэффективно.
Решение
Лучшее решение этой проблемы — вывод данных конфигурации приложения во внешний источник. Нам не нужно будет каждый раз заново компилировать код при переключении среды. Мы сможем переопределить значения параметров конфигурации, затратив меньше усилий. Наше приложение Spring сможет считывать значения коммерческого счета из внешних источников, например переменных среды, аргументов командной строки и файлов свойств.
Вывод данных конфигурации во внешний источник
Источники свойств
Существуют различные способы вывода данных конфигурации приложения Spring во внешний источник. Для задания свойств приложения мы можем использовать переменные среды, файлы свойств (например, в формате YAML или с расширением *.properties) и аргументы командной строки. Мы также можем хранить файлы свойств в произвольных местах и сообщать приложению Spring, где их искать.
Файлы свойств
По умолчанию приложение Spring загружает свойства из файлов application.properties или application.yml из перечисленных ниже источников в порядке приоритета (то есть вышестоящий файл свойств переопределяет файлы из источников нижнего уровня) и добавляет их в среду:
подкаталог конфигурации текущего каталога;
пакет конфигураций в параметре classpath;
корневой каталог classpath.
Пользовательское место хранения
Примечание. При указании расположения каталога необходимо убедиться, что после значения spring.config.location стоит символ / (например, spring.config.location=classpath:/config/ ) и что задано имя файла конфигурации по умолчанию. Также с помощью ключа свойств spring.config.additional-location можно указать дополнительные каталоги, поиск в которых будет проводиться перед поиском в местоположениях по умолчанию.
Форматы файлов
application.properties
application.yml
Множество профилей
1. Файл YAML
application.yml
application-development.properties
application-production.properties
При наличии свойств, общих для профилей разработки и эксплуатации, нам может потребоваться файл application.properties, заданный по умолчанию.
application.properties
Образец данных конфигурации в приведенных выше фрагментах кода устанавливает профиль разработки в качестве активного профиля. Поэтому при запуске приложения значения свойств, определенные в этом профиле, будут иметь приоритет над эксплуатационным профилем. Но не стоит забывать, что настройки профиля можно также переопределить с помощью аргументов командной строки.
Вы можете узнать больше о профилях Spring в этой статье.
Читаемость
application.yml
application.properties
Тестовые примеры для проверки сопоставлений можно найти в тестовых пакетах с примером кода из данной статьи.
Аргументы командной строки
Когда мы вводим аргумент командной строки, приложение Spring преобразует его в свойство и добавляет в Spring Environment. С помощью этих аргументов можно сконфигурировать параметры приложения. К примеру, следующие аргументы командной строки переопределят порт сервера приложения, заданный любым другим источником свойств. При запуске приложения командой Maven или Java мы все равно получим тот же результат.
Команда Maven:
Команда JVM:
Также можно вводить несколько аргументов одновременно. Дополним приведенный выше пример еще одним свойством — портом сервера, как показано ниже.
Команда Maven (через пробел):
Команда JVM:
Переменные среды
Откроем терминал и выполним следующую команду. Она устанавливает переменные среды приложения, переопределяя настройки подключения.
После этого запустим наше приложение:
Результат
Проверив журнал, мы заметим, что адреса подключения в профиле разработки были переопределены, а значения в файле JSON, который мы передали через переменную среды, были в приоритете.
Передача свойств
Этот метод актуален при наличии небольшого количества свойств, но он не рекомендуется, если свойств много. Представьте, если в коммерческом счете более двадцати свойств, нам придется указывать аннотацию @Value двадцать раз. Приведенный ниже фрагмент кода показывает, как можно использовать эту аннотацию для внедрения значения свойства в приложение.
Важно убедиться, что имя свойства @Value совпадает с именем, указанным в источниках свойств.
При наличии нескольких свойств мы можем сгруппировать их и сопоставить с классом POJO. Таким образом, мы получим структурированный и типобезопасный объект, который сможем внедрить в любое место в нашем приложении. Поэтому вместо использования аннотации @Value значения свойств можно получить с помощью метода чтения значения класса POJO.
application.yml
Важно отметить, что аннотация @ConfigurationProperties также позволяет нам сопоставлять списки и карты, как показано ниже:
Порядок приоритета данных конфигурации
В приложении Spring Boot может быть несколько источников свойств. Поэтому важно знать, какой источник свойства имеет наивысший приоритет. Например, если конфигурация нашего приложения находится в файле application.yml и во время выполнения приложения мы решаем передать аргументы командной строки, тогда значения свойств в файле application.yml будут переопределены значениями аргументов командной строки.
В Spring Boot 2.2.x используется приведенный ниже порядок источников свойств. Источник свойств, расположенный выше в списке, имеет приоритет над источниками под ним.
Аннотации @TestPropertySource в ваших тестах.
Атрибут свойств в ваших тестах. Он доступен в @SpringBootTest и тестовых аннотациях для проверки работы определенного фрагмента вашего приложения.
Аргументы командной строки.
Свойства из SPRING_APPLICATION_JSON (строковый JSON в переменной среды или системном свойстве).
Переменные среды ОС.
Свойства по умолчанию (заданные настройкой SpringApplication.setDefaultProperties ).



