Beiträge nach Schlagwörtern anzeigen

Beiträge nach Kategorien anzeigen

Spring Boot mit Kotlin — Erstellen Ihrer ersten API

Spring Boot mit Kotlin — Erstellen Ihrer ersten API
SHARE

In diesem Artikel erfahren Sie, wie Sie Ihre erste API mit Spring Boot und Kotlin erstellen.

Spring Framework

Spring Framework 🍃, das beliebteste Framework in Java für die Anwendungsentwicklung. Millionen von Entwicklern nutzen Spring täglich, um alle Arten von Anwendungen zu erstellen. Die erste Veröffentlichung von Spring im Jahr 2003 konzentrierte sich auf Dependency Injection, aber das Framework ist enorm gewachsen. Das Spring-Team hat eine Reihe von Modulen erstellt, die fast alles unterstützen, was ein Entwickler zum Erstellen einer Anwendung benötigt. Angesichts dieser Reife ist es kein Wunder, dass das Framework in den weltweit führenden Technologieunternehmen eingesetzt wird. Das ist der Vorteil eines Projekts, das seit fast 20 Jahren ständig weiterentwickelt und verbessert wird.

Was ist Kotlin

Kotlin ist eine Sprache, die von Jetbrains entwickelt wurde, derselben Firma, die alle großartigen IDEs wie IntelliJ Idea erstellt hat. JetBrains war mit Java und den anderen Sprachen auf dem Markt nicht zufrieden und beschloss, Kotlin zu entwickeln, um eine Verbesserung gegenüber Java zu erzielen. Und wenn ich von Verbesserung spreche, dann meine ich damit, dass Kotlin vollständig kompatibel mit Java gestaltet wurde, was es ermöglicht, eine gemeinsame Codebasis zwischen diesen beiden Sprachen zu haben und Unternehmen eine schrittweise Migration von Java zu ermöglichen.

Kotlin wird bei Entwicklern immer beliebter und laut der StackOverflow-Umfrage 2020 ist Kotlin eine der am meisten geliebten Sprachen. All diese Liebe kommt nicht von ungefähr. Kotlin hat viele Funktionen, die die Entwicklung einfacher, weniger wortreich und fehlerresistenter machen. Eine der besten Eigenschaften ist ein natives Null-sicheres System, Erweiterungsklassen, höherwertige Funktionen und die async await-Version der Sprache, die Coroutines genannt wird.

Erstellung des Projekts

Um mit der Erstellung unseres Projekts zu beginnen, werden wir das Spring Initializer-Tool verwenden. Sie finden es unter start.spring.io. Dies ist ein Tool, das vom Spring-Team erstellt wurde und sehr nützlich ist, da wir bereits unser initiales Setup durchführen und unsere Hauptabhängigkeiten einbinden können.

Spring initializer screen

Zuerst können Sie den gesamten Quellcode hier auf meinem Github finden. Für dieses Projekt werden wir ein Setup verwenden, das sich ein wenig von den anderen häufigen Tutorials unterscheidet, die wir mit Spring Boot finden können. Wir werden Gradle als unser Build-Tool verwenden, aber natürlich, wie Sie sehen können, ist auch Maven eine Option. Wir werden die stabilste Version von Spring wählen, das ist 2.6.0 (als ich diesen Artikel schreibe). Sie können die Projekt-Metadaten ändern, wenn Sie möchten, oder einfach die Standardwerte belassen. Ich habe Java 11 gewählt, da ich Java 17 noch nicht installiert habe, aber für dieses Projekt wird es keinen Unterschied machen.

In der rechten Spalte haben wir die Option, unsere initialen Abhängigkeiten einzubinden. Für dieses Projekt werden wir einige von ihnen einbeziehen und später weitere Abhängigkeiten manuell in unser Gradle-File hinzufügen. Lassen Sie uns beginnen mit:

  • Spring Boot DevTools — Es gibt einige nützliche Funktionen, die uns während der Entwicklung helfen können, wie automatisches Neuladen, Live-Reload und Deaktivierung des Cachings. Sehr nützlich während der Entwicklung.
  • Spring Web — Wir werden benötigen, um unsere API’s zu erstellen
  • Spring JPA und H2 Database — zum Speichern unserer Daten

Spring initializer dependencies

Wie Sie in meinen Metadaten sehen können, werde ich ein Beispiel erstellen, das eine Bank simuliert, und wir werden eine Transfer-API erstellen. 💰

IntelliJ Idea

Entpacken Sie die Datei und öffnen Sie sie in Ihrer IDE. Ich werde IntelliJ Idea Community Edition verwenden. Sie können es kostenlos herunterladen oder eine andere IDE verwenden. Erwähnenswert ist, dass, wenn Sie die Ultimate-Version von IntelliJ Idea haben, das Spring Initializer Tool, das wir gerade gesehen haben, bereits in die IDE eingebettet ist, sodass Sie die gesamte Projekt-Erstellung direkt dort durchführen können.

Gradle-Datei

Das erste, was wir uns anschauen werden, ist unsere build.gradle.kts-Datei. Sie befindet sich im Stammverzeichnis des Projektordners. Dies ist die Datei, in der wir unsere Abhängigkeiten verwalten können. Da wir den Spring Initializer für die anfängliche Einrichtung verwendet haben, können wir sehen, dass das Tool diese Datei mit unserer Konfiguration und den ausgewählten Abhängigkeiten erstellt hat.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "2.5.7"
	id("io.spring.dependency-management") version "1.0.11.RELEASE"
	kotlin("jvm") version "1.5.31"
	kotlin("plugin.spring") version "1.5.31"
	kotlin("plugin.jpa") version "1.5.31"
}

group = "com.backendhorizon"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	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")
	runtimeOnly("com.h2database:h2")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs = listOf("-Xjsr305=strict")
		jvmTarget = "11"
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

Da wir uns entschieden haben, Kotlin zu verwenden, hat Spring Initializer diese Konfigurationsdatei bereits als Kotlin-Datei erstellt. Für diejenigen, die mit Gradle nicht vertraut sind, weise ich darauf hin, dass es auch möglich ist, Ihre Gradle-Dateien mit Groovy zu schreiben. Da wir bereits Kotlin verwenden, empfehle ich dringend, es auch mit Gradle zu verwenden, da es einige Vorteile gegenüber Groovy bietet, wie zum Beispiel die Autovervollständigung.

Gradle-Plugins

Lassen Sie uns den Plugins-Block ansehen. Gradle selbst bietet standardmäßig nicht viel, aber es bietet uns Entwicklern die Möglichkeit, unsere Builds zu erweitern und neue Funktionen hinzuzufügen. Ein großartiges Beispiel hierfür ist der „plugins“-Block, in dem wir Plugins haben, die unser Projekt sehr einfach einsatzbereit machen.

Wir sehen, dass wir einige Plugins haben und die meisten von ihnen für eine Ersteinrichtung von Spring und Kotlin vorgesehen sind. Werfen wir einen Blick auf die Spring Boot Plugin-Seite und sehen, wie das Plugin beschrieben wird:

Spring Boot macht es einfach, eigenständige, produktionsreife, Spring-basierte Anwendungen zu erstellen, die Sie „einfach ausführen“ können. Wir nehmen eine meinungsstarke Sicht auf die Spring-Plattform und Drittanbieterbibliotheken ein, sodass Sie mit minimalem Aufwand loslegen können. Die meisten Spring Boot-Anwendungen benötigen sehr wenig Spring-Konfiguration.

Mit anderen Worten, einfache Konfiguration, um sofort mit Ihrem Projekt zu beginnen. Alle anderen Plugins tun etwas Ähnliches: Sie bereiten unsere Projektabhängigkeiten vor und helfen, sie auf die bestmögliche Weise zu konfigurieren und zu integrieren.

Unter den Plugins haben wir die Metadaten, die wir wählen, und die Java-Version.

Maven Central ist das Repository, aus dem wir unsere Abhängigkeiten herunterladen, und im Block dependencies sind alle Bibliotheken aufgeführt, die wir im Projekt haben. Später werden wir zu dieser Datei zurückkehren und einige zusätzliche Abhängigkeiten hinzufügen.

Es gibt eine weitere wichtige Datei, die einen kurzen Blick wert ist, und das ist unsere Anwendungsdatei.

Application-Datei

Lassen Sie uns unsere Application-Datei öffnen. Im Projekt auf GitHub können Sie diese Datei als BankApplication finden, aber dies wird sich je nach Wahl in Ihren Metadaten ändern. Hier finden wir die berühmteste Funktion von allen, die main-Funktion, wo die Anwendung startet.

package com.backendhorizon.bank

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class BankApplication

fun main(args: Array<String>) {
	runApplication<BankApplication>(*args)
}

Wir können sehen, dass in der main-Methode diese Funktion runApplication verwendet wird, um mit der BankApplication-Klasse zu starten, und in dieser Klasse haben wir nur eine einzige Annotation, @SpringBootApplication. Diese einzelne Annotation übernimmt eine Menge für uns.

Werfen wir einen kurzen Blick darauf, wie die Dokumentation dies beschreibt:

Gibt eine Konfigurationsklasse an, die eine oder mehrere @Bean-Methoden deklariert und auch die automatische Konfiguration und das Scannen von Komponenten auslöst. Dies ist eine Komfort-Anmerkung, die der Deklaration von @Configuration, @EnableAutoConfiguration und @ComponentScan entspricht."

Im nächsten Kapitel werden wir unseren ersten Controller erstellen und etwas, das Sie bemerken werden, ist, dass wir keine Instanz des Controllers erstellen werden. Das liegt daran, dass die @SpringBootApplication-Annotation aus 3 anderen Annotationen besteht und eine davon ist die @ComponentScan.

Wie der Name bereits vermuten lässt, wird @ComponentScan unser Projekt nach Components durchsuchen/scannen und wenn sie gefunden wird, wird Spring diese Instanz für uns erstellen und verwalten. Es ist erwähnenswert, dass eine Komponente, damit sie automatisch erkannt wird, sich auf derselben Paketebene oder darunter der Anwendungsklasse befinden muss.

Components sind Klassen, die mit speziellen Annotationen markiert sind, welche diese Klassen zur automatischen Erkennung vorschlagen. Es gibt einige verschiedene Annotationen, die für unterschiedliche Zwecke verwendet werden können, wie z. B. @Component, @Controller, @RestController, @Repository und @Service.

Die @EnableAutoConfiguration wird etwas Ähnliches für die Bibliotheken im Projekt tun. Sie wird in unserem Klassenpfad nachsehen, die Bibliotheken überprüfen, die wir verwenden, und eine anfängliche Konfiguration für uns laden. Das werden wir mit unserer Datenbank sehen. Bleiben Sie dran. 😉

Erste API

Lassen Sie uns unseren ersten API-Controller erstellen. Erstellen Sie ein Paket namens controller und darin eine Klasse namens TransferController. Erstellen Sie 2 Methoden, newTransfer und getAllTransfers wie im folgenden Beispiel.

package com.backendhorizon.bank.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/transfer")
class TransferController {

    @PostMapping("/new")
    fun newTransfer() {
        System.out.println("Saved")
    }

    @GetMapping("/all")
    fun getAllTransfers(): String {
        return "Hello"
    }

}

Lass uns verstehen, was hier passiert. Am Anfang unserer TransferController-Klasse haben wir zwei Anmerkungen verwendet, @RestController und @RequestMapping:

  • @RestController: Mit dieser Anmerkung geben wir Spring Boot an, dass diese Klasse ein Web-Handler/Web-Controller ist. Sie wird Anfragen erhalten und Antworten an die Anrufer zurückgeben. Da es sich um eine Art von “Controller” handelt, wird diese Komponente (denken Sie daran, diesen Begriff im Hinterkopf zu behalten) auch von Spring Boot verwaltet.
  • @RequestMapping: Dies gibt an, welche Signatur dieser Web-Controller in der Anfrageadresse nach der Hostadresse haben wird. Zum Beispiel werden unsere beiden Methodenendpunkte die folgenden Adressen haben: localhost:8080/transfer/new und localhost:8080/transfer/all.

In Bezug auf die Methoden sind sie relativ einfach:

  • newTransfer protokolliert eine “Saved”-Nachricht in der Konsole und seine Anmerkung zeigt an, dass dies eine “Post"-Anfrage sein wird, @PostMapping, und wie der Controller gibt sie die Signatur für diese Methode an, localhost:8080/payment/new.
  • getAllTransfer gibt eine “Hello”-Nachricht an den Anrufer zurück und ähnlich wie bei der vorherigen Methode gibt es den Anfragetyp an, in diesem Fall eine Get-Anfrage, @GetMapping, gefolgt von der Signatur, localhost:8080/payment/all.
  • Get und Post sind Teil der REST-Spezifikation, falls Sie damit nicht vertraut sind, hinterlassen Sie einen Kommentar und ich werde einen neuen Artikel erstellen, der diese ausführlicher erklärt.

Ausführen & Testen

Jetzt haben wir alles, was wir für unseren ersten Test benötigen. Gehe zur main-Funktion in der Anwendungsdatei und starte deine Anwendung. Du solltest beginnen, einige Logs zu sehen und Tomcat, unser WebServer, wird starten und die Anwendung ohne Probleme hosten. Beachte in den Logs, dass Tomcat dir mitteilt, auf welchem Port deine Anwendung läuft.

Tomcat logs

Jetzt müssen wir diese Endpunkte testen. Der beste Weg ist, ein dafür entwickeltes Tool zu verwenden. Ich persönlich empfehle Postman. Es unterstützt REST-APIs vollständig und ermöglicht das Testen aller verschiedenen Arten von REST-Endpunkten, das Einrichten von JSON-Bodies, Headern, Autorisierungstoken usw. Postman hat auch GraphQL-Unterstützung, falls du darauf stehen solltest. 😂

Öffne einen neuen Tab in Postman, wähle GET und gib die Adresse des getAllTransfers-Endpunkts ein, wie im Bild unten:

Postman screen

Wie du sehen kannst, haben wir eine „Hello“-Nachricht als Antwort erhalten. Unsere API funktioniert! Mache dasselbe für newTransfer, ändere die URL und den Typ zu POST. Du wirst sehen, dass die API ebenfalls funktioniert und die „Saved“-Nachricht in der IntelliJ Idea-Konsole angezeigt wird.

Beachte, dass wir keine Instanz von TransferController erstellt haben, nur die @RestController-Annotation hat Spring angezeigt, dass unser Controller erstellt werden muss. Läuft wie geschmiert, oder?!

Datenmodelle

Lassen Sie uns damit beginnen, unsere Anwendung vorzubereiten, um Daten zu empfangen und zu speichern. Innerhalb des Controller-Pakets erstellen Sie ein neues namens model. Erstellen Sie zwei neue Klassen, TransactionModel und OverviewTransactionModel.

In der TransactionModel Klasse werden wir ein paar Attribute erstellen. Stellen wir uns vor, dass wir für eine Überweisung an eine andere Person die Kontokennung benötigen, die je nach Land, in dem Sie leben, eine Nummer oder eine Zeichenfolge sein kann. Wir benötigen auch den Wert und eine Beschreibung, die optional sein kann.

package com.backendhorizon.bank.controller.model

class TransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String = "",
)

Wenn wir die Liste aller Transaktionen abrufen, möchten wir ein wenig mehr Daten sehen. Neben allen bisherigen Daten haben wir zwei zusätzliche Felder, das Datum der Transaktion und die Transaktionskennung(id).

package com.backendhorizon.bank.controller.model

import java.util.*

class OverviewTransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String,
    val date: Date,
    val id: String
)

Aktualisieren Sie Ihren TransferController wie im folgenden Code mit den neuen Modellen, die wir erstellt haben. Sie können feststellen, dass wir in der newTransfer-Methode eine neue Annotation @RequestBody haben. Dies zeigt an, dass das TransactionModel Teil dieser Anfrage ist und das JSON automatisch in unser Modell deserialisieren wird.

package com.backendhorizon.bank.controller

import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.TransactionModel
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/transfer")
class TransferController {

    @PostMapping("/new")
    fun newTransfer(@RequestBody transactionModel: TransactionModel) {
        // TODO: SAVE THE DATA
    }

    @GetMapping("/all")
    fun getAllTransfer(): List<OverviewTransactionModel> {
        // TODO: FETCH THE DATA
        return listOf()
    }

}

Datenbank

Da wir nun Daten über unsere API empfangen, müssen wir sie in einer Datenbank speichern. Wir werden eine In-Memory-Datenbank namens H2 verwenden, die wir bereits in unserer build.gradle.kts Datei aufgenommen haben.

Erstellen Sie ein neues Paket auf der gleichen Ebene wie controller namens repository. Innerhalb dieses Pakets eine Schnittstelle namens TransferRepository. Lassen Sie uns auch ein weiteres model Paket innerhalb des Repositorys erstellen und darin eine Klasse namens TransactionDBModel. Am Ende sollten Sie so etwas haben:

Projektstrukturbaum

Zuerst arbeiten wir an den Attributen des DB-Modells. Ähnlich wie TransactionModel werden wir die Kontokennung, den Wert und die Beschreibung haben. Um in die Datenbank speichern zu können, müssen wir einen eindeutigen Bezeichner haben, und dieser Bezeichner wird automatisch für uns generiert. Wir werden auch das aktuelle Datum als Transaktionsdatum verwenden.

package com.backendhorizon.bank.repository.model

import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
class TransactionDBModel(
    val accountIdentifier: String,
    val value: Double,
    val description: String = "",
) {
    @Id
    @GeneratedValue
    var id: UUID? = null
        private set

    val date: Date = Date()
}

Durch den Konstruktor werden wir die Attribute empfangen, die wir über unsere API erhalten, und im Klassenkörper werden wir die zusätzlichen zwei Attribute (id und date) haben. Wir könnten die id bereits mit einer zufälligen UUID initialisieren, aber wir lassen JPA das erledigen. Wir machen das Set privat, damit wir diesen Wert nicht überschreiben, sondern nur lesen können. Das Datum wird immer das aktuelle Datum sein, also initialisieren wir es einfach mit dem aktuellen.

In dieser Klasse haben wir drei neue Anmerkungen:

  • @Entity: Dies zeigt an, dass diese Klasse von JPA (Java Persistence API) verwendet wird, um die Daten in einer Datenbank zu speichern.
  • @Id: Zeigt an, dass das annotierte Feld für diese Entität der Primärschlüssel in der Datenbank sein wird.
  • @GeneratedValue: Der Name ist selbsterklärend. Es zeigt an, dass dieser Primärschlüssel seinen eigenen Wert automatisch generieren soll.

Jetzt, da wir unser Modell haben, erstellen wir unser Interface, das CRUD-Operationen (Create, Read, Update und Delete) durchführt. Die Art und Weise, wie wir dies tun werden, besteht darin, unser TransferRepository mit CrudRepository zu erweitern. Es ist erstaunlich, wie einfach und leistungsstark das ist.

package com.backendhorizon.bank.repository

import com.backendhorizon.bank.repository.model.TransactionDBModel
import org.springframework.data.repository.CrudRepository
import java.util.*

interface TransferRepository: CrudRepository<TransactionDBModel, UUID>

Wenn Sie das CrudRepository-Interface öffnen, ist es leicht zu erkennen, was passiert. Dieses Interface enthält alle generischen Funktionen für CRUD und alles, was wir tun müssen, ist anzugeben, welcher Typ unser Modell und der Primärschlüssel sind, in unserem Fall TransactionDBModel und UUID.

Indem wir lediglich die Modell- und Primärschlüsseltypen angeben, wird Spring herausfinden, dass wir dieses Interface verwenden und eine Standardimplementierung für dieses Interface (und die von uns bereitgestellten Typen) injizieren. Sie können sich die Standardimplementierung ansehen, es handelt sich um eine Klasse namens SimpleJpaRepository.

Natürlich sind wir nicht auf nur diese Funktionen beschränkt. Es ist auch möglich, komplexere Datenbankoperationen zu erstellen, aber für diesen Artikel arbeiten wir nur mit dieser Standardimplementierung.

Von der API zur Datenbank und umgekehrt

Jetzt, da unsere Persistenzschicht fertig ist, können wir die Daten speichern, die wir von der API erhalten. Das Einzige, was wir tun müssen, ist eine kleine Konvertierung zwischen unseren Modellen, da das Modell, das wir im Endpoint erhalten, sich von dem unterscheidet, das wir in der Datenbank verwenden. Lassen Sie uns zwei Erweiterungsfunktionen erstellen, um diese Modelle zu konvertieren, convertToDBModel und convertToOverviewTransactionModel.

Zuerst bei TransactionModel, konvertieren Sie das Endpoint-Modell in ein Datenbankmodell, TransactionDBModel.

package com.backendhorizon.bank.controller.model

import com.backendhorizon.bank.repository.model.TransactionDBModel

class TransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String = "",
)

fun TransactionModel.convertToDBModel() = TransactionDBModel(
    accountIdentifier = this.targetAccount,
    value = this.value,
    description = this.description
)

Und für die Übersicht, etwas Ähnliches. Diesmal jedoch das Datenbankmodell TransactionDBModel in ein Endpoint-Modell, OverviewTransactionModel.

package com.backendhorizon.bank.controller.model

import com.backendhorizon.bank.repository.model.TransactionDBModel
import java.util.*

class OverviewTransactionModel(
    val targetAccount: String,
    val value: Double,
    val description: String,
    val date: Date,
    val id: String
)

fun TransactionDBModel.convertToOverviewTransactionModel() = OverviewTransactionModel(
    targetAccount = this.accountIdentifier,
    value = this.value,
    description = this.description,
    date = this.date,
    id = this.id.toString()
)

OK, nun können wir die Daten zwischen unseren Endpunkten und der Datenbank konvertieren. Lassen Sie uns unseren Controller aktualisieren, um diese Aufrufe zu tätigen.

In den Konstruktor werden wir unsere TransferRepository Klasse einfügen. Denken Sie daran, Spring ist schlau; wenn Spring sieht, dass wir diese Abhängigkeit in unserem Controller haben, wird Spring die für TransactionRepository (basierend auf SimpleJpaRepository) erstellte Implementierung injizieren.

package com.backendhorizon.bank.controller

import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.convertToDBModel
import com.backendhorizon.bank.controller.model.convertToOverviewTransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/transfer")
class TransferController(val repository: TransferRepository) {

    @PostMapping("/new")
    fun newTransfer(@RequestBody transactionModel: TransactionModel) {
        repository.save(transactionModel.convertToDBModel())
    }

    @GetMapping("/all")
    fun getAllTransfers(): List<OverviewTransactionModel> {
        return repository.findAll().map { it.convertToOverviewTransactionModel() }
    }

}

Wie Sie im obigen Beispiel sehen können, verwendet unsere Repository-Klasse die generischen Methoden von CrudRepository, um die Transaktionen in der Datenbank zu speichern und alle zu finden. Wir nutzen auch die Erweiterungsmethoden, die wir erstellt haben, um das Modell in das am besten geeignete umzuwandeln.

Zu diesem Zeitpunkt sind unsere Endpunkte alle fertig. Kompilieren und führen Sie die Anwendung neu aus. Lassen Sie uns eine neue Überweisungsanforderung erstellen. Denken Sie daran, dass wir jetzt alle erforderlichen Daten in Postman zu unserem Endpunkt hinzufügen müssen, damit er korrekt funktioniert. Fügen Sie einen JSON-Body wie im Bild unten ein und reichen Sie ihn ein.

Postman screen

Nach dem Einreichen sollte alles einwandfrei funktionieren. Sie sollten einen Status 200 erhalten, was bedeutet, dass die Anfrage erfolgreich war. Wenn Sie jetzt alle Daten erneut abrufen, sehen Sie die übermittelten Überweisungsdaten, einschließlich des Datums und der generierten ID.

Postman screen

Unsere API funktioniert 🎉, aber wir sind noch nicht fertig!

Database Console

Es gibt eine sehr schöne Möglichkeit, die uns die H2 Database bietet, um die gespeicherten Daten zu überprüfen, und das ist die “H2 Console”. Es ist eine Web-Admin-Oberfläche, in der Sie die Datenbankdaten überprüfen und Abfragen ausführen können.

Zuerst müssen Sie, um diese Konsole zu aktivieren, den folgenden Code in Ihre application.properties-Datei im Ressourcen-Ordner einfügen.

# Enabling H2 Console
spring.h2.console.enabled=true

Nachdem hinzugefügt, führen Sie Ihre Anwendung erneut aus und geben Sie in Ihrem Browser die URL http://localhost:8080/h2-console ein. Sie werden ein Anmeldefenster sehen, um sich mit Ihrer Datenbank zu verbinden. Das Einzige, was Sie ändern müssen, ist die JDBC-URL. Schauen Sie in Ihre Konsolenprotokolle und suchen Sie nach „H2 console available at …“ wie im Bild unten.

Kopieren Sie die Datenbank-URL und drücken Sie auf „Verbinden“. Sie sollten die Konsole sehen. Jetzt können Sie mit Ihrer Datenbank spielen, SQL-Abfragen ausführen und die von Ihrer API gespeicherten Daten sehen.

Konsolenprotokolle

Jetzt sind wir fertig, oder? FALSCH! Wir sind gute Entwickler und gute Entwickler schreiben Tests!

Unit-Tests

Bevor wir unseren ersten Test schreiben, fügen wir eine neue Abhängigkeit im Abhängigkeitsblock innerhalb von build.gradle.kts hinzu. Mit dieser Abhängigkeit werden wir anstelle von Mockito Mockk verwenden, um Mock-Instanzen zu erstellen.

// ... other dependencies already included
testImplementation("com.ninja-squad:springmockk:3.0.1")

Diese Bibliothek hilft uns, Mockk im Spring-Kontext besser zu unterstützen. Sie können hier mehr darüber lesen.

Erstellen Sie eine neue Klasse namens PaymentsControllerTest im Testordner. Nehmen Sie sich einen Moment Zeit, um die untenstehenden Tests anzusehen, und wir werden verstehen, was passiert.

package com.backendhorizon.bank.controller

import com.fasterxml.jackson.databind.ObjectMapper
import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import com.backendhorizon.bank.repository.model.TransactionDBModel
import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import java.util.*


@WebMvcTest
class PaymentsControllerTest(@Autowired val mockMvc: MockMvc) {

    @MockkBean
    private lateinit var paymentsRepository: TransferRepository


    @Test
    fun `should submit transaction with success`() {
        val transfer = TransactionModel(
            value = 1.50,
            description = "store 1",
            targetAccount = "NL76ABNA2376059879"
        )

        every { paymentsRepository.save(any()) } returns mockk()

        mockMvc.perform(post("/transfer/new")
            .content(ObjectMapper().writeValueAsString(transfer))
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk)
    }

    @Test
    fun `should get transaction with success`() {
        val mockTransaction = mockk<TransactionDBModel>().apply {
            every { value } returns 1.50
            every { description } returns "store 1"
            every { accountIdentifier } returns "NL47INGB8845464385"
            every { date } returns Date()
            every { id } returns UUID.randomUUID()
        }

        every { paymentsRepository.findAll() } returns listOf(mockTransaction)

        mockMvc.perform(get("/transfer/all").accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk)
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("\$.[0].description").value("store 1"))
    }

    @Test
    fun `should return a bad request error if the request there's no body`() {
        mockMvc.perform(post("/transfer/new")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().is4xxClientError)
    }

}

Die allgemeine Idee, was die Tests tun, ist dank des Spring Testing-Frameworks leicht zu verstehen, da es sehr ausdrucksstark ist. Schauen wir uns die technischen Details an:

  • @WebMvcTest: Diese Annotation zeigt an, dass diese Tests eine MVC-Komponente testen, in diesem Fall einen REST-Controller.
  • MockMVC: Dies ist der Einstiegspunkt, der es uns ermöglicht, während der Tests mit unserem Controller zu kommunizieren, Anfragen zu stellen und das Ergebnis sowie die Statuscodes zu überprüfen.
  • @MockkBean: Wir mocken eine Fake-TransferRepository-Klasse, um verschiedene Szenarien zu simulieren. Wir simulieren diese Szenarien immer, bevor wir eine Aktion mit MockMvc ausführen, indem wir das Ergebnis der findAll- und save-Methoden mocken.

Nachdem wir unsere Mocks eingerichtet haben, stellen wir eine Anfrage an den Controller mithilfe von MockMvc. Alle Tests folgen einer ähnlichen Struktur:

  • Wir geben MockMvc an, eine Anfrage auszuführen und welche Art es ist (post oder get).
  • Wenn wir einen Body in der Anfrage haben, transformieren wir das Objekt in ein JSON.
  • Der Inhaltstyp der Anfrage, APPLICATION_JSON.
  • Wir drucken die Anfragedetails und Ergebnisse in der Konsole aus.
  • Und schließlich geben wir an, was wir von dieser Anfrage erwarten. Es kann eine Statuscodeüberprüfung sein, ob die Anfrage erfolgreich ist, oder eine JSON-Antwort (und deren Inhalt).

Und schließlich sind wir fertig. 🎉🎉🎉

Du kannst das vollständige Projekt auf GitHub finden.


Fragen oder Vorschläge? Bitte hinterlassen Sie einen Kommentar, wir sind alle hier, um gemeinsam zu lernen. 🤠

Danke fürs Lesen.

comments powered by Disqus

Verwandte Beiträge