Microservices mit REST-API in Kotlin und Spring Boot

Ein angenehmer Aspekt von Microservices ist, dass ihre Entwicklung häufig auf der grünen Wiese startet. Ein neuer Microservice ist also immer eine Chance einen neuen Technologie-Stack auszuprobieren. Für eingefleischte Java-Entwickler bietet sich dazu die Programmiersprache Kotlin an. Als JVM basierte Programmiersprache kann Kotlin problemlos das bei Java-Entwicklern beliebte Spring Framework verwenden. Daher zeige ich in diesem Artikel, wie ihr mit Spring Boot schnell einen Kotlin Microservice mit REST-API aufsetzt.

Neuer Microservice, die Gelegenheit mit Kotlin anzufangen...

In meinem beruflichen Umfeld entwickeln wir deutlich häufiger neue Microservices als neue Systeme mit klassischen Software-Architekturen oder als Monolithen. Da sich Microservices auf eine Geschäfts-Capability konzentrieren, siehe auch https://www.martinfowler.com/microservices, ist ihre Codebase im Vergleich zu herkömmlichen Systemen meist deutlich kleiner. Die Situation, einen neuen Microservice auf der grünen Wiese zu entwickeln, kommt auch relativ häufig vor. Daher ist dies immer eine Gelegenheit einen neuen Technologie-Stack auszuprobieren, z.B. mit der Programmiersprache Kotlin statt Java. Durch die Konzentration auf eine einzelne Geschäfts-Capability hat der neue Microservice eine kleine Codebase. So betrifft ein unpassender Technologie-Stack im schlimmsten Fall nur einen einzelnen, kleinen Microservice.  

Als Software-Entwickler arbeite ich meistens mit der Programmiersprache Java. Ein guter Online-Kurs (https://www.coursera.org/learn/kotlin-for-java-developers) überzeugte mich von der Programmiersprache Kotlin. So war mein erster Kotlin Wow-Effekt als ich realisierte, dass Kotlin in der JVM läuft und somit (fast) alle Java-Bibliotheken, also auch Spring, verwenden kann. Ab dann hat mich Kotlin damit beeindruckt, dass es als moderne Programmiersprache vieles kompakter und weniger fehleranfällig ausdrücken kann. Schaut euch zum Beispiel an wie Kotlin NullPointerExceptions vermeidet: https://kotlinlang.org/docs/null-safety.html. Der nächste Microservice war also für mich die Gelegenheit Kotlin in der Praxis auszuprobieren. Das Spring Framework ist in meinem Umfeld neben Java die 2. Konstante. Um weniger experimentierfreudige Kollegen mitzunehmen, habe ich Spring als Framework nicht ausgetauscht. In den nächsten Abschnitten schauen wir uns den Kotlin und Spring Technologie-Stack genauer an.

1. IDE auswählen: IntelliJ vs. Eclipse

Eigentlich entwickle ich am liebsten mit Eclipse. Da die Programmiersprache Kotlin aber von JetBrains designt und entwickelt wird, habe ich für die Entwicklung mit Kotlin die IntelliJ IDEA ausprobiert, welche ebenfalls von JetBrains entwickelt wird. Wie zu erwarten, funktioniert das sehr gut, da Programmiersprache und IDE von derselben Firma stammen. Hier ist eine Anleitung zum Loslegen:

Es gibt auch diverse Anleitungen, wie ihr mit Eclipse in Kotlin programmieren könnt. Dazu installiert ihr euch ein Kotlin Plugin über den Eclipse Marketplace und könnt dann in der Theorie loslegen. In der Praxis hatte ich leider immer wieder "komische" Fehler, so dass ich dann entschieden habe die IntelliJ Community Edition zu verwenden. Ich teile hier keinen Link zu einer Anleitung, weil ich damit kein Glück hatte.

2. Kotlin Projekt mit Spring Boot aufsetzen

Neue Spring Projekte setzt ihr leicht mit Plugins in der Entwicklungsumgebung auf, siehe microservices-mit-spring-boot-erstellen.html. Oder ihr verwendet den Online Spring Initializr: https://start.spring.io/
Der ist mindestens genauso leicht zu bedienen wie die IDE Plugins.

https://start.spring.io/

Im hier gezeigten Tutorial verwendete ich im Spring Initializr:
  • Gradle als Build Tool
  • Kotlin als Sprache 😉
  • Spring Boot Version 2.6.5 (oder höher)
  • jar als Ziel-Paket meiner Anwendung
  • Java Version 11 für die unterliegende JVM (Programmiersprache ist Kotlin)
  • Spring Web und Spring Boot DevTools als Dependencies
Nach dem Klick auf den GENERATE Knopf bekommt ihr ein zip-File, welches euer Spring Boot Kotlin Projekt enthält und in IntelliJ importiert bzw. geöffnet werden kann.

3. Kotlin Projekt starten

IntelliJ sollte dann automatisch den Gradle build starten und ihr solltet ein Projekt ohne Compile-Fehler haben. Weitere Infos zu Gradle im Spring Boot Kontext gibt es hier:

Danach könnt ihr die Spring Boot Anwendung starten, indem ihr die Kotlin-Datei mit der static void main Methode ausführt (Run ....main()). In einem neuen generierten Projekt sollte es im src/main Verzeichnisbaum nur eine kt-Datei geben, welche dann auch die main Methode enthält.

In der Konsole bzw. im Run Tab in IntelliJ seht ihr dann die typischen Spring Log-Einträge, also auch unter welchem Port die Anwendung läuft und wie lange der Start gedauert hat:

2022-03-31 22:13:11.002  INFO 18444 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-03-31 22:13:11.017  INFO 18444 --- [  restartedMain] de.bsi.restkt.RestKtApplication          : Started RestKtApplication in 3.031 seconds (JVM running for 3.617)

Ruft ihr die Anwendung jetzt im Browser unter http://localhost:8080 auf, wird euch die "Whitelabel Error Page" der Anwendung angezeigt. Im nächsten Abschnitt schreiben wir Code, damit sich das ändert. 

REST API in Kotlin mit Spring

Gradle Dependencies

Zum Entwickeln der REST-API können wir in Kotlin die gleichen Spring Annotationen verwenden, die wir auch in Java verwenden. Das liegt daran, dass wir auch die gleichen Spring-Dependencies in Gradle verwenden, die wir auch mit Java verwenden würden. Die build.gradle.kts wurde vom Spring Initializr generiert und hat in unserem Fall diese Dependencies:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Zusätzlich zu den Spring Bibliotheken gibt es noch spezielle Dependencies für Kotlin, die aber auch vom Spring Initializr ausgewählt wurden.

Rest-API in Kotlin

Die eigentlich REST-API wird von einem RestController bereitgestellt. Microservices haben typischer Weise eine einfache API, die sich auf eine Domäne konzentriert. Die hier gezeigte Item API bietet daher auch nur 3 Methoden an:
  • POST /item : Erstellt ein neues Item.
  • GET /item?itemName=Ball : Sucht ein vorhandenes Item anhand seines Namens.
  • DELETE /item/<itemId> : Löscht ein Item anhand seiner Id.
Items sind einfache Objekte mit 3 Attributen. Sie werden im JSON-Format mit der API ausgetauscht und sehen beispielsweise so aus:
    {
        "name""Ball",
        "id""1",
        "date""2022-03-27T09:55:16.930+00:00"
    }

Die Klasse ItemController fungiert in unserem Microservice als RestController und verarbeitet http-Requests mit ihren Methoden:
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.*
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/item")
class ItemController {

val items = mutableListOf<Item>()

@PostMapping
fun addItem(@RequestBody item : Item): ResponseEntity<Unit> {
items.add(item)
return status(HttpStatus.CREATED).build()
}
    ...
}
  • @RestController macht aus der Kotlin-Klasse einen Spring MVC Controller im Kontext des Design Patterns Model-View-Controller, siehe dazu auch: rest-json-apis-in-java.html. Der Controller wird vom standard-mäßig konfigurierten Spring RequestMappingHandler erkannt und verarbeitet.
  • @RequestMapping Annotationen werden verwendet, um zu definieren auf welche http Methoden, URL-Pfade, Datentypen usw. die Klasse und oder die jeweilig annotierte Methode reagieren. Hier verarbeitet der Controller alle eingehenden Requests mit dem Pfad /item.
  • @PostMapping entspricht dabei @RequestMapping ergänzt um die zu verwendende http POST-Methode, also: @RequestMapping(method = RequestMethod.POST). Die Annotation an der Methode übernimmt den definierten Pfad in der Klassen-Annotation als Prefix.
  • @RequestBody gibt an, dass der Body des POST Requests im annotierten Parameter an die Methode übergeben wird. Der Request-Body ist im JSON-Format und wird von Spring automatisch mittels Jackson Bibliothek in die Datenklasse Item geparst. Weiter unten zeige ich den Code dieser Klasse - es ist vergleichbar zu einer einfachen POJO-Klasse in Java.
  • ResponseEntity ist eine Klasse des Spring Frameworks, mit der eine http Response gebaut werden kann. Im Beispiel wird einfach der http Code 201 (CREATED) gesetzt und ein leerer http Response Body mit den Standard-Headern zurückgeschickt. Status, Body und Header können generell mit dieser Klasse gesetzt werden, so dass unser Controller mit entsprechenden http Responses antwortet.
  • Zum Speichern der Item Objekte verwende ich eine veränderbare Liste, die ich mit der Kotlin-Methode mutableListOf<Item>() erzeugt habe. Unter der Haube verwendet Kotlin hier die Klasse ArrayList von Java.

http Methoden hinzufügen

Spring unterstützt in RestControllern alle http Methoden:
  • GET mittels Annotation @GetMapping
  • POST mittels Annotation @PostMapping
  • PUT mittels Annotation @PutMapping
  • PATCH mittels Annotation @PatchMapping
  • HEAD mittels @RequestMapping(method = RequestMethod.HEAD)
  • DELETE mittels Annotation @DeleteMapping
  • OPTIONS mittels @RequestMapping(method = RequestMethod.OPTIONS)
  • TRACE mittels @RequestMapping(method = RequestMethod.TRACE) 
Die jeweilige Annotation wird an die zugehörigen public Methoden geschrieben. Hier 2 Beispiele für eine GET und eine DELETE Methode:
@GetMapping
fun getItemByName(@RequestParam(required = true) itemName: String):
ResponseEntity<Item> =
items.stream().filter { it.name.equals(itemName, true) }.findFirst()
.map { ok(it) }.orElse(notFound().build())

@DeleteMapping("/{itemId}")
fun deleteItemById(@PathVariable itemId: String?):
ResponseEntity<Unit> =
if (items.removeIf{it.id == itemId}) noContent().build()
else notFound().build()
  • @RequestParam ist eine weitere Spring Annotation, die verwendet wird, um Query-Parameter aus der URL in einen Methoden Parameter zu überführen.
    Wird unser REST-Service mit folgender URL aufgerufen:
    http://localhost:8080/item?itemName=Ball
    so nimmt der String Parameter itemName automatisch den Wert "Ball" an. Wenn der Parametername mit dem Namen des Request-Parameters in der URL nicht übereinstimmt, bekommen wir automatisch einen Bad Request Fehler mit HTTP Code 400.
  • Mit ok(it) wird hier eine http Response mit dem Code 200 erstellt. Im Body dieser Response bzw. in der Variable it befindet sich die gefundene Item Instanz. it ist in Kotlin ein Keyword zur Vereinfachung von Lambda-Ausdrücken.
  • Eine weitere Variante der Parameter-Übertragung von der URL in die public Methode (@PathVariable) wird in der mit @DeleteMapping annotierten Methode gezeigt. In @DeleteMapping wird zusätzlich ein Teil des URL-Pfades definiert, nämlich "/{itemId}". Im vorherigen Abschnitt haben wir gesehen, dass man auch in der @RequestMapping Annotation den URL-Pfad spezifizieren kann. Wenn dies auf Klassenebene geschieht, so gilt es für alle Methoden. Eine valide URL für unsere Delete-Methode würde so aussehen:
    http://localhost:8080/item/123
    Die in geschweiften Klammern notierten Teile der URL werden an Parameter mit der Annotation @PathVariable übergeben. In unserem Beispiel hätte der String Parameter itemId also den Wert "123".
  • Die hier gezeigten Annotationen lassen sich beliebig kombinieren, so dass man die REST-API wie benötigt implementieren kann.

Gegenüberstellung Kotlin und Java

Methoden Deklarationen sehen in Kotlin anders aus als in Java. Schaut euch Details dazu am besten in der Kotlin Dokumentation an: https://kotlinlang.org/docs/functions.html

Hier noch eine 1:1 Gegenüberstellung:

@PostMapping
fun addItem(@RequestBody item : Item): ResponseEntity<Unit>

@PostMapping
public ResponseEntity<Void> addItem(@RequestBody Item newItem)
  • Der Kotlin Code kann kompakter werden, weil Methoden-Rückgabewerte einfach mittels des Operators = zurückgegeben werden, wenn sie in einem Statement berechnet werden können. So werden geschweifte Klammern, return-Statements und Deklaration des Methoden-Rückgabetyps gespart. Das habe ich in getItemByName und deleteItemById gemacht.
  • In Kotlin können if-Ausdrücke auch direkt einen Rückgabewert haben. In deleteItemById wird also das Ergebnis des if-Ausdrucks als Methodenergebnis zurückgegeben. Also hier eine ResponseEntity mit dem HTTP-Code 204 oder 404. Java entwickelt sich ebenfalls in diese Richtung, wie man anhand der in Java 14 eingeführten Neuerungen am switch-Statement sieht:
    java-17-features.html
  • Die Item Klasse war in Java eine reine Datenklassen mit Getter- und Setter-Methoden für alle Attribute - also voll von Boilerplate-Code. Kotlin bietet data class Klassen an, die es uns ermöglichen alles in eine einzige Zeile zu schreiben und uns somit sämtlichen Boilerplate-Code abnehmen:
data class Item (
    val name: String, val id: String, val date: Date = Date())

Kotlin hat noch viele weitere tolle Features, die ich hier aber nicht im Detail erklären kann, da es hier ja auch in erster Linie um die REST-API eines Microservices mit Spring Mitteln geht.

Item API ausprobieren

Zum Demonstrieren und Ausprobieren von REST-APIs verwende ich gerne Postman. In GitHub findet ihr eine Postman Testsuite, in der ich alle Methoden vorbereitet habe:

HTTP-POST-Request mit Postman an Spring Boot Anwendung geschickt

Diese Postman Testsuite funktioniert sowohl bei der Kotlin als auch bei der Java Variante meiner Spring Boot REST-API. Die Spring Boot Anwendung muss vorher gestartet werden, das funktioniert so wie weiter oben beschrieben.

Fazit

Neben der hier gezeigten REST-API bestehen Microservices meist noch aus weiteren Bestandteilen. 
So gibt es eine Geschäftslogik, welche die Geschäfts-Capability des Microservices umsetzt und dazu gegebenenfalls andere Microservices, Cloud Services oder eine Datenbank benutzt. In diesem Artikel legte ich den Fokus aber auf die REST-API des in Kotlin geschriebenen Microservices.

Microservices in Kotlin zu schreiben, sollte Java Programmieren im Vergleich zu anderen Programmiersprachen relativ leicht fallen, da beide Sprachen auf der JVM basieren. Mit Kotlin könnt ihr bestehende Java Bibliotheken über euer Build-Tool einbinden, so wie hier Spring als Dependency geladen wurde. Damit wird der Einstieg auch erheblich erleichtert, weil ihr in der für euch neuen Programmiersprache Kotlin etablierte und vertraute Frameworks wie z.B. Spring weiterhin verwenden könnt. Um damit arbeiten zu können, müsst ihr also "nur" eine neue Programmiersprache lernen und nicht noch zusätzlich neue Frameworks.

Den kompletten Code zu diesem Artikel findet ihr in GitHub:

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection