Erstellen eines Microservices für Quarkus, Kotlin und Gradle


Einleitung


Im vorherigen Artikel haben wir kurz den Prozess der Erstellung eines Mikroservices auf modernen JVM-Frameworks sowie deren Vergleich beschrieben. Dieser Artikel wird im kürzlich veröffentlichten Quarkus anhand des Beispiels der Erstellung eines Mikrodienstes mit den genannten Technologien und gemäß den im Hauptartikel angegebenen Anforderungen näher erläutert. Die resultierende Anwendung wird Teil der folgenden Microservice-Architektur:


Zielarchitektur


Der Quellcode des Projekts ist wie gewohnt auf GitHub verfügbar.


Bevor Sie mit der Arbeit an einem Projekt beginnen können, muss Folgendes installiert sein:



Erstellen Sie ein neues Projekt


Verwenden Sie zum Generieren eines neuen Projekts Web Starter oder Maven (zum Erstellen eines Maven- Projekts oder Gradle- Projekts). Es ist erwähnenswert, dass das Framework die Sprachen Java, Kotlin und Scala unterstützt.


Sucht


In diesem Projekt wird Gradle Kotlin DSL als Build-System verwendet. Das Build-Skript sollte enthalten:


  • Plugins
    Listing 1. build.gradle.kts


    plugins { kotlin("jvm") kotlin("plugin.allopen") id("io.quarkus") } 

    Die Auflösung der Plugin-Version erfolgt in settings.gradle.kts .


  • Sucht
    Listing 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") ... } 

    Informationen zum Importieren von Maven-Stücklisten finden Sie in der Gradle-Dokumentation .



Sie müssen auch einige Kotlin-Klassen open (diese sind standardmäßig final . Weitere Informationen zur Konfiguration von Gradle finden Sie im Quarkus Kotlin-Handbuch :


Listing 3. build.gradle.kts


 allOpen { annotation("javax.enterprise.context.ApplicationScoped") } 

Konfiguration


Das Framework unterstützt die Konfiguration mithilfe von Eigenschaften und YAML-Dateien
(mehr im Quarkus Konfigurationshandbuch ). Die Konfigurationsdatei befindet sich im resources und sieht folgendermaßen aus:


Listing 4. application.yaml


 quarkus: http: host: localhost port: 8084 application-info: name: quarkus-service framework: name: Quarkus release-year: 2019 

In diesem Codefragment werden die Standard- und benutzerdefinierten Parameter des Mikrodienstes konfiguriert. Letzteres kann so gelesen werden:


Listing 5. Benutzerdefinierte Anwendungsparameter lesen ( Quellcode )


 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 } } 

Bins


Bevor Sie mit der Arbeit mit dem Code beginnen, sollten Sie beachten, dass es im Quellcode der Quarkus-Anwendung keine Hauptmethode gibt (obwohl diese möglicherweise angezeigt wird).


@ConfigProperties Bean aus der vorherigen Auflistung in eine andere Bean erfolgt mit der Annotation @Inject :


Listing 6. Implementierung der @ConfigProperties Bean ( Quelle )


 @ApplicationScoped class ApplicationInfoService( @Inject private val applicationInfoProperties: ApplicationInfoProperties, @Inject private val serviceClient: ServiceClient ) { ... } 

ApplicationInfoService von @ApplicationScoped annotierte ApplicationInfoService Bean wird wiederum wie folgt implementiert:


Listing 7. Bereitstellen der @ApplicationScoped Bean ( Quelle )


 class ApplicationInfoResource( @Inject private val applicationInfoService: ApplicationInfoService ) 

Weitere Informationen zu Contexts und Dependency Injection finden Sie im Quarkus CDI-Handbuch .


REST-Controller


Ein REST-Controller ist nichts Ungewöhnliches für Benutzer, die mit Spring Framework oder Java EE arbeiten:


Listing 8. REST-Controller ( Quelle )


 @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-Client


Um in der Quarkus Microservice-Architektur zu arbeiten, muss ein Service in der Lage sein, Anforderungen für andere Services zu erfüllen. Da alle Services dieselbe API haben, ist es sinnvoll, eine einzige Schnittstelle für gemeinsamen Code und eine Reihe von REST-Clients zu erstellen, die diesen erben:


Listing 9. REST-Clients ( Quellcode )


 @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 

Wie Sie sehen, wird beim Erstellen eines REST-Clients für andere Services lediglich eine Schnittstelle mit den entsprechenden JAX-RS- und MicroProfile-Annotationen erstellt.


Service-Erkennung


Wie Sie im vorherigen Abschnitt gesehen haben, sind die baseUri Parameterwerte die Namen der Services. Bisher hat Quarkus jedoch keine integrierte Unterstützung für Service Discovery ( Eureka ) oder funktioniert nicht ( Consul ), da das Framework in erster Linie auf die Arbeit in Cloud-Umgebungen ausgerichtet ist. Daher wird das Service Discovery-Muster mithilfe der Consul Client for Java- Bibliothek implementiert.


Der Client für Consul enthält zwei Methoden, register und getServiceInstance (unter Verwendung des Round-Robin-Algorithmus):


Listing 10. Kunde an Konsul ( Quelle )


 @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() } 

Zuerst müssen Sie die Anwendung registrieren:


Listing 11. Registrierung bei Consul ( Quelle )


 @ApplicationScoped class ConsulRegistrationBean( @Inject private val consulClient: ConsulClient ) { fun onStart(@Observes event: StartupEvent) { consulClient.register() } } 

Als Nächstes müssen Sie die Namen von Diensten in einen realen Ort konvertieren. Verwenden Sie dazu eine Klasse, die ClientRequestFilter und @Provider annotiert:


Listing 12. Filter für die Arbeit mit Service Discovery ( Quelle )


 @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 } } 

Der Filter ersetzt lediglich den URI des requestContext Objekts durch den vom Client an Consul empfangenen Dienstort.


Testen


Tests für zwei API-Methoden werden mithilfe der Bibliothek REST Assured implementiert:


Listing 13. Tests ( Quelle )


 @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())) } } 

Während des Tests muss die Anwendung nicht bei Consul registriert werden. Im Quellcode des Projekts neben dem Test befindet sich daher ConsulClientMock , das ConsulClient :


Listing 14. Mock for ConsulClient ( Quelle )


 @Mock @ApplicationScoped class ConsulClientMock : ConsulClient("", 0) { // do nothing override fun register() { } } 

Versammlung


Während der Gradle- build Task wird die quarkusBuild Task quarkusBuild . Standardmäßig werden eine Runner- JAR und ein lib Ordner generiert, in dem sich die Abhängigkeiten befinden. Um eine Uber-JAR zu erstellen, muss die quarkusBuild Task wie folgt konfiguriert werden:


Listing 15. Konfiguration der Uber-JAR-Generierung ( Quelle )


 tasks { withType<QuarkusBuild> { isUberJar = true } } 

Führen Sie zum ./gradlew clean build im Stammverzeichnis des Projekts aus.


Starten Sie


Bevor Sie den Microservice starten, müssen Sie Consul starten (beschrieben im Hauptartikel ).


Microservice kann gestartet werden mit:


  • Gradle quarkusDev Aufgabe
    Führen Sie im Stammverzeichnis des Projekts Folgendes aus:
    ./gradlew :quarkus-service:quarkusDev
    oder führen Sie die Task in der IDE aus
  • Überglas
    Führen Sie im Stammverzeichnis des Projekts Folgendes aus:
    java -jar quarkus-service/build/quarkus-service-1.0.0-runner.jar

Jetzt können Sie die REST-API verwenden und beispielsweise die folgende Abfrage ausführen:


GET http://localhost:8084/application-info


Das Ergebnis wird sein:


Listing 16. Das Ergebnis des API-Aufrufs


 { "name": "quarkus-service", "framework": { "name": "Quarkus", "releaseYear": 2019 }, "requestedService": null } 

Federkompatibilität


Das Framework bietet Kompatibilität mit verschiedenen Spring-Technologien: DI , Web , Sicherheit , Daten-JPA .


Fazit


Der Artikel untersuchte ein Beispiel für die Erstellung eines einfachen REST-Service unter Quarkus mit Kotlin und Gradle. Im Hauptartikel sehen Sie, dass die resultierende Anwendung vergleichbare Parameter wie Anwendungen auf anderen modernen JVM-Frameworks aufweist. Somit hat Quarkus ernsthafte Konkurrenten wie Helidon MicroProfile, Micronaut und Spring Boot (wenn es sich um Fullstack-Frameworks handelt). Ich denke daher, dass wir auf eine interessante Entwicklung von Ereignissen warten, die für das gesamte Java-Ökosystem von Nutzen sein werden.


Nützliche Links



PS Danke vladimirsitnikov für die Hilfe bei der Vorbereitung des Artikels.

Source: https://habr.com/ru/post/de484320/


All Articles