- PVSM.RU - https://www.pvsm.ru -

В предыдущей статье [1] было приведено краткое описание процесса создания микросервиса на современных JVM фреймворках, а также их сравнение. В этой статье будет более детально рассмотрен недавно вышедший Quarkus [2] на примере создания микросервиса с использованием упомянутых технологий и в соответствии с требованиями, указанными в основной статье. Полученное приложение станет частью следующей микросервисной архитектуры:

Исходный код проекта, как обычно, доступен на GitHub [3].
Перед началом работы с проектом должны быть установлены:
Для генерации нового проекта используйте web starter [5] или Maven (для создания Maven [6] проекта или Gradle [7] проекта). Стоит отметить, что фреймворк поддерживает языки Java, Kotlin и Scala.
В этом проекте в качестве системы сборки используется Gradle Kotlin DSL. Скрипт сборки должен содержать:
плагины
Листинг 1. build.gradle.kts [8]
plugins {
kotlin("jvm")
kotlin("plugin.allopen")
id("io.quarkus")
}
Разрешение версий плагинов выполняется в settings.gradle.kts [9].
зависимости
Листинг 2. build.gradle.kts [8]
dependencies {
...
implementation(enforcedPlatform("io.quarkus:quarkus-bom:$quarkusVersion"))
implementation("io.quarkus:quarkus-resteasy-jackson")
implementation("io.quarkus:quarkus-rest-client")
implementation("io.quarkus:quarkus-kotlin")
implementation("io.quarkus:quarkus-config-yaml")
testImplementation("io.quarkus:quarkus-junit5")
...
}
Информация по поводу импорта Maven BOM доступна в документации Gradle [10].
Также требуется сделать некоторые Kotlin классы open (по умолчанию они final; больше информации по конфигурированию Gradle в Quarkus Kotlin guide [11]:
Листинг 3. build.gradle.kts [8]
allOpen {
annotation("javax.enterprise.context.ApplicationScoped")
}
Фреймворк поддерживает конфигурирование с использованием properties и YAML файлов
(подробнее в Quarkus config guide [12]). Конфигурационный файл располагается в папке resources и выглядит так:
Листинг 4. application.yaml [13]
quarkus:
http:
host: localhost
port: 8084
application-info:
name: quarkus-service
framework:
name: Quarkus
releaseYear: 2019
В этом фрагменте кода конфигурируются стандартные и кастомные параметры микросервиса. Последние могут быть прочитаны так:
Листинг 5. Чтение кастомных параметров приложения (исходный код [14])
import io.quarkus.arc.config.ConfigProperties
@ConfigProperties(prefix = "application-info")
class ApplicationInfoProperties {
lateinit var name: String
lateinit var framework: FrameworkConfiguration
class FrameworkConfiguration {
lateinit var name: String
lateinit var releaseYear: String
}
}
Перед тем как начать работать с кодом, надо отметить, что в исходном коде приложения на Quarkus нет main метода (хотя, возможно, появится [15]).
Внедрение @ConfigProperties бина с предыдущего листинга в другой бин выполняется с использованием аннотации @Inject:
Листинг 6. Внедрение @ConfigProperties бина (исходный код [16])
@ApplicationScoped
class ApplicationInfoService(
@Inject private val applicationInfoProperties: ApplicationInfoProperties,
@Inject private val serviceClient: ServiceClient
) {
...
}
ApplicationInfoService бин, аннотированный @ApplicationScoped, в свою очередь, внедряется так:
Листинг 7. Внедрение @ApplicationScoped бина (исходный код [17])
class ApplicationInfoResource(
@Inject private val applicationInfoService: ApplicationInfoService
)
Больше информации по Contexts and Dependency Injection в Quarkus CDI guide [18].
В REST контроллере нет ничего необычного для тех, кто работал с Spring Framework или Java EE:
Листинг 8. REST контроллер (исходный код [17])
@Path("/application-info")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class ApplicationInfoResource(
@Inject private val applicationInfoService: ApplicationInfoService
) {
@GET
fun get(@QueryParam("request-to") requestTo: String?): Response =
Response.ok(applicationInfoService.get(requestTo)).build()
@GET
@Path("/logo")
@Produces("image/png")
fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
}
Для работы в микросервисной архитектуре Quarkus сервис должен уметь выполнять запросы к другим сервисам. Т. к. все сервисы имеют одинаковый API, имеет смысл создать единый интерфейс для общего кода и пачку наследующих его REST клиентов:
Листинг 9. REST клиенты (исходный код [19])
@ApplicationScoped
@Path("/")
interface ExternalServiceClient {
@GET
@Path("/application-info")
@Produces("application/json")
fun getApplicationInfo(): ApplicationInfo
}
@RegisterRestClient(baseUri = "http://helidon-service")
interface HelidonServiceClient : ExternalServiceClient
@RegisterRestClient(baseUri = "http://ktor-service")
interface KtorServiceClient : ExternalServiceClient
@RegisterRestClient(baseUri = "http://micronaut-service")
interface MicronautServiceClient : ExternalServiceClient
@RegisterRestClient(baseUri = "http://quarkus-service")
interface QuarkusServiceClient : ExternalServiceClient
@RegisterRestClient(baseUri = "http://spring-boot-service")
interface SpringBootServiceClient : ExternalServiceClient
Как видите, создание REST клиента к другим сервисам представляет собой всего лишь создание интерфейса с соответствующими JAX-RS и MicroProfile аннотациями.
Как было видно в предыдущем разделе, значениями параметра baseUri являются названия сервисов. Но пока что в Quarkus нет встроенной поддержки Service Discovery (Eureka [20]) или она не работает (Consul [21]), т. к. фреймворк в перую очередь направлен на работу в облачных средах. Поэтому паттерн Service Discovery реализован с использованием библиотеки Consul Client for Java [22].
Клиент к Consul включает два метода, register и getServiceInstance (использующий алгоритм Round-robin):
Листинг 10. Клиент к Consul (исходный код [23])
@ApplicationScoped
class ConsulClient(
@ConfigProperty(name = "application-info.name")
private val serviceName: String,
@ConfigProperty(name = "quarkus.http.port")
private val port: Int
) {
private val consulUrl = "http://localhost:8500"
private val consulClient by lazy {
Consul.builder().withUrl(consulUrl).build()
}
private var serviceInstanceIndex: Int = 0
fun register() {
consulClient.agentClient().register(createConsulRegistration())
}
fun getServiceInstance(serviceName: String): Service {
val serviceInstances = consulClient.healthClient().getHealthyServiceInstances(serviceName).response
val selectedInstance = serviceInstances[serviceInstanceIndex]
serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
return selectedInstance.service
}
private fun createConsulRegistration() = ImmutableRegistration.builder()
.id("$serviceName-$port")
.name(serviceName)
.address("localhost")
.port(port)
.build()
}
Сначала надо зарегистрировать приложение:
Листинг 11. Регистрация в Consul (исходный код [24])
@ApplicationScoped
class ConsulRegistrationBean(
@Inject private val consulClient: ConsulClient
) {
fun onStart(@Observes event: StartupEvent) {
consulClient.register()
}
}
Далее требуется преобразовать названия сервисов в реальное расположение. Для этого используется класс, расширяющий ClientRequestFilter и аннотированный @Provider:
Листинг 12. Фильтр для работы с Service Discovery (исходный код [25])
@Provider
@ApplicationScoped
class ConsulFilter(
@Inject private val consulClient: ConsulClient
) : ClientRequestFilter {
override fun filter(requestContext: ClientRequestContext) {
val serviceName = requestContext.uri.host
val serviceInstance = consulClient.getServiceInstance(serviceName)
val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
.setHost(serviceInstance.address)
.setPort(serviceInstance.port)
.build()
requestContext.uri = newUri
}
}
В фильтре всего лишь осуществляется замена URI объекта requestContext на расположение сервиса, полученное от клиента к Consul.
Тесты для двух методов API реализованы с использованием библиотеки REST Assured:
Листинг 13. Тесты (исходный код [26])
@QuarkusTest
class QuarkusServiceApplicationTest {
@Test
fun testGet() {
given()
.`when`().get("/application-info")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("name") { `is`("quarkus-service") }
.body("framework.name") { `is`("Quarkus") }
.body("framework.releaseYear") { `is`(2019) }
}
@Test
fun testGetLogo() {
given()
.`when`().get("/application-info/logo")
.then()
.statusCode(200)
.contentType("image/png")
.body(`is`(notNullValue()))
}
}
Во время тестирования нет необходимости регистрировать приложение в Consul, поэтому в исходном коде проекта рядом с тестом находится ConsulClientMock, расширяющий ConsulClient:
Листинг 14. Мок для ConsulClient (исходный код [27])
@Mock
@ApplicationScoped
class ConsulClientMock : ConsulClient("", 0) {
// do nothing
override fun register() {
}
}
Во время Gradle задачи build вызывается задача quarkusBuild. По умолчанию она генерирует runner JAR и папку lib, где находятся зависимости. Для создания uber-JAR задача quarkusBuild долна быть сконфигурирована следующим образом:
Листинг 15. Настройка генерации uber-JAR (исходный код [28])
tasks {
withType<QuarkusBuild> {
isUberJar = true
}
}
Для сборки выполните в корне проекта ./gradlew clean build.
Перед запуском микросервиса надо стартовать Consul (описано в основной статье [29]).
Микросервис можно запустить, используя:
quarkusDev./gradlew :quarkus-service:quarkusDevjava -jar quarkus-service/build/quarkus-service-1.0.0-runner.jarТеперь можно использовать REST API, например, выполните следующий запрос:
GET http://localhost:8084/application-info
Результатом будет:
Листинг 16. Результат вызова API
{
"name": "quarkus-service",
"framework": {
"name": "Quarkus",
"releaseYear": 2019
},
"requestedService": null
}
Фреймворк обеспечивает совместимость с несколькими технологиями Spring: DI [30], Web [31], Security [32], Data JPA [33].
В статье был рассмотрен пример создания простого REST сервиса на Quarkus с использованием Kotlin и Gradle. В основной статье [34] вы можете видеть, что полученное приложение имеет сопоставимые параметры с приложениями на других современных JVM фреймворках. Таким образом у Quarkus есть серьёзные конкуренты, такие как Helidon MicroProfile, Micronaut и Spring Boot (если речь идёт о fullstack фреймворках). Поэтому, думаю, нас ждёт интересное развитие событий, которое будет полезно для всей экосистемы Java.
P.S. Спасибо vladimirsitnikov [37] за помощь в подготовке статьи.
Автор: not_bad
Источник [38]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/343578
Ссылки в тексте:
[1] предыдущей статье: https://romankudryashov.com/blog/2020/01/heterogeneous-microservices/
[2] Quarkus: https://quarkus.io/
[3] GitHub: https://github.com/rkudryashov/heterogeneous-microservices/tree/master/quarkus-service
[4] Consul: https://www.consul.io
[5] web starter: https://code.quarkus.io
[6] Maven: https://quarkus.io/guides/getting-started
[7] Gradle: https://quarkus.io/guides/gradle-tooling
[8] build.gradle.kts: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/build.gradle.kts
[9] settings.gradle.kts: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/settings.gradle.kts
[10] документации Gradle: https://docs.gradle.org/current/userguide/platforms.html#sub:bom_import
[11] Quarkus Kotlin guide: https://quarkus.io/guides/kotlin#important-gradle-configuration-points
[12] Quarkus config guide: https://quarkus.io/guides/config
[13] application.yaml: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/resources/application.yaml
[14] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/config/ApplicationInfoProperties.kt
[15] появится: https://github.com/quarkusio/quarkus/issues/6499
[16] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/service/ApplicationInfoService.kt
[17] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/web/ApplicationInfoResource.kt
[18] Quarkus CDI guide: https://quarkus.io/guides/cdi-reference
[19] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/service/ExternalServiceClients.kt
[20] Eureka: https://github.com/quarkusio/quarkus/issues/2052
[21] Consul: https://github.com/quarkusio/quarkus/issues/5812
[22] Consul Client for Java: https://github.com/rickfast/consul-client
[23] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulClient.kt
[24] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulRegistrationBean.kt
[25] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/main/kotlin/io/heterogeneousmicroservices/quarkusservice/servicediscovery/ConsulFilter.kt
[26] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/test/kotlin/io/heterogeneousmicroservices/quarkusservice/QuarkusServiceApplicationTest.kt
[27] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/quarkus-service/src/test/kotlin/io/heterogeneousmicroservices/quarkusservice/ConsulClientMock.kt
[28] исходный код: https://github.com/rkudryashov/heterogeneous-microservices/blob/master/build.gradle.kts
[29] основной статье: https://romankudryashov.com/blog/2020/01/heterogeneous-microservices/#_launch
[30] DI: https://quarkus.io/guides/spring-di
[31] Web: https://quarkus.io/guides/spring-web
[32] Security: https://quarkus.io/guides/spring-security
[33] Data JPA: https://quarkus.io/guides/spring-data-jpa
[34] основной статье: https://romankudryashov.com/blog/2020/01/heterogeneous-microservices/#_comparison_of_applications_parameters
[35] Quarkus — Get Started: https://quarkus.io/get-started/
[36] Quarkus for Spring Developers: https://quarkus.io/blog/quarkus-for-spring-developers/
[37] vladimirsitnikov: https://habr.com/en/users/vladimirsitnikov/
[38] Источник: https://habr.com/ru/post/484320/?utm_campaign=484320&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.