
Derzeit mangelt es nicht an Frameworks für die Erstellung von Microservices in Java und Kotlin. Der Artikel beschreibt Folgendes:
Basierend darauf wurden vier Dienste erstellt, die über die HTTP-API mithilfe des mit
Consul implementierten Diensterkennungsmusters miteinander interagieren können. Somit bilden sie eine heterogene (auf Framework-Ebene) Mikroservice-Architektur (im Folgenden als ISA bezeichnet):

Definieren Sie eine Reihe von Anforderungen für jeden Dienst:
- Technologie-Stack:
- JDK 12;
- Kotlin
- Gradle (Kotlin DSL);
- Einheit 5.
- Funktionalität (HTTP API):
GET /application-info{?request-to=some-service-name}
Gibt einige grundlegende Informationen zum Microservice zurück (Name, Framework, Erscheinungsjahr des Frameworks). Wenn Sie den Namen eines der vier Mikrodienste im Parameter request-to
für die HTTP-API angeben, wird eine ähnliche Anforderung ausgeführt, die grundlegende Informationen zurückgibt.GET /application-info/logo
Gibt das Bild zurück.
- Implementierung:
- Einrichtung unter Verwendung der Konfigurationsdatei;
- Verwenden der Abhängigkeitsinjektion
- Tests, die die Funktionalität der HTTP-API überprüfen.
- ISA:
- Verwenden des Service Discovery-Musters (Registrieren bei Consul, Zugreifen auf die HTTP-API eines anderen Mikrodienstes anhand seines Namens mithilfe des Client-Lastausgleichs);
- Überglas-Artefaktbildung.
Als nächstes betrachten wir die Implementierung eines Mikrodienstes auf jedem der Frameworks und vergleichen die Parameter der empfangenen Anwendungen.
Helidon-Service
Das Entwicklungsframework wurde bei Oracle für den internen Gebrauch erstellt und anschließend zu Open Source. Es gibt zwei Entwicklungsmodelle, die auf diesem Framework basieren: Standard Edition (SE) und MicroProfile (MP). In beiden Fällen handelt es sich bei dem Dienst um ein reguläres Java SE-Programm. Erfahren Sie mehr über die Unterschiede auf
dieser Seite.
Kurz gesagt, Helidon MP ist eine der Eclipse
MicroProfile- Implementierungen, mit der viele APIs verwendet werden können, die Java EE-Entwicklern (z. B. JAX-RS, CDI) und neueren (Health Check, Metrics, Fault Tolerance) bisher bekannt waren usw.). In der Helidon SE-Variante orientierten sich die Entwickler am Prinzip „Keine Magie“, das sich insbesondere in weniger oder gar keinen Anmerkungen zum Erstellen der Anwendung äußert.
Helidon SE wurde für die Entwicklung von Mikroservices ausgewählt. Unter anderem fehlen Tools zum Implementieren der Abhängigkeitsinjektion, sodass
Koin zum Implementieren von Abhängigkeiten verwendet wird. Das Folgende ist eine Klasse, die die Hauptmethode enthält. Um Dependency Injection zu implementieren, erbt die Klasse von
KoinComponent . Koin wird zuerst
startServer()
, dann werden die erforderlichen Abhängigkeiten initialisiert und die Methode
startServer()
aufgerufen, wobei ein Objekt vom Typ
WebServer erstellt wird, an das zuvor die Anwendungskonfiguration und die Routing-Einstellungen übertragen wurden. Nach dem Start wird die Anwendung in Consul registriert:
object HelidonServiceApplication : KoinComponent { @JvmStatic fun main(args: Array<String>) { val startTime = System.currentTimeMillis() startKoin { modules(koinModule) } val applicationInfoService: ApplicationInfoService by inject() val consulClient: Consul by inject() val applicationInfoProperties: ApplicationInfoProperties by inject() val serviceName = applicationInfoProperties.name startServer(applicationInfoService, consulClient, serviceName, startTime) } } fun startServer( applicationInfoService: ApplicationInfoService, consulClient: Consul, serviceName: String, startTime: Long ): WebServer { val serverConfig = ServerConfiguration.create(Config.create().get("webserver")) val server: WebServer = WebServer .builder(createRouting(applicationInfoService)) .config(serverConfig) .build() server.start().thenAccept { ws -> val durationInMillis = System.currentTimeMillis() - startTime log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
Das Routing ist wie folgt konfiguriert:
private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder() .register(JacksonSupport.create()) .get("/application-info", Handler { req, res -> val requestTo: String? = req.queryParams() .first("request-to") .orElse(null) res .status(Http.ResponseStatus.create(200)) .send(applicationInfoService.get(requestTo)) }) .get("/application-info/logo", Handler { req, res -> res.headers().contentType(MediaType.create("image", "png")) res .status(Http.ResponseStatus.create(200)) .send(applicationInfoService.getLogo()) }) .error(Exception::class.java) { req, res, ex -> log.error("Exception:", ex) res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send() } .build()
Die Anwendung verwendet die Konfiguration im
HOCON- Format:
webserver { port: 8081 } application-info { name: "helidon-service" framework { name: "Helidon SE" release-year: 2019 } }
Es ist auch möglich, Dateien in den Formaten JSON, YAML und Eigenschaften für die Konfiguration zu verwenden (weitere Details
hier ).
Ktor Service
Das Framework ist in Kotlin geschrieben. Ein neues Projekt kann auf verschiedene Arten erstellt werden: mit dem Build-System
start.ktor.io oder dem Plug-In für IntelliJ IDEA (mehr
hier ).
Wie Helidon SE verfügt Ktor nicht über einen sofort einsatzbereiten DI. Daher werden Abhängigkeiten mithilfe von Koin implementiert, bevor der Server gestartet wird:
val koinModule = module { single { ApplicationInfoService(get(), get()) } single { ApplicationInfoProperties() } single { ServiceClient(get()) } single { Consul.builder().withUrl("http://localhost:8500").build() } } fun main(args: Array<String>) { startKoin { modules(koinModule) } val server = embeddedServer(Netty, commandLineEnvironment(args)) server.start(wait = true) }
Von der Anwendung benötigte Module sind in der Konfigurationsdatei angegeben (es ist möglich, nur das HOCON-Format zu verwenden; mehr zur Konfiguration des Ktor-Servers
hier ), deren Inhalt unten dargestellt wird:
ktor { deployment { host = localhost port = 8082 watch = [io.heterogeneousmicroservices.ktorservice] } application { modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module] } } application-info { name: "ktor-service" framework { name: "Ktor" release-year: 2018 }
Ktor und Koin verwenden den Begriff „Modul“, der unterschiedliche Bedeutungen hat. In Koin ist ein Modul ein Analogon zum Anwendungskontext im Spring Framework. Das Ktor-Modul ist eine benutzerdefinierte Funktion, die ein Objekt vom Typ
Anwendung akzeptiert und eine Pipeline konfigurieren, Funktionen festlegen, Routen registrieren und verarbeiten kann
Anfragen usw.:
fun Application.module() { val applicationInfoService: ApplicationInfoService by inject() if (!isTest()) { val consulClient: Consul by inject() registerInConsul(applicationInfoService.get(null).name, consulClient) } install(DefaultHeaders) install(Compression) install(CallLogging) install(ContentNegotiation) { jackson {} } routing { route("application-info") { get { val requestTo: String? = call.parameters["request-to"] call.respond(applicationInfoService.get(requestTo)) } static { resource("/logo", "logo.png") } } } }
In diesem Codefragment wird das Routing von Anforderungen konfiguriert, insbesondere die statische Ressource
logo.png
.
Der Ktor-Dienst kann Funktionen enthalten. Eine Funktion ist eine Funktionalität, die in eine Anforderungs-Antwort-
Pipeline eingebettet ist (
DefaultHeaders, Compression und andere im obigen Codebeispiel). Es ist möglich, eigene Funktionen zu implementieren. Der folgende Code implementiert beispielsweise das Service Discovery-Muster in Kombination mit dem Client-Lastausgleich basierend auf dem Round-Robin-Algorithmus:
class ConsulFeature(private val consulClient: Consul) { class Config { lateinit var consulClient: Consul } companion object Feature : HttpClientFeature<Config, ConsulFeature> { var serviceInstanceIndex: Int = 0 override val key = AttributeKey<ConsulFeature>("ConsulFeature") override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient) override fun install(feature: ConsulFeature, scope: HttpClient) { scope.requestPipeline.intercept(HttpRequestPipeline.Render) { val serviceName = context.url.host val serviceInstances = feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response val selectedInstance = serviceInstances[serviceInstanceIndex] context.url.apply { host = selectedInstance.service.address port = selectedInstance.service.port } serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size } } } }
Die Hauptlogik liegt in der
install
: Während der
Renderanforderungsphase (die vor der
Sendephase ausgeführt wird) wird zuerst der Name des aufgerufenen Dienstes bestimmt, dann wird eine Liste der Instanzen dieses Dienstes von
consulClient
angefordert, wonach die Instanz unter Verwendung des Round-Robin-Algorithmus bestimmt wird. Somit wird der folgende Aufruf möglich:
fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking { httpClient.get<ApplicationInfo>("http://$serviceName/application-info") }
Micronaut-Service
Micronaut wurde von den Entwicklern des
Grails- Frameworks entwickelt und ist von der Erfahrung der Gebäudetechnik mit Spring, Spring Boot und Grails inspiriert. Das Framework ist ein Polyglot, das Java, Kotlin und Groovy unterstützt.
Vielleicht gibt es Unterstützung für Scala. Die Abhängigkeitsinjektion wird in der Kompilierungsphase ausgeführt, was im Vergleich zu Spring Boot zu einem geringeren Speicherverbrauch und einem schnelleren Start der Anwendung führt.
Die Hauptklasse hat folgende Form:
object MicronautServiceApplication { @JvmStatic fun main(args: Array<String>) { Micronaut.build() .packages("io.heterogeneousmicroservices.micronautservice") .mainClass(MicronautServiceApplication.javaClass) .start() } }
Einige Komponenten einer Micronaut-basierten Anwendung ähneln ihren Gegenstücken in einer Spring Boot-Anwendung. Der Controller-Code lautet beispielsweise wie folgt:
@Controller( value = "/application-info", consumes = [MediaType.APPLICATION_JSON], produces = [MediaType.APPLICATION_JSON] ) class ApplicationInfoController( private val applicationInfoService: ApplicationInfoService ) { @Get fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo) @Get("/logo", produces = [MediaType.IMAGE_PNG]) fun getLogo(): ByteArray = applicationInfoService.getLogo() }
Die Kotlin-Unterstützung in Micronaut basiert auf dem
kapt- Compiler-
Plugin (weitere
Informationen hier ). Das Assembly-Skript ist wie folgt konfiguriert:
plugins { ... kotlin("kapt") ... } dependencies { kapt("io.micronaut:micronaut-inject-java") ... kaptTest("io.micronaut:micronaut-inject-java") ... }
Folgendes ist der Inhalt der Konfigurationsdatei:
micronaut: application: name: micronaut-service server: port: 8083 consul: client: registration: enabled: true application-info: name: ${micronaut.application.name} framework: name: Micronaut release-year: 2018
Die Microservice-Konfiguration ist auch mit JSON, Eigenschaften und Groovy-Dateiformaten möglich (weitere Details
hier ).
Spring Boot Service
Das Framework wurde erstellt, um die Entwicklung von Anwendungen mithilfe des Spring Framework-Ökosystems zu vereinfachen. Dies wird durch Autokonfigurationsmechanismen beim Verbinden von Bibliotheken erreicht. Das Folgende ist der Controller-Code:
@RestController @RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE]) class ApplicationInfoController( private val applicationInfoService: ApplicationInfoService ) { @GetMapping fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo) @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE]) fun getLogo(): ByteArray = applicationInfoService.getLogo() }
Der Microservice wird mit einer YAML-Datei konfiguriert:
spring: application: name: spring-boot-service server: port: 8084 application-info: name: ${spring.application.name} framework: name: Spring Boot release-year: 2014
Es ist auch möglich, Dateien im Eigenschaftenformat für die Konfiguration zu verwenden (weitere Details
hier ).
Starten
Das Projekt funktioniert unter JDK 12, obwohl es wahrscheinlich auch unter Version 11 funktioniert, müssen Sie nur den Parameter
jvmTarget
in den Assemblyskripten
jvmTarget
ändern:
withType<KotlinCompile> { kotlinOptions { jvmTarget = "12" ... } }
Bevor Sie Microservices starten, müssen Sie Consul
installieren und
den Agenten
starten - zum Beispiel wie
consul agent -dev
:
consul agent -dev
.
Das Starten von Microservices ist möglich von:
Nachdem Sie alle Microservices unter
http://localhost:8500/ui/dc1/services
Sie:

API-Tests
Die Ergebnisse des Testens der Helidon-Service-API sind als Beispiel angegeben:
GET http://localhost:8081/application-info
{ "name": "helidon-service", "framework": { "name": "Helidon SE", "releaseYear": 2019 }, "requestedService": null }
GET http://localhost:8081/application-info?request-to=ktor-service
{ "name": "helidon-service", "framework": { "name": "Helidon SE", "releaseYear": 2019 }, "requestedService": { "name": "ktor-service", "framework": { "name": "Ktor", "releaseYear": 2018 }, "requestedService": null } }
GET http://localhost:8081/application-info/logo
Gibt das Bild zurück.
Sie können eine beliebige Microservice-API mit
Postman (eine
Sammlung von Anforderungen), einem IntelliJ IDEA
HTTP-Client (eine
Sammlung von Anforderungen), einem Browser oder einem anderen Tool testen. Wenn Sie die ersten beiden Clients verwenden, müssen Sie den Port des aufgerufenen Mikrodienstes in der entsprechenden Variablen angeben (in Postman im
Sammlungsmenü -> Bearbeiten -> Variablen und im HTTP-Client in der in
dieser Datei angegebenen Umgebungsvariablen) und beim Testen von Methode 2). Die API muss auch den Namen des angeforderten "unter der Haube" -Mikroservices angeben. Die Antworten sind ähnlich wie oben angegeben.
Vergleich der Anwendungseinstellungen
Artefaktgröße
Um die Einfachheit der Konfiguration und Ausführung von Anwendungen in Assemblyskripten zu gewährleisten, wurden keine transitiven Abhängigkeiten ausgeschlossen. Daher übersteigt die Größe des Uber-JAR-Dienstes in Spring Boot die Größe von Analoga in anderen Frameworks erheblich (da bei Verwendung von Startern nicht nur die erforderlichen Abhängigkeiten importiert werden. Falls gewünscht, kann die Größe erheblich reduziert werden.
Startzeit
Die Startzeit jeder Anwendung ist inkonsistent und fällt in ein „Fenster“. Die folgende Tabelle zeigt die Startzeit des Artefakts ohne Angabe zusätzlicher Parameter:
Es ist erwähnenswert, dass Sie die Startzeit erheblich verkürzen können, wenn Sie die Spring Boot-Anwendung von unnötigen Abhängigkeiten „bereinigen“ und darauf achten, die Anwendung zum Starten zu konfigurieren (z. B. nur die erforderlichen Pakete scannen und die Initialisierung des verzögerten Bin verwenden).
Lasttest
Zum Testen wurden
Gatling und
ein Scala-
Skript verwendet. Der Lastgenerator und der zu testende Dienst wurden auf demselben Computer ausgeführt (Windows 10, ein Quad-Core-Prozessor mit 3,2 GHz, 24 GB RAM, SSD). Der Port dieses Dienstes wird im Scala-Skript angegeben.
Für jeden Microservice wird bestimmt:
- Die Mindestmenge an Heap-Speicher (
-Xmx
), die erforderlich ist, um einen funktionierenden (auf Anforderungen reagierenden) Mikroservice auszuführen - Mindest-Heap-Speicher erforderlich, um den Auslastungstest zu bestehen 50 Benutzer * 1000 Anforderungen
- Mindest-Heap-Speicher erforderlich, um den Auslastungstest zu bestehen 500 Benutzer * 1000 Anforderungen
Das Bestehen eines Auslastungstests bedeutet, dass der Microservice jederzeit auf alle Anforderungen reagiert hat.
Es ist erwähnenswert, dass alle Microservices den Netty HTTP-Server verwenden.
Fazit
Die Aufgabe - die Erstellung eines einfachen Dienstes mit HTTP-API und die Fähigkeit, in der ISA zu funktionieren - konnte auf allen fraglichen Frameworks abgeschlossen werden. Es ist Zeit, Bilanz zu ziehen und ihre Vor- und Nachteile zu berücksichtigen.
HelidonStandard Edition- Pluspunkte
- Anwendungseinstellungen
In jeder Hinsicht zeigten gute Ergebnisse; - "Keine Magie"
Das Framework begründete das von den Entwicklern angegebene Prinzip: Es war nur eine Anmerkung @JvmStatic
, um die Anwendung zu erstellen ( @JvmStatic
- für das Java-Kotlin-Interope).
- Nachteile
- Mikroframework
Einige für die industrielle Entwicklung erforderliche Komponenten fehlen sofort, z. B. die Abhängigkeitsinjektion und die Implementierung von Service Discovery.
MikroprofilMicroservice wurde in diesem Framework nicht implementiert, daher möchte ich nur einige Punkte beachten, die ich kenne:
- Pluspunkte
- Implementierung von Eclipse MicroProfile
Im Wesentlichen ist MicroProfile Java EE, optimiert für ISA. Auf diese Weise erhalten Sie zum einen Zugriff auf die gesamte Vielfalt der Java EE-APIs, einschließlich der speziell für die ISA entwickelten, und zum anderen können Sie die MicroProfile-Implementierung in eine andere ändern (Open Liberty, WildFly Swarm usw.). .
- zusätzlich
- In MicroProfile Starter können Sie ein Projekt mit den erforderlichen Parametern in Analogie zu ähnlichen Tools für andere Frameworks (z. B. Spring Initializr ) von Grund auf neu erstellen. Zum Zeitpunkt der Veröffentlichung des Artikels implementiert Helidon MicroProfile 1.2, während die neueste Version der Spezifikation 3.0 ist.
Ktor- Pluspunkte
- Leichtigkeit
Ermöglicht es Ihnen, nur die Funktionen zu verbinden, die direkt zur Ausführung der Aufgabe benötigt werden. - Anwendungseinstellungen
In jeder Hinsicht gute Ergebnisse.
- Nachteile
- Unter Kotlin „geschärft“, das heißt, es ist möglich, aber nicht notwendig, in Java zu entwickeln.
- Mikroframework (siehe ähnlichen Artikel für Helidon SE).
- zusätzlich
Einerseits ist das Entwicklungskonzept des Frameworks nicht in den beiden beliebtesten Java-Entwicklungsmodellen (Spring-like (Spring Boot / Micronaut) und Java EE / MicroProfile) enthalten, was zu folgenden Ergebnissen führen kann:
- ein Problem bei der Suche nach Spezialisten;
- längere Zeit zum Ausführen von Aufgaben im Vergleich zu Spring Boot, da die erforderlichen Funktionen explizit konfiguriert werden müssen.
Andererseits ermöglicht Ihnen die Unähnlichkeit zum „klassischen“ Spring und Java EE, den Entwicklungsprozess aus einem anderen Blickwinkel zu betrachten, vielleicht bewusster.
Micronaut- Pluspunkte
- Aot
Wie bereits erwähnt, können Sie mit AOT die Startzeit und den von der Anwendung verbrauchten Speicher im Vergleich zu Spring Boot reduzieren. - Frühlingsartiges Entwicklungsmodell
Programmierer mit Erfahrung in der Entwicklung von Spring werden nicht viel Zeit brauchen, um dieses Framework zu beherrschen. - Anwendungseinstellungen
Gute Ergebnisse in jeder Hinsicht; - mehrsprachig
Erstklassige Bürgerunterstützung für Java, Kotlin, Groovy; Vielleicht gibt es Unterstützung für Scala. Meiner Meinung nach kann dies das Wachstum der Community positiv beeinflussen. Übrigens, im Juni 2019 belegt Groovy im Ranking der Popularität von Programmiersprachen TIOBE den 14. Platz, ab dem 60. Jahr zuvor, und belegt damit einen ehrenwerten zweiten Platz unter den JVM-Sprachen; - Mit dem Micronaut for Spring- Projekt können Sie auch die Laufzeit Ihrer vorhandenen Spring Boot-Anwendung in Micronaut ändern (mit Einschränkungen).
Frühlingsstiefel- Pluspunkte
- Plattformreife und Ökosystem
Der Rahmen "jeden Tag". Für die meisten alltäglichen Aufgaben gibt es bereits eine Lösung im Spring-Programmierparadigma, die vielen Programmierern vertraut ist. Die Entwicklung wird durch die Konzepte von Startern und Autokonfigurationen vereinfacht. - die Anwesenheit einer großen Anzahl von Spezialisten auf dem Arbeitsmarkt sowie eine bedeutende Wissensbasis (einschließlich Dokumentation und Antworten auf den Stapelüberlauf);
- Perspektive
Ich denke, viele werden zustimmen, dass der Frühling in naher Zukunft der führende Entwicklungsrahmen bleiben wird.
- Nachteile
- Anwendungseinstellungen
Die Anwendung auf diesem Framework gehörte nicht zu den führenden Unternehmen, jedoch können einige Parameter, wie bereits erwähnt, unabhängig voneinander optimiert werden. Es sei auch daran erinnert, dass das Spring Fu- Projekt in der aktiven Entwicklung ist, mit dessen Hilfe diese Parameter reduziert werden können.
Sie können auch die allgemeinen Probleme hervorheben, die mit den neuen Frameworks verbunden sind, die in Spring Boot fehlen:
- weniger entwickeltes Ökosystem;
- eine kleine Anzahl von Spezialisten mit Erfahrung mit diesen Technologien;
- längere Zeit, um Aufgaben zu erledigen;
- dunkle Aussichten.
Die betrachteten Frameworks gehören zu verschiedenen Gewichtsklassen: Helidon SE und Ktor sind
Mikroframes , Spring Boot ist ein Full-Stack-Framework, Micronaut eher auch Full-Stack; Eine andere Kategorie ist MicroProfile (z. B. Helidon MP). In Mikroframes ist die Funktionalität eingeschränkt, was die Ausführung von Aufgaben verlangsamen kann. Um die Möglichkeit der Implementierung dieser oder jener Funktionalität auf der Grundlage eines Entwicklungsframeworks zu klären, empfehle ich, dass Sie sich mit dessen Dokumentation vertraut machen.
Ich wage es nicht zu beurteilen, ob dieses oder jenes Framework in naher Zukunft "schießen" wird. Daher ist es meiner Meinung nach besser, die Entwicklung von Ereignissen weiterhin unter Verwendung des vorhandenen Entwicklungs-Frameworks zur Lösung von Arbeitsaufgaben zu überwachen.
Gleichzeitig übertreffen die neuen Frameworks, wie im Artikel gezeigt, Spring Boot um die berücksichtigten Parameter der empfangenen Anwendungen. Wenn einer dieser Parameter für einen Ihrer Microservices kritisch ist, müssen Sie möglicherweise auf die Frameworks achten, die die besten Ergebnisse erzielt haben. Vergessen Sie jedoch nicht, dass sich Spring Boot zum einen weiter verbessert und zum anderen ein riesiges Ökosystem hat und eine beträchtliche Anzahl von Java-Programmierern damit vertraut ist. Es gibt andere Frameworks, die in diesem Artikel nicht behandelt werden: Javalin, Quarkus usw.
Sie können den Projektcode auf
GitHub anzeigen. Danke für die Aufmerksamkeit!
PS: Vielen Dank an
artglorin für die Hilfe bei diesem Artikel.