#3: Bauen einer einfachen Spring Boot API

Ich möchte hier vorstellen, wie eine API mit Spring Boot in VSCode erstellt wird. Hier geht es nicht darum eine perfekte Anleitung und die allerbesten best-practices zu vermitteln, sondern ein simples “ramp-up” Beispiel zu geben. Ich will euch hier ein Tutorial liefern, das euch befähigt innerhalb sehr kurzer Zeit eine lauffähige Anwendung zu haben.

Dieser Artikel ist Teil einer ganzen Serie. Schau Dir die vorherigen Artikel in dieser Serie an, bevor du weiter fortfährst.

Eine Übersicht über die gesamte Artikelserie findest du hier.

Controller erstellen

Innerhalb des Projektes wird nun ein erster Controller erstellt im package “controller” erstellt. Das “Package” ist nichts weiter als ein Ordner der letzlich auch den Namenraum definiert, in dem die darin enthaltenen Klassen leben.

Anlegen des Controller-packages

Ist der Ordner erstellt, legst du eine neue Datei “BasicDataController.java” an. Ist alles korrekt konfiguriert, wird euch VSCode den package path eintragen und standardmäßig eine Klasse anlegen die so heißt wie die Datei selber.

Nun beginnt die “Spring Boot-Magie”. Fügt oberhalb der Klassendefinition die “Annotation” “@RestController” hinzu und drückt anschließend “Strg + .“. VSCode wird euch dann auffordern die entsprechende Klasse “RestController” aus dem Spring Framework zu importieren. Funktioniert dies nicht automatisch, habt Ihr sehr wahrscheinlich “Spring Web” nicht ins POM importiert (siehe Kapitel 2).

Nun legen wir eine Methode “health” an, die einfach den String “OK” zurückgebt und fügen für die Methode die Annotation “RequestMapping” hinzu, in der wir notieren, in welcher Form die Methode konkret reagieren soll.

Anmerkung: Mit dieser Funktion könnt Ihr tatsächlich die Verfügbarkeit eurer REST-API überwachen, allerdings nicht deren Funktionstüchtigkeit. Für das Monitoring eurer Anwendung stellt Spring Boot explizite Funktionen zur Verfügung. Mehr Infos findest du hier. Betrachte dieses Funktion als Ersatz für das klassische “HelloWorld”.

@RequestMapping(method = RequestMethod.GET, path = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
    public String health() {
        return "OK";
    }

Anmerkung: Die identische Funktion wie mit “RequestMapping” erhaltet Ihr auch mit “GetMapping(“/heating”)“.

Am Anfang wird euch “RequestMethod” und “MediaType” ebenfalls angemeckert, wie zuvor könnt Ihr hier komfortabel die entsprechenden Klassen importieren.

Import Mediatype (aber den richtigen)

Klar passiert hier, was irgendwann passieren muss. MediaType existiert mit gleichen Namen mehrfach. Passt auf, dass Ihr die Klasse “MediaType” aus dem Spring Framework importiert.
Habt Ihr das falsche importiert, wird euch leider die Import-Funktion nicht mehr angezeigt. In dem Fall müsst Ihr den falschen import einmal löschen und könnt dann neu importieren.

Start der Anwendung

Die Anwendung könnt Ihr nun auf verschiedene Art und Weise starten. Der “Run”-Link den VSCode on der Startklasse (DemoTelemetryApplication.java) zur Verfügung stellt fällt evtl. als erstes ins Auge.

Ausführen einer Applikation

Weitere Methoden zum Start der Anwendung sind:

  • Drücken der Funktionstaste “F5”
  • Der Button “Select and start debug” im unteren Menüband
  • Das Menü “Run > Start Debugging”
  • Starten über den “Run-Explorer” (Strg + Shift + D). Hierzu später auch mehr.

Fehlerbehebung: Doppelte Port-Belegung

Ab und wann kann es vorkommen, dass ein vorangeganger Prozess nicht beendet wird und Ihr nicht mehr starten könnt. Zum Beispiel passiert dies, wenn Ihr die Anwendung mit “Run” startet (was asynchron passiert). die VSCode Konsole gibt euch dann auch direkt “a watschn”:

Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

Ruft hier einfach eine wsl-konsole auf (wsl -d Debian) und schaut mit “netstat -tulpn” eure belegten Ports an und schiesst den noch laufenden Prozess ab.

Laufenden Prozess beenden

Testen der REST-API

Nach dem Start der Anwendung wollen wir natürlich auch sehen, ob die Spring Boot API etwas zurückgibt, was uns nun die VSCode-Extension “REST Client” ermöglicht.

Legt eine neue Datei unter test/de/frickeldave/flickerlteppich/telemetry an und nennt diese testsrequests.http. In dieser Datei können alle requests die für manuelle Tests notwendig sind, gesammelt werden. Wir starten mit einer einfach GET-Anfrage.

Anlegen von testrequests

Mit Klick auf “Send request” könnt Ihr den request nun absenden und das Ergebnis im neu öffnenden Fenster bestaunen.

Komplexe Objekte auslesen

Nun wollen wir ja nicht nur ein String-Objekt zurückgeben, sondern ein komplexes Objekt. Hierfür legst du erstmal ein neues package mit dem Namen “Entity” an und darunter eine Klasse “System“.

Anlegen einer Entity mit Eigenschaften

In dieser Klasse “System” legen wir folgende private Eigenschaften an:

    private String hostname;
    private String fqdn;
    private String primaryIPv4Address;
    private String mac;
    private String serial;
    private LocalDateTime timestamp;
    private LocalDateTime lastboot;

Klar gibt es noch ganz viele Informationen die interessant wäre, wir beschränken uns mal auf die genannten, die auch wesentlich sind, um ein Gerät ein-eindeutig im Netzwerk zu identifizieren. Detailliertere Informationen (z.B. Festplattengröße, RAM, etc.) sollten wir dann sowieso in eine Child-Klasse auslagern.

Sind die Properties angelegt, erscheint eine kleine Glühbirne am Anfang der Zeile, wenn Ihr mit der Maus über die Eigenschaft geht. Klickt Ihr darauf, erscheint ein Kontextmenü über das Ihr nun die “getter” und “setter” anlegen könnt.

Anmerkung: Einfach in jeder Klasse stumpf getter/setter einzufügen ist nicht unbedingt die schönste Art und Weise sauberen Code zu schreiben. Im Idealfall enthält jede dieser Methoden eine sinnvolles Stück Business Logik. Für dieses Beispiel sind die getter/setter tatsächlich aber auch notwendig, da Spring Boot diese zur Funktionalität voraussetzt.

Erstellen der getter und setter

Tipp: Das Projekt “Lombok” schafft euch sogar diese Arbeit vom Hals und ermöglicht es euch, die Getter/Setter/Konstruktoren und vieles mehr automatisiert mit Annotation einzubinden. Zum Beispiel erstellt “@NoArgsConstructor” einen leeren Konstruktor an den nichts übergeben wird. Dafür gibt es auch eine VSCode Extension, die das nativ unterstützt. Das kann man mögen. Ich persönlich tippe lieber ein wenig, bevor ich jedem im Team erklären muss, warum da z.B. ein Konstruktor fehlt, aber der Code trotzdem funktioniert. Aber das soll jeder (oder jedes “Team”) für sich entscheiden, daher erwähne ich es hier.

Nun fehlt noch ein Konstruktor. Auch dieser lässt sich ganz simpel anlegen. Klickt einfach auf eine der Properties und ruft über die Glühbirne die Option “Generate Contructor” auf.

Du kannst in dem folgenden Assistenten nun die Eigenschaften definieren, die über den Konstruktor gesetzt werden sollen.

Auch an dem nun angelegten Konstruktor kannst du die Glühbirne anwählen, die es dir ermöglicht, alle Eingabeparameter als “final” zu definieren wenn du das willst.

Erweitern des SystemObject um eine Liste von Systemen zurückzugeben

Wir implementieren an dieser Stelle eine ganz einfache statische Methode in der Klasse “Systems” in der drei Systeme angelegt, in eine lokale Liste aufgenommen und zurückgegeben werden.

public static List<System> getSystemList() {
        final System s1 = new System("s1", "s1.fleckerlteppich.frickeldave.de", "192.168.1.1", "001122334401", "abc1", 
            LocalDateTime.of(2020, Month.AUGUST, 01, 13, 01), LocalDateTime.of(2020, Month.JULY, 01, 13, 01));
        final System s2 = new System("s2", "s2.fleckerlteppich.frickeldave.de", "192.168.1.2", "001122334402", "abc2", 
            LocalDateTime.of(2020, Month.AUGUST, 01, 13, 02), LocalDateTime.of(2020, Month.JULY, 01, 13, 02));
        final System s3 = new System("s3", "s3.fleckerlteppich.frickeldave.de", "192.168.1.3", "001122334403", "abc3", 
            LocalDateTime.of(2020, Month.AUGUST, 01, 13, 03), LocalDateTime.of(2020, Month.JULY, 01, 13, 03));
        final List<System> systemList = new ArrayList<System>();
        systemList.add(s1);
        systemList.add(s2);
        systemList.add(s3);
        return systemList;
    }

Anlegen eines Controllers der ein Java Object zurückgibt

Leg nun als nächstes im package “Controller” einen neuen Controller “SystemController” an, den wir wie zuvor als “RestController” markieren und eine Methode “getSystemList” anlegen, die eine Liste aller Systeme zurückgibt. Zusätzlich zum RestController, tragen wir noch die Annotation “@RequestMapping(“systems)” ein, die dafür sorgt das alle Anfragen die unter dem Pfad “http://<hostname>:<port>/systems” gestellt werden in diesem Controller landen. Die ganze Klasse schaut nun wie folgt aus:

package de.frickeldave.fleckerlteppich.telemetry.controller;

import java.util.List;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("systems")
public class SystemController {

    @RequestMapping(method = RequestMethod.GET, path = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
    public List<System> getDevices() {
        final List<System> deviceList = System.getSystemList();
        return deviceList;
    }
}

Hier wirst du ggf. darüber stolpern, dass “System.getSystemList” nicht gefunden wird und VSCode mit Druck auf “Strg + .” auch keinen Lösungsvorschlag bringt. Das liegt daran, dass standardmäßig “Java.System” schon importiert ist und VSCode in dieser Klasse eine “getSystemList” Methode erwartet. Wenn du in diesem Fall händisch “import de.frickeldave.fleckerteppich.entity.system” hinzufügst, funktioniert alles wie erwartet.
Man kann an dieser Stelle natürlich vortrefflich darüber streiten, ob es Sinn macht eine eigene Klasse “System” oder “Object” zu benennen. Auch das ist eine Entscheidung des jeweiligen Projektes.

Testen der Methode

Nun kannst du deine Testaufrufe “testRequests.http” einfach um eine neue Zeile erweitern und diese (nach Start der Anwendung) vom vorherigen Eintrag aus dem letzten Kapitel durch drei aufeinander folgende “#” trennen.

Neuer testrequest
Antwort des testrequests

Fazit

Ich persönlich bin jedesmal wieder begeistert, wie schnell eine API mit Spring Boot und VSCode zur Verfügung gestellt werden kann. Die Umsetzung dieses Beispiel nimmt, selbst wenn man nicht geübt ist, vielleicht 30 Minuten Zeit in Anspruch. Grade in Projekten, in denen ein schnell vorzeigbarer MVP gefordert wird, bietet sich diese Möglichkeit an. Zudem zeigt sich hier VSCode als ernstzunehmende Variante zu schwergewichtigen und teuer bezahlten IDEs.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.