
引言
在上一篇文章中,我们简要描述了在现代JVM框架上创建微服务的过程以及它们的比较。 本文将在最近的Quarkus中更详细地研究,该示例以使用提到的技术并根据主要文章中指定的需求创建微服务为例。 生成的应用程序将成为以下微服务体系结构的一部分:

与往常一样,该项目的源代码可在GitHub上获得 。
开始进行项目之前,必须先安装以下软件:
创建一个新项目
要生成新项目,请使用Web Starter或Maven(创建Maven项目或Gradle项目)。 值得注意的是,该框架支持Java,Kotlin和Scala语言。
成瘾
在此项目中,Gradle Kotlin DSL被用作构建系统。 构建脚本应包含:
外挂程式
清单1. build.gradle.kts
plugins { kotlin("jvm") kotlin("plugin.allopen") id("io.quarkus") }
插件版本解析在settings.gradle.kts
完成。
成瘾
清单2. build.gradle.kts
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") ... }
Gradle文档中提供了有关导入Maven BOM的信息。
您还需要open
一些Kotlin类(默认情况下它们是final
的;有关更多信息,请参见Quarkus Kotlin指南中的Gradle配置:
清单3. build.gradle.kts
allOpen { annotation("javax.enterprise.context.ApplicationScoped") }
构型
该框架支持使用属性和YAML文件进行配置
(更多在Quarkus配置指南中 )。 配置文件位于resources
文件夹中,如下所示:
清单4. application.yaml
quarkus: http: host: localhost port: 8084 application-info: name: quarkus-service framework: name: Quarkus release-year: 2019
在此代码段中,配置了微服务的标准和自定义参数。 后者可以这样读取:
清单5.读取自定义应用程序参数( 源代码 )
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应用程序的源代码中没有主要方法(尽管它可能会出现 )。
使用@Inject
批注将上一个清单中@ConfigProperties
bean @ConfigProperties
到另一个bean中:
清单6.实现@ConfigProperties
bean( 源 )
@ApplicationScoped class ApplicationInfoService( @Inject private val applicationInfoProperties: ApplicationInfoProperties, @Inject private val serviceClient: ServiceClient ) { ... }
由@ApplicationScoped
注释ApplicationInfoService
bean依次实现如下:
清单7.部署@ApplicationScoped
bean( 源 )
class ApplicationInfoResource( @Inject private val applicationInfoService: ApplicationInfoService )
Quarkus CDI指南中有关上下文和依赖注入的更多信息。
REST控制器
对于使用Spring Framework或Java EE的人员来说,REST控制器中没有什么异常之处:
清单8. REST控制器( 源 )
@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() }
REST客户端
为了在Quarkus微服务架构中工作,服务必须能够满足对其他服务的请求。 由于所有服务都具有相同的API,因此有必要为通用代码和继承它的REST客户端创建单个接口:
清单9. REST客户端( 源代码 )
@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
参数baseUri
是服务的名称。 但是到目前为止,Quarkus还没有对Service Discovery( Eureka )的内置支持,或者它不起作用( Consul ),因为该框架主要针对在云环境中工作。 因此,使用Consul Client for Java库实现服务发现模式。
Consul的客户端包括两个方法, register
和getServiceInstance
(使用Round-robin算法):
清单10.客户端到领事( 源 )
@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注册( 源 )
@ApplicationScoped class ConsulRegistrationBean( @Inject private val consulClient: ConsulClient ) { fun onStart(@Observes event: StartupEvent) { consulClient.register() } }
接下来,您需要将服务名称转换为真实位置。 为此,请使用扩展ClientRequestFilter
和带注释的@Provider
:
清单12.使用服务发现的过滤器( 源 )
@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 } }
过滤器仅将requestContext
对象的URI替换为从客户端接收到Consul的服务位置。
测试中
使用REST Assured库实现了两种API方法的测试:
清单13.测试( 源 )
@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
模拟( 源 )
@Mock @ApplicationScoped class ConsulClientMock : ConsulClient("", 0) {
组装方式
在Gradle build
任务中, quarkusBuild
任务quarkusBuild
。 默认情况下,它将生成运行程序JAR和依赖项所在的lib
文件夹。 要创建uber-JAR, quarkusBuild
按以下方式配置quarkusBuild
任务:
清单15.配置uber-JAR生成( 源 )
tasks { withType<QuarkusBuild> { isUberJar = true } }
要进行构建,请在项目的根目录下运行./gradlew clean build
。
发射
在启动微服务之前,您必须启动Consul(在主文章中介绍 )。
可以使用以下方法启动微服务:
- Gradle
quarkusDev
任务
在项目的根目录运行:
./gradlew :quarkus-service:quarkusDev
或在IDE中运行任务 - 乌伯尔
在项目的根目录运行:
java -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 , Web , 安全性 , Data JPA 。
结论
本文研究了使用Kotlin和Gradle在Quarkus上创建简单REST服务的示例。 在主要文章中,您可以看到生成的应用程序具有与其他现代JVM框架上的应用程序相当的参数。 因此,Quarkus拥有严格的竞争对手,例如Helidon MicroProfile,Micronaut和Spring Boot(如果我们谈论的是全栈框架)。 因此,我认为我们正在等待事件的有趣发展,这将对整个Java生态系统有用。
有用的链接
PS感谢vladimirsitnikov在撰写本文时提供的帮助。