Microframeworks mit Kotlin

Ein Microframework ist ein minimalistisches Webanwendungs-Framework. Was es in der Regel von traditionelleren, großen Anwendungsframeworks unterscheidet, ist der Fokus auf Einfachheit und Geschwindigkeit der Entwicklung, die viel kleinere Codebasis oder das Fehlen einiger fortgeschrittener Features, wie z.B. Templating-Engines, fortgeschrittene Sicherheitsfunktionen usw. In diesem Blogpost werde ich einige Microframeworks vorstellen, die zusammen mit Kotlin verwendet werden können. Lass uns einen Blick darauf werfen.

Spring Fu

Spring Fu ist ein experimentelles Kotlin-Micro-Framework, mit dem sich leichtgewichtige, auf Spring basierende Anwendungen mit funktionalen APIs anstelle von Annotationen erstellen lassen. Die Kotlin-DSL basiert nun auf der Spring Boot-Infrastruktur und wird Kofu (für Kotlin und funktional) genannt.

Spring Fu ist eigentlich ein Inkubator für die programmatische Konfiguration von Spring Boot unter Verwendung von DSLs, um es explizit mit Code auf deklarative Weise zu konfigurieren und dabei eine große Auffindbarkeit dank Autovervollständigung zu erreichen.

Um es zu nutzen, muss man einer Spring Boot-Anwendung die Abhängigkeit org.springframework.fu:spring-boot-kofu hinzufügen.

Um eine Spring Bean zu definieren, sieht die Konfiguration wie folgt aus:

@Configuration
class SpringConfigBean() {
    @Bean
    fun springConfigBean() = SpringBean()
}

Spring fu: 

configuration {
    beans {
        bean <SpringBean>()
    }
}

Spring Fu bietet einen schnellen Start (40 % schneller als die reguläre Autokonfiguration bei einer minimalen Spring MVC-App), einen geringen Speicherverbrauch und passt dank seines (fast) reflection-freien Ansatzes gut zu GraalVM Native (GraalVM ist eine von Oracle entwickelte virtuelle Maschine, die es unter anderem ermöglicht, JVM-Bytecode über Substratevm in native ausführbare Dateien zu kompilieren).

Spring Fu ist noch nicht für den produktiven Einsatz vorgesehen, sondern dient eher der Inkubation und dem Erhalt von Feedback und Beiträgen aus der Community, um hoffentlich einen Punkt zu erreichen, an dem es als Teil von Spring Framework und Spring Boot integriert werden kann.

Javalin

Javalin begann als Fork des Java- und Kotlin-Webframeworks SparkJava, entwickelte sich aber schnell zu einer grundlegenden Neufassung, die von koa.js beeinflusst wurde. Beide Web-Frameworks sind von dem modernen Ruby Micro-Web-Framework Sinatra inspiriert. Wenn man also von Ruby kommt, sollte sich Javalin nicht allzu fremd anfühlen.

Javalin ist sowohl ein Kotlin-Webframework als auch ein Java-Webframework, was bedeutet, dass die API mit Fokus auf eine große Interoperabilität zwischen den beiden Sprachen entwickelt wird. Die Bibliothek selbst ist hauptsächlich in Kotlin geschrieben, hat aber einige Kernklassen, die in Java geschrieben sind, um die beste Interoperabilität zwischen den beiden Sprachen zu erreichen.

Javalin und Kotlin arbeiten gut zusammen, da sie beide sehr pragmatisch sind und sich darauf konzentrieren, Dinge schnell und mit wenig Code zu erledigen.

Um einen einfachen Server zu erstellen, der auf eine GET-Anfrage an / mit Hello World antwortet, benötigen wir zwei Zeilen Javalin. Eine Zeile, um den Server zu erstellen und zu starten, und eine Zeile, um einen Handler zu einem Pfad hinzuzufügen:

import io.javalin.Javalin

fun main(args: Array<String>) {
val app = Javalin.start(7000) 
app.get("/") { r-> r.result("Hello World") } 
}

Das obige Beispiel startet einen einfachen Javalin-Server mit der Standardkonfiguration. Wer mehr über Javalin erfahren will, kann sich hier die Tutorials anschauen.

Warum Javalin?

  • Einfach und leichtgewichtig: Javalin ist nur ein paar tausend Zeilen Code mit dem Jetty Server als Basis, und die Leistung entspricht der von Jetty. Aufgrund seiner Größe ist es sehr einfach, über den Quellcode nachzudenken.
  • Flexibel: Javalin ist so konzipiert, dass es einfach und blockierend ist, da dies das am einfachsten zu verstehende Programmiermodell ist.
  • API-Entwurf: alle Methoden der Javalin-Instanz geben diese zurück, was die API vollständig fluent macht. So kann man eine deklarative und vorhersagbare REST-API erstellen, die für neue Entwickler, die in Projekt einsteigen, sehr einfach zu verstehen ist.

Bei Javalin gibt es kein MVC-Konzept, aber es gibt Unterstützung für Template-Engines, WebSockets und statisches File-Serving. So kann man Javalin sowohl für die Erstellung des RESTful-API-Backends als auch für das Serving einer index.html mit statischen Ressourcen (falls man eine Single Page Application erstellt) verwenden. Dies ist praktisch, wenn man nicht zusätzlich zu dem Javalin-Dienst einen Apache- oder Nginx-Server einsetzen will. Wenn man Javalin verwenden möchte, um eine traditionellere Website anstelle einer REST-API zu erstellen, gibt es mehrere Template-Engine-Wrapper, die eine schnelle und einfache Einrichtung ermöglichen.

Javalin eignet sich am besten für die schnelle Erstellung von überschaubaren kleinen (Micro-)Services.

Micronaut

Micronaut ist ein modernes, JVM-basiertes, Full-Stack-Microservices-Framework, das für die Erstellung von modularen, leicht testbaren Microservice-Anwendungen entwickelt wurde.
Micronaut zielt darauf ab, alle Werkzeuge zur Verfügung zu stellen, die notwendig sind, um voll funktionsfähige Microservice-Anwendungen zu erstellen, einschließlich Dependency Injection, Auto-Konfiguration, HTTP-Routing, HTTP-Client mit clientseitigem Load-Balancing usw. Micronaut bietet eine schnelle Startzeit, reduzierten Speicherbedarf, minimale Verwendung von Reflection, minimale Verwendung von Proxies und einfache Unit-Tests.
Das Micronaut Framework bietet Unterstützung für Kotlin, das auf dem Kapt Annotation Processing Compiler Plugin aufbaut.

Lass uns das “Hello World” – Beispiel in diesem Fall anschauen:

Um einen Microservice zu erstellen, der mit “Hello World” antwortet, benötigen wir einen Controller.

import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces

@Controller("/hello") 
class HelloController {

    @Get("/")  
    @Produces(MediaType.TEXT_PLAIN) 
    fun index(): String {
        return "Hello World"  
    }
}

Die Klasse wird als Controller mit der @Controller-Annotation definiert, die auf den Pfad /hello abgebildet wird. Die @Get-Annotation wird verwendet, um die Index-Methode auf alle HTTP-GET Anfragen abzubilden. Standardmäßig verwendet die Antwort eines Micronaut Controllers application/json als Content-Type. Wir geben einen String und kein JSON-Objekt zurück. Deshalb setzen wir ihn auf text/plain. Am Ende wird ein “Hello World” String als Ergebnis zurückgegeben.

Was unterscheidet Micronaut von Spring Boot?

Spring Boot ist derzeit das beliebteste Framework in der JVM-Welt, aber Micronaut ist ein schnell an Popularität gewinnendes Framework, das speziell für die Erstellung von Serverless-Funktionen oder Microservices mit geringem Memory-Footprint entwickelt wurde und Entwicklern die Tür zur Serverless-Welt öffnet. Insbesondere wird die Startup-Zeit stark reduziert, und auch der RAM-Verbrauch sinkt. Lass uns einen Vergleich zwischen Spring Boot und Micronaut machen bezüglich folgender Punkte:


Einfachheit der Installation
Beide Frameworks verfügen über Kommandozeilen-Tools, um schnell mit der Entwicklung zu beginnen. Wir können die entsprechenden Kommandozeilen-Tools installieren und die Beispielanwendung von Spring Boot/Micronaut erstellen und testen. Die Einfachheit der Installation ist also die gleiche.


Nativ cloud-fähig

Hier hat Micronaut den Vorteil, dass die gängigsten Cloud-Features standardmäßig unterstützt werden, ohne dass man auf Cloud-Dienste von Drittanbietern angewiesen ist. Diese Funktionen sind Teil der Anwendung selbst. Der Slogan von Micronaut ist “‘Natively cloud-native app development”.

Serverlose Funktionen

Micronauts schnelle Startzeit, der Compile-Time-Ansatz und der geringe Speicherbedarf machen dieses Framework zu einem großartigen Kandidaten für die Entwicklung von Serverless Funktionen. Tatsächlich verfügt Micronaut über eine spezielle Unterstützung für die Implementierung und Bereitstellung von Funktionen für AWS Lambda und jedes andere System, das die Ausführung von Funktionen als Container unterstützt. Während der Speicherverbrauch und die Startzeit von Spring Boot-Anwendungen im Vergleich zu Micronaut etwas höher sind, wird Micronaut für die Implementierung von Serverless-Funktionen empfohlen.

Anwendungskonfigurationen

Micronaut ist sowohl von Grails als auch von Spring Boot inspiriert, indem es Konfigurationseigenschaften aus verschiedenen Quellen direkt in den Core-IoC-Container integriert.

Micronaut verfügt standardmäßig über die Implementierung von PropertySourceLoader, die die Eigenschaften von den angegebenen “locations” und deren Reihenfolge laden werden wie: Kommandozeilenargumente, Java-Systemeigenschaften, anwendungsspezifische Eigenschaften aus der Anwendung.{extension} könnte .properties, .json, .yml oder .groovy sein, spezielle Eigenschaften (Zufallswerte) und OS-Umgebungsvariablen.

Spring Boot unterstützt an seiner Stelle alle oben genannten Property Locations und zusätzlich zu den oben aufgeführten unterstützt es auch andere Properties Locations wie Spring Boot Devtool, ServletConfig init parameters, ServletContext init parameters, JNDI Attribute von java:comp/env. usw. In diesem Fall ist Spring Boot Micronaut überlegen.


Sicherheit
Spring Boot unterstützt standardmäßig folgende Sicherheitsmechanismen: MVC Security, WebFlux Security, OAuth2 und Actuator Security.

Micronaut unterstützt auch einige Sicherheitsmechanismen standardmäßig, wie Authentication Providers, Security Rules, IP Pattern Rule, Secured Annotation, Built-In Endpoints SecurityBasic Auth, JSON Web Token usw.

Wie wir sehen können, haben beide Frameworks unterschiedliche Sicherheitsmechanismen und jedes hat seine eigenen Vorteile.

Als Schlussfolgerung ist Spring Boot in vielen Aspekten immer noch führend, unterstützt durch die große Erfahrung, die es im Laufe der Jahre im Umgang mit Microservices gesammelt hat. Aber Micronaut ist aber vielversprechend genug, um Spring Boot eine harte Konkurrenz zu machen.

Spark

Apache Spark ist eine Open-Source Unified-Analytics-Engine für die groß angelegte verteilte Datenverarbeitung. In den letzten Jahren hat es sich zu einem der beliebtesten Tools für die Verarbeitung großer Datenmengen entwickelt. Es deckt ein breites Spektrum an Aufgaben ab – von der Datenstapelverarbeitung und einfachem ETL (Extract/Transform/Load) bis hin zu Streaming und maschinellem Lernen.

Aufgrund der Interoperabilität von Kotlin mit Java können Kotlin-Entwickler bereits über die Java-API mit Apache Spark arbeiten. Auf diese Weise können sie jedoch nicht das volle Potenzial von Kotlin nutzen, und die allgemeine Erfahrung ist alles andere als reibungslos. Das Projekt Kotlin API for Apache Spark, fügt eine fehlende Kompatibilitätsschicht zwischen Kotlin und Apache Spark hinzu. Es erlaubt, idiomatischen Kotlin-Code zu schreiben und dabei vertraute Sprachmerkmale wie Datenklassen und Lambda-Ausdrücke zu verwenden.

Kotlin für Apache Spark erweitert außerdem die bestehenden APIs um einige nette Funktionen wie withSpark und withCached: withSpark ist eine einfache und elegante Möglichkeit, mit SparkSession zu arbeiten, die sich automatisch um den Aufruf von spark.stop() am Ende des Blocks kümmert. Man kann ihr Parameter übergeben, die für die Ausführung von Spark erforderlich sind, z. B. den Master-Speicherort, die Protokollstufe oder den App-Namen. Sie enthält auch einen praktischen Satz von Standardwerten für die lokale Ausführung von Spark.

Eine weitere nützliche Funktion ist withCached. Wenn man in anderen APIs Berechnungen in mehrere Pfade aufteilen möchte, die Dinge aber nur einmal berechnen, würde man die “cache“-Methode aufrufen. Das wird aber schnell unübersichtlich und man muss daran denken, die gecachten Daten wieder zu entfernen. Andernfalls riskiert man, mehr Speicher als beabsichtigt zu verbrauchen oder sogar Dinge ganz kaputt zu machen. withCached kümmert sich um das Tracking und Aufräumen.

Kotlin ist eine pragmatische, gut lesbare Sprache, von der ich glaube, dass sie Potenzial für die Übernahme in Spark hat. Sie braucht dazu nur mehr Dokumentation.

Quarkus

Quarkus ist das neue Open-Source-Framework und Toolset, das eine Menge Produktivitätsverbesserungen für Java bringt. Seine Vorteile sind nicht auf Java-Entwickler beschränkt, die Kotlin-Unterstützung ist auch sehr gut.

Das Framework wird als ein Tool vorgestellt, das für aktuelle Herausforderungen wie neue Einsatzumgebungen und Anwendungsarchitekturen geeignet ist; Anwendungen, die auf dem Framework geschrieben werden, haben einen geringen Speicherverbrauch und eine geringe Boot-Zeit.

Außerdem gibt es Vorteile für einen Entwickler, z. B. Live-Reload- “out of the box”.
Es gibt keine Hauptmethode im Quellcode einer Quarkus-Anwendung.
Insbesondere scheint Quarkus sehr gut für eine Microservices-Architektur geeignet zu sein, mit seinem Versprechen von “supersonic” Startzeit und “subatomarem” Speicherverbrauch. Darüber hinaus arbeitet es gut mit GraalVM zusammen.

Ktor

Zu guter Letzt: Ktor, mein Lieblings-Microframework.

Ktor ist ein Kotlin-Framework, mit dem Entwickler asynchrone Client- und Server-Anwendungen in Kotlin schreiben können. Auch wenn es noch nicht ganz so weit ist, ist das Ziel von Ktor, ein durchgängiges Multiplattform-Anwendungs-Framework für vernetzte Anwendungen bereitzustellen. Derzeit werden JVM-Client- und Server-Szenarien sowie JavaScript-, iOS- und Android-Clients unterstützt, und man arbeitet daran, Server-Funktionen in native Umgebungen und Client-Funktionen in andere native Ziele zu bringen.


Warum Ktor?
Leichtgewichtig – Ktor erlaubt es, transparent nur die Funktionalität zu konfigurieren, die das Projekt benötigt.
Asynchron – Durch die Verwendung von Kotlin Coroutines ist Ktor wirklich asynchron und hoch skalierbar. Man kann die Möglichkeiten der nicht-blockierenden Entwicklung ohne den “Callback-Albtraum” nutzen.
Multiplattform – Von Grund auf mit der Kotlin Multiplattform-Technologie entwickelt, kann man Ktor-Anwendungen überall einsetzen.
Erweiterbar – Mit einer konfigurierbaren Pipeline kann man die Erweiterungen erstellen, die man benötigt, und sie überall platzieren.

Ein Ktor Server ist eine Server Engine (Tomcat, Jetty, Netty oder Coroutine I/O), die auf einem Port hört. Er setzt sich aus Modulen zusammen, die wiederum aus Features bestehen (z. B. Routing, Sessions, Logging). Wenn ein Ktor-Server startet, wartet er auf Verbindungen an einem bestimmten Port.

Ktor definiert Pipelines für asynchrone, erweiterbare Berechnungen, die überall in Ktor verwendet werden. Alle Pipelines haben einen zugehörigen Subjekttyp, einen Kontexttyp und eine Liste von Phasen mit zugehörigen Interceptoren, sowie Attribute, die als ein kleiner typisierter Objektcontainer fungieren. Die Phasen sind geordnet und können so definiert werden, dass sie nach oder vor einer anderen Phase oder am Ende ausgeführt werden. Jede Pipeline hat eine geordnete Liste von Phasenkontexten für diese Instanz, die einen Datensatz von Interceptoren für jede Phase enthält.

Die Idee dabei ist, dass jeder Interceptor für eine bestimmte Phase nicht von anderen Interceptoren für dieselbe Phase abhängt, sondern von Interceptoren aus früheren Phasen. Beim Ausführen einer Pipeline werden alle registrierten Interceptoren in der durch die Phasen festgelegten Reihenfolge ausgeführt. Zum Beispiel definiert der Serverteil von Ktor eine ApplicationCallPipeline ohne Subjekt und mit ApplicationCall als Kontext: Die Application-Instanz ist eine ApplicationCallPipeline, wenn also die Anwendungs-Engine des Servers eine HTTP-Anfrage verarbeitet, führt sie die Application-Pipeline aus. Die Kontextklasse ApplicationCall enthält die Anwendung, die Anfrage, die Antwort und die Attribute und Parameter. Am Ende registrieren die Anwendungsmodule Interceptoren für bestimmte Phasen der Anwendungspipeline, um die Anforderung zu verarbeiten und eine Antwort auszugeben. Ktor-Module sind die Konfiguration der Applikation. So können wir den Server konfigurieren, mit seiner Engine, seinem aktiven Port, oder sogar spezielle Funktionalitäten für jedes Modul installieren (Routing, Session, Logging…).

Schauen wir uns an, wie ein “Hallo Welt” in Ktor aussieht:

fun main(args: Array<String>) {
    val server = embeddedServer(Netty, port = 8080) {
        routing {
            get("/") {
                call.respondText("Hello World!", ContentType.Text.Plain)
            }
            get("/demoktor") {
                call.respondText("HELLO WORLD!")
            }
        }
    }
    server.start(wait = true)
}

Wir haben erst einmal die Hauptmethode main() definiert und einen eingebetteten Server mit Netty als Back-End erstellt, der auf Port 8080 lauscht. Der nächste Schritt ist die Installation der Routing-Funktion mit einem Block, in dem man Routen für bestimmte Pfade und HTTP-Methoden definieren kann. Es wird eine GET-Anfrage für den Pfad /demoktor bearbeitet, die mit einer HELLO WORLD! Nachricht beantwortet wird.

Das Webapplikations-Framework Ktor, selbst in Kotlin geschrieben, soll ein Tool zum schnellen Erstellen von Webapplikationen mit Kotlin bieten. Unabhängig davon, welches Hosting man wählt, macht Ktor starken Gebrauch von Kotlin Coroutines, sodass es zu 100% asynchron und nicht-blockierend implementiert ist. Ktor schreibt nicht vor, welche Frameworks oder Tools verwendet werden sollen, so dass man wählen kann, welche Logging-, DI- oder Templating-Engine man will. Die Bibliothek ist im Allgemeinen ziemlich leichtgewichtig und dennoch durch einen Plugin-Mechanismus sehr erweiterbar. Ktor verwendet auch DSLs, die es dem Benutzer ermöglichen, die Endpunkte der Web-App auf eine sehr deklarative Weise zu definieren.

Zusammenfassend lässt sich sagen, dass Ktor von den schönen Features von Kotlin regen Gebrauch macht. Es nutzt insbesondere DSLs, Coroutines und Erweiterungsfunktionen, die die Sprache und Ktor selbst so elegant machen. Ktor ist ein fantastisches Tool, wenn man mit Webanwendungen in Kotlin beginnen will, da es einfach und sehr erweiterbar ist, nach dem Prinzip “einfach ist einfach, komplex ist verfügbar”. Aber es gibt auch einen Nachteil von Ktor: Es ist auf Kotlin ausgelegt, d. h. eine Entwicklung in anderen Sprachen kann unmöglich sein oder lohnt sich nicht.

Die betrachteten Frameworks gehören zu unterschiedlichen Bereichen, man sollte die Dokumentation genau lesen und das Framework verwenden, das für den Anwendungsbereich am besten geeignet ist.

Views All Time
1518
Views Today
3
Short URL for this post: https://blog.oio.de/dPupy
This entry was posted in Other languages for the Java VM and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *