Предисловие
В предыдущей статье были приведены краткие пояснения по созданию микросервисов на современных средах JVM и их сравнение. Теперь пришло время познакомиться с наиболее недавно появившейся платформой: Quarkus . Я опишу процесс создания микросервиса с использованием упомянутых технологий и в соответствии с требованиями, указанными в основной статье.
Вам также может понравиться:
Создать Java REST API с помощью Quarkus
Этот микросервис станет частью следующей микросервисной архитектуры:
Как обычно, исходный код проекта доступен на GitHub .
Предпосылки
-
JDK 13.
-
Консул .
Создание приложения с нуля
Для создания нового проекта вы можете использовать веб-стартер или Maven (для генерации проектов Maven или Gradle ). Стоит отметить, что фреймворк поддерживает языки Java, Kotlin и Scala .
зависимости
В этом проекте Gradle Kotlin DSL используется как инструмент для сборки. Скрипт сборки должен содержать:
Плагины
Листинг 1. build.gradle.kts
Котлин
1
plugins {
2
kotlin("jvm")
3
kotlin("plugin.allopen")
4
id("io.quarkus")
5
}
Разрешение версий плагинов выполняется в settings.gradle.kts
.
зависимости
Листинг 2.
build.gradle.kts
Котлин
xxxxxxxxxx
1
dependencies {
2
...
4
implementation(enforcedPlatform("io.quarkus:quarkus-bom:$quarkusVersion"))
6
implementation("io.quarkus:quarkus-resteasy-jackson")
8
implementation("io.quarkus:quarkus-rest-client")
10
implementation("io.quarkus:quarkus-kotlin")
12
implementation("io.quarkus:quarkus-config-yaml")
14
testImplementation("io.quarkus:quarkus-junit5")
16
...
18
}
Подробнее об импорте спецификаций Maven см. В
документации Gradle .
Кроме того, необходимо сделать открытыми некоторые классы Kotlin (по умолчанию они являются окончательными; более подробную информацию о конфигурации Gradle см. В руководстве Quarkus Kotlin ):
Листинг 3.
build.gradle.kts .
Котлин
xxxxxxxxxx
1
allOpen {
2
annotation("javax.enterprise.context.ApplicationScoped")
3
}
конфигурация
Каркас поддерживает настройку через свойства или файлы YAML (более подробно в руководстве по настройке Quarkus ).
Файл конфигурации находится в resources
папке и выглядит так:
Листинг 4.
application.yaml .
YAML
xxxxxxxxxx
1
quarkus
2
http
3
host localhost
4
port8084
5
application-info
7
name quarkus-service
8
framework
9
name Quarkus
10
release-year2019
Здесь определены стандартные свойства приложения, а также пользовательские. Последнее можно прочитать следующим образом:
Листинг 5. Чтение свойств (
исходный код ).
Котлин
xxxxxxxxxx
1
import io.quarkus.arc.config.ConfigProperties
2
@ConfigProperties(prefix = "application-info")
4
class ApplicationInfoProperties {
5
lateinit var name: String
7
lateinit var framework: FrameworkConfiguration
9
class FrameworkConfiguration {
11
lateinit var name: String
12
lateinit var releaseYear: String
13
}
14
}
Фасоль
Прежде чем мы начнем с части кода, следует отметить, что в исходном коде вашего приложения Quarkus нет метода main, но, возможно, когда-нибудь это произойдет .
Инъекция @ConfigProperties
bean-компонента из предыдущего списка в другой bean-компонент выполняется с помощью @Inject
аннотации:
Листинг 6. Инъекция
@ConfigProperties
bean-компонента (
исходный код )
Котлин
xxxxxxxxxx
1
@ApplicationScoped
2
class ApplicationInfoService(
3
@Inject private val applicationInfoProperties: ApplicationInfoProperties,
4
@Inject private val serviceClient: ServiceClient
5
) {
6
...
7
}
ApplicationInfoService
bean-компонент с аннотацией @ApplicationScoped
затем может быть введен таким образом:
Листинг 7. Инъекция
@ApplicationScoped
bean-компонента (
исходный код )
Котлин
xxxxxxxxxx
1
class ApplicationInfoResource(
2
@Inject private val applicationInfoService: ApplicationInfoService
3
)
Подробнее о контекстах и внедрении зависимостей в руководстве Quarkus CDI .
Конечные точки REST
Контроллер REST выглядит очень типично для тех, кто знаком с Spring или Java EE:
Листинг 8. Контроллер REST (
исходный код ).
Котлин
xxxxxxxxxx
1
@Path("/application-info")
2
@Produces(MediaType.APPLICATION_JSON)
3
@Consumes(MediaType.APPLICATION_JSON)
4
class ApplicationInfoResource(
5
@Inject private val applicationInfoService: ApplicationInfoService
6
) {
7
@GET
9
fun get(@QueryParam("request-to") requestTo: String?): Response =
10
Response.ok(applicationInfoService.get(requestTo)).build()
11
@GET
13
@Path("/logo")
14
@Produces("image/png")
15
fun logo(): Response = Response.ok(applicationInfoService.getLogo()).build()
16
}
REST Client
Для работы в микросервисной архитектуре сервис Quarkus должен иметь возможность выполнять запросы к другим сервисам. Поскольку каждый сервис имеет один и тот же API, стоит создать единый интерфейс для общего кода, а затем несколько клиентов REST, расширяющих этот интерфейс:
Листинг 9. REST-клиенты (
исходный код ).
Котлин
xxxxxxxxxx
1
@ApplicationScoped
2
@Path("/")
3
interface ExternalServiceClient {
4
@GET
5
@Path("/application-info")
6
@Produces("application/json")
7
fun getApplicationInfo(): ApplicationInfo
8
}
9
@RegisterRestClient(baseUri = "http://helidon-service")
11
interface HelidonServiceClient : ExternalServiceClient
12
@RegisterRestClient(baseUri = "http://ktor-service")
14
interface KtorServiceClient : ExternalServiceClient
15
@RegisterRestClient(baseUri = "http://micronaut-service")
17
interface MicronautServiceClient : ExternalServiceClient
18
@RegisterRestClient(baseUri = "http://quarkus-service")
20
interface QuarkusServiceClient : ExternalServiceClient
21
@RegisterRestClient(baseUri = "http://spring-boot-service")
23
interface SpringBootServiceClient : ExternalServiceClient
Как видите, создание REST-клиентов для других сервисов так же просто, как создание интерфейса с использованием соответствующих аннотаций JAX-RS и MicroProfile.
Сервис Discovery
Как вы видели в предыдущем разделе, для baseUri
сервисов параметров используются имена. Но сейчас нет встроенной поддержки Service Discovery ( Eureka ) или она не работает должным образом ( Consul ), потому что платформа в основном предназначена для облачных сред. Я реализовал Service Discovery с помощью клиента Consul для библиотеки Java . Консул-клиент включает в себя две необходимые функции register
и getServiceInstance
(который использует алгоритм циклического перебора):
Листинг 10. Консул-клиент (
исходный код ).
Котлин
xxxxxxxxxx
1
@ApplicationScoped
2
class ConsulClient(
3
@ConfigProperty(name = "application-info.name")
4
private val serviceName: String,
5
@ConfigProperty(name = "quarkus.http.port")
6
private val port: Int
7
) {
8
private val consulUrl = "http://localhost:8500"
10
private val consulClient by lazy {
11
Consul.builder().withUrl(consulUrl).build()
12
}
13
private var serviceInstanceIndex: Int = 0
14
fun register() {
16
consulClient.agentClient().register(createConsulRegistration())
17
}
18
fun getServiceInstance(serviceName: String): Service {
20
val serviceInstances = consulClient.healthClient().getHealthyServiceInstances(serviceName).response
21
val selectedInstance = serviceInstances[serviceInstanceIndex]
22
serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size
23
return selectedInstance.service
24
}
25
private fun createConsulRegistration() = ImmutableRegistration.builder()
27
.id("$serviceName-$port")
28
.name(serviceName)
29
.address("localhost")
30
.port(port)
31
.build()
32
}
Для начала необходимо зарегистрировать приложение:
Листинг 11. Регистрация в Консуле (
исходный код ).
Котлин
xxxxxxxxxx
1
@ApplicationScoped
2
class ConsulRegistrationBean(
3
@Inject private val consulClient: ConsulClient
4
) {
5
fun onStart(@Observes event: StartupEvent) {
7
consulClient.register()
8
}
9
}
Затем необходимо разрешить имена служб в их конкретном месте. Для этого был создан класс, который расширяется ClientRequestFilter
и аннотируется @Provider
:
Листинг 12. Фильтр для работы с Service Discovery (
исходный код ).
Котлин
xxxxxxxxxx
1
@Provider
2
@ApplicationScoped
3
class ConsulFilter(
4
@Inject private val consulClient: ConsulClient
5
) : ClientRequestFilter {
6
override fun filter(requestContext: ClientRequestContext) {
8
val serviceName = requestContext.uri.host
9
val serviceInstance = consulClient.getServiceInstance(serviceName)
10
val newUri: URI = URIBuilder(URI.create(requestContext.uri.toString()))
11
.setHost(serviceInstance.address)
12
.setPort(serviceInstance.port)
13
.build()
14
requestContext.uri = newUri
16
}
17
}
Разрешение реализуется просто путем замены URI requestContext
объекта на местоположение сервиса, полученное от клиента Консула.
тестирование
Тесты для обеих конечных точек API реализованы с использованием библиотеки REST Assured:
Листинг 13. Тесты (
исходный код ).
Котлин
xxxxxxxxxx
1
@QuarkusTest
2
class QuarkusServiceApplicationTest {
3
@Test
5
fun testGet() {
6
given()
7
.`when`().get("/application-info")
8
.then()
9
.statusCode(200)
10
.contentType(ContentType.JSON)
11
.body("name") { `is`("quarkus-service") }
12
.body("framework.name") { `is`("Quarkus") }
13
.body("framework.releaseYear") { `is`(2019) }
14
}
15
@Test
17
fun testGetLogo() {
18
given()
19
.`when`().get("/application-info/logo")
20
.then()
21
.statusCode(200)
22
.contentType("image/png")
23
.body(`is`(notNullValue()))
24
}
25
}
Во время тестирования нет необходимости регистрировать приложение в Консуле, поэтому я просто добавлю ConsulClientMock
факт расширения действительный ConsulClient
рядом с классом тестирования:
Листинг 14. Макет для
ConsulClient
(
исходный код )
Котлин
xxxxxxxxxx
1
@Mock
2
@ApplicationScoped
3
class ConsulClientMock : ConsulClient("", 0) {
4
// do nothing
6
override fun register() {
7
}
8
}
Строительство
Во время build
Gradle задание quarkusBuild
вызывается. По умолчанию он генерирует JAR и директорию бегунаlib
со всеми зависимостями. Для создания quarkusBuild
задачи Uber-JAR-артефакта необходимо выполнить следующие настройки:
Листинг 15. Настройка генерации uber-JAR (
исходный код ).
Котлин
xxxxxxxxxx
1
tasks {
2
withType<QuarkusBuild> {
3
isUberJar = true
4
}
5
}
Для сборки проекта запустите ./gradlew clean build
в корневой папке проекта.
запуск
Перед запуском микросервиса необходимо запустить Консул (описано в основной статье ).
Вы можете запустить микросервисы:
-
Используя
quarkusDev
задачу Gradle.Выполнить в корневой папке проекта:
./gradlew :quarkus-service:quarkusDev
Или вызовите задачу из IDE.
-
Использование Uber-JAR.
Выполнить в корневой папке проекта:
java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar
Теперь вы можете использовать REST API, например, выполнить следующий запрос:
GET http://localhost:8084/application-info
Он вернется:
Листинг 16. Ответ API.
JSON
xxxxxxxxxx
1
{
2
"name": "quarkus-service",
3
"framework": {
4
"name": "Quarkus",
5
"releaseYear": 2019
6
},
7
"requestedService": null
8
}
Весна Совместимость
Фреймворк обеспечивает уровни совместимости для нескольких технологий Spring: DI , Web , Security , Data JPA .
Заключение
В этой статье мы увидели, как реализовать простой REST-сервис на Quarkus с использованием Kotlin и Gradle. Если вы посмотрите основную статью , то увидите, что созданное приложение имеет параметры, сопоставимые с приложениями в других новых платформах JVM. У фреймворка есть серьезные конкуренты, такие как Helidon MicroProfile, Micronaut и Spring Boot (если мы говорим о фреймворках с полным стеком). Поэтому я думаю, что нас ждет интересная разработка событий, которая будет полезна для всей экосистемы Java.
PS Спасибо VLSI за помощь в этой статье.