Kommunikation zwischen Browser und Backend: Objektserialisierung mit Kotlin

Java bietet eine sogenannte Objektserialisierung, bei der ein Objekt als eine Folge von Bytes dargestellt werden kann, die sowohl die Daten des Objekts als auch Informationen über den Typ des Objekts und die im Objekt gespeicherten Datentypen enthält. Nachdem ein serialisiertes Objekt in einen Datenstrom geschrieben wurde, kann es aus diesem Datenstrom wieder gelesen und deserialisiert werden, d.h. die Typinformationen und Bytes, die das Objekt und seine Daten repräsentieren, können verwendet werden, um das Objekt im Speicher neu zu erstellen.

Am beeindruckendsten ist, dass der gesamte Prozess JVM-unabhängig ist, d.h. ein Objekt kann auf einer Plattform serialisiert und auf einer völlig anderen Plattform deserialisiert werden

Zum Beispiel GWT RPC: Wer schon mit dem Google Web Toolkit (GWT) gearbeitet hat, kann sich gut an GWT RPC erinnern. GWT RPC (GWT Remote Procedure Call) ist der von GWT verwendete Mechanismus, mit dem Client-Code direkt server-seitige Methoden aufrufen kann. GWT RPC ist servlet-basiert und asynchron, d.h. der Client wird während der Kommunikation nie blockiert. Mit GWT RPC können Java-Objekte (die vom GWT-Framework automatisch serialisiert werden) direkt zwischen dem Client und dem Server ausgetauscht werden. Ein server-seitiges Servlet wird als Service bezeichnet. Ein Remote Procedure Call, bei dem Methoden server-seitiger Servlets vom client-seitigen Code aus aufgerufen werden, wird als Aufruf eines Services bezeichnet.

Kotlin hat keinen eigenen RPC-Mechanismus, wie GWT. Aber Kotlin kann Objekte zu/von JSON (de)serialisieren. Das ist total einfach, weil auf beiden Seiten (Browser und Backend) die gleichen Klassen benutzt werden. Ich werde den Prozess Schritt für Schritt erklären.

kotlinx.serialization bietet eine Reihe von Bibliotheken für alle unterstützten Plattformen – JVM, JavaScript, Native – und für verschiedene Serialisierungsformate – JSON, CBOR, Protokollpuffer und andere. Einige Daten-Serialisierungsformate, wie JSON, sind besonders verbreitet. Da sie sprach- und plattformneutral sind, ermöglichen sie den Datenaustausch zwischen Systemen, die in einer beliebigen modernen Sprache geschrieben wurden. Alle Kotlin-Serialisierungsbibliotheken gehören zu org.jetbrains.kotlinx.

kotlinx.serialization enthält Bibliotheken für verschiedene Serialisierungsformate:

  • JSON: kotlinx-serialization-core
  • Protocol buffers: kotlinx-serialization-protobuf
  • CBOR: kotlinx-serialization-cbor
  • Properties: kotlinx-serialization-properties
  • HOCON: kotlinx-serialization-hocon (nur auf JVM)

Einrichtung

Zuerst muss man sein Build-Skript so konfigurieren, dass man die Kotlin-Serialisierungstools im Projekt verwenden kann. Zweitens wendet  man das Kotlin Serialisierungs-Plugin für Gradle org.jetbrains.kotlin.plugin.serialization (oder kotlin ("plugin.serialization") in der Kotlin Gradle-DSL) an.

plugins {
   
kotlin("multiplatform") version "1.3.71"
    application //to run JVM part
    kotlin("plugin.serialization") version "1.3.71"
    kotlin("plugin.spring") version "1.3.71"

}

Als nächstes fügt man die Abhängigkeit zu der JSON-Serialisierungsbibliothek hinzu.

implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version")

Beispiel Todo-Liste

Ich habe eine Todo-Liste erstellt, deren Einträge drei Eingabeparameter erhalten, nämlich Kategorie, Beschreibung und Id, wie man in der TodoEntry-Klasse sehen kann.

data class TodoEntry(val category: String, val description: String, val id: Int = 0)

Diese Klasse kann jedoch noch nicht für Serialisierungszwecke verwendet werden, da wir den Compiler noch nicht angewiesen haben, Serialisierungscode zu generieren. An dieser Stelle kommt die @Serializable Annotation zum Einsatz. Diese Annotation weist den Compiler dazu an, den Code zu generieren, der für die Durchführung der Serialisierung für unsere Klasse erforderlich ist:

@Serializable
data class TodoEntry(val category: String, val description: String, val id: Int = 0)

Jetzt kann der Compiler TodoEntry-Objekte verstehen und serialisieren.

Wir werden auch definieren müssen, welche Art von JSON wir erwarten. Wir definieren dies mit Hilfe von JsonConfiguration. Diese Klasse hilft uns, das Verhalten für das JSON-Objekt zu konfigurieren. Sie stellt uns zwei gebrauchsfertige Konfigurationen zur Verfügung – Stable und Default. Wir werden die Konfiguration Stable verwenden, die sich an die Einschränkung der JSON-Spezifikation hält und von der Laufzeitbibliothek bereitgestellt wird. Um ein Objekt zu serialisieren (d.h. in JSON zu konvertieren), kann die Methode stringify() verwendet werden.

Im folgenden Beispiel sehen wir, wie wir mit einem JSON-Konfigurationsobjekt ein TodoEntry-Objekt in einen JSON String umwandeln können:

val json = Json(JsonConfiguration.Stable)
val todo = json.stringify(TodoEntry.serializer(), TodoEntry(category.value, description.value))

Wir wissen bereits aus dem vorherigen Blog-Beitrag, wie man eine Spring-Boot-Anwendung mit Kotlin erstellt. Jetzt können wir die Kommunikation zwischen Browser und Backend herstellen.

Zuerst erstellen wir einen Controller, mit dem man Todo-Einträge bearbeiten kann. Er enthält vier Methoden, um die Einträge auszulesen, einen Eintrag hinzuzufügen, zu aktualisieren und zu löschen.

Controller:

@RestController
@RequestMapping("/todo")
class TodoController @Autowired constructor(val service: TodoService) {

    @GetMapping(produces = arrayOf(MediaType.APPLICATION_JSON_VALUE))
    fun getEntries(): String {
        val json = Json(JsonConfiguration.Stable)
        return json.stringify(TodoEntry.serializer().list, service.getTodoEntries())
    }

    @PostMapping
    fun addEntry(@RequestBody todoJson: String) {
        val json = Json(JsonConfiguration.Stable)
        val todoEntry = json.parse(TodoEntry.serializer(), todoJson)
        service.addEntry(todoEntry)
    }

    @PutMapping(consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE))
    fun updateEntry(@RequestBody todoJson: String) {
        val json = Json(JsonConfiguration.Stable)
        val todoEntry = json.parse(TodoEntry.serializer(), todoJson)
        service.updateEntry(todoEntry)
    }

    @DeleteMapping("/{id}")
    fun deleteEntry(@PathVariable("id") id: Int) {
        service.removeEntry(id)
    }
}

@RestController zeigt an, dass die von jeder Methode zurückgegebenen Daten direkt in den Antwortbody geschrieben werden, anstatt ein Seitentemplate zu rendern.

Wir haben Routen für jede Operation (@GetMapping, @PostMapping, @PutMapping und @DeleteMapping, entsprechend den HTTP-Aufrufen GET, POST, PUT und DELETE). Dazu werde ich nicht weiter ins Detail gehen.

Service Components sind Klassen, die mit @Service annotiert sind. Diese Klassen werden verwendet, um Geschäftslogik in einer eigenen Schicht, getrennt von der @RestController-Klasse, zu schreiben. Die Logik einer Service-Klasse zur Behandlung von TodoEntry-Objekten ist hier dargestellt:

Service:

@Component
class TodoService() {

    var entries: MutableMap<Int, TodoEntry> = HashMap();
    var counter: Int = 0;

    @PostConstruct
    fun initEntries() {
        addEntry(TodoEntry("shopping","Butter"))
        addEntry(TodoEntry("shopping","Bread"))
        addEntry(TodoEntry("work","Learn Kotlin"))
    }

    fun getTodoEntries(): List<TodoEntry> {
        return ArrayList(entries.values)
    }

    fun addEntry(entry: TodoEntry) {
        val id = counter++
        entries.put(id, TodoEntry(entry.category, entry.description, id))
    }

    fun updateEntry(entry: TodoEntry) {
        val id = entry.id
        if (!entries.containsKey(id)) {
            throw IllegalArgumentException("Unknown entry id " + id)
        }
        entries.put(id, entry)
    }

    fun removeEntry(id: Int) {
        entries.minus(id)
    }
}

Wenn man über localhost auf Port 8080 zugreift und das /todo aus dem @RequestMapping hinzufügt, kann man die hinzugefügte Todo-Liste als JSON Objekte sehen.

Man kann Listen hinzufügen mit Hilfe eines Buttons: UI-Elemente, die in weiteren Blog-Beiträgen aus dieser Serie diskutiert werden.

Ich hoffe, dass der Beitrag interessant war. Es folgen noch weitere Beiträge zu Kotlin – UI, Web Basics und “Development Server”.

Views All Time
375
Views Today
1
Short URL for this post: https://blog.oio.de/9vO0s
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 *