Die Entwicklung von REST-APIs in Spring Boot-Anwendungen in einem Code-First-Ansatz hat inzwischen weite Verbreitung gefunden und nach nur wenigen Minuten kann ein erster REST-Endpunkt aufgerufen werden.
Deutlich weniger bekannt ist, wie man einen kompletten Entwicklungs-Roundtrip inklusive API-Client-Generierung und Integrationstests für eine solche API aufsetzt.
In diesem Blog Artikel zeigen wir, wie sich mit SpringDoc und OpenAPI Generator eine automatisierte Tool-Kette für die Entwicklung Spring-Boot-basierter REST-APIs realisieren lässt, die mühsame Entwicklungs- und Wartungsaufwände deutlich reduziert.
Wie in vielen modernen Frameworks ist es auch mit Spring Boot sehr leicht in einer Anwendung JSON-REST-Endpunkte bereitzustellen. Hierzu ist lediglich der Spring Boot Web Starter als Abhängigkeit einzubinden und eine Klasse mit @RestController sowie mindestens eine Methode mittels @RequestMapping (oder einer spezifischeren Annotation) als Handler für HTTP-Anfragen zu definieren.
In diesem Artikel stellen wir eine automatisierte Tool-Kette vor, die dabei hilft die Spring Boot REST-API-Entwicklung zu professionalisieren und dank der Automatisierung manuelle Aufwände zu sparen.
Code-First aber mit Spezifikation
In der API Community genauso wie in der Entwicklung jeder anderen Form von Schnittstellen, wird oft diskutiert, ob man zuerst eine Schnittstellen-Spezifikation schreiben und darauf basierend eine API implementieren (API-First), oder alternativ die Spezifikation aus der Implementierung generieren sollte (Code-First). Beide Ansätze haben definitiv ihre Berechtigung.
In diesem Artikel fokussieren wir uns auf Anwendungsfälle, in denen ein Code-First-Vorgehensmodell gewählt wurde, wie es oft mit Spring Boot gehandhabt wird. Wir klammern also einmal die Frage aus, ob man eher mit der Implementierung der REST-API oder mit einer davon unabhängigen Schnittstellen- / API-Dokumentation starten sollte.
Wie in der folgenden Abbildung gezeigt, wird bei dem Code-First-Ansatz zunächst eine Spring Boot Anwendung entwickelt (1), dann werden REST-Endpunkte erstellt und damit implizit eine API gestaltet (2) . Manchmal wird auch noch eine API-Dokumentation, beispielsweise im OpenAPI-Standard geschrieben (3) und hin und wieder auch ein API-Client erstellt (4).
Anwendungsbeispiel: User API
Für die leichtere Nachvollziehbarkeit der im Artikel beschriebenen Tool-Kette und die Übertragung in die eigene Entwicklung haben wir eine User API als Beispiel entwickelt.
Die API erlaubt es Benutzer zu erstellen, zu aktualisieren und mittels ID oder als vollständige Liste abzufragen. Hierfür werden REST-Operationen HTTP-konform mittels GET, POST, PUT oder DELETE bereitgestellt.
Während hier im Artikel die relevanten Code-Ausschnitte enthalten sind, ist der gesamte Code zu der Beispiel-API frei auf GitHub zugänglich: https://github.com/viadee/api-roundtrip-spring-boot
API-Roundtrip – Das Full-Service-Paket
Als API-Roundtrip bezeichnen wir die Entwicklung eines kompletten API-Pakets, das folgende Artefakte umfasst:
- REST-API
Die API selbst - API-Dokumentation
Eine API-Dokumentation als OpenAPI-Spezifikation - API-Client
Ein Client für die Nutzung der API, der leicht in konsumierende Anwendungen eingebunden werden kann - Integrationstests
Tests, die nach dem Deployment, beispielsweise in einer Testumgebung, automatisiert die Funktionsfähigkeit der API testen
Status Quo
Während es für andere Schnittstellentypen wie SOAP oder CORBA in der Vergangenheit recht etabliert war eine Dokumentation (WSDL) und vorgefertigte Clients bereitzustellen, so sind solche Artefakte für REST-APIs noch lange nicht selbstverständlich. Hierfür ist nicht zuletzt die in der Vergangenheit fehlende Automatisierung verantwortlich.
Für Integrationstests kommen heutzutage oft Werkzeuge wie Postman, SOAP UI oder REST Assured zum Einsatz. Sie alle haben gemein, dass sie die Möglichkeit bieten JSON-Anfragen an die REST-API zu schicken.
Da es hierbei notwendig ist JSON-Anfragen und Prüfungen der JSON-Antworten zu erstellen und zu warten, führt dieser Medienbruch zur ursprünglichen API-Entwicklung oft zu hohen manuellen Aufwänden.
Nicht selten bedeutet eine eher kleine Anpassung in der Spring Boot-Anwendung hohe manuelle und oft lästige Aufwände betrachtet auf das Gesamtpaket.
Automatisierung dank SpringDoc und OpenAPI Generator
Für eine effektive REST-API-Entwicklung, bietet sich eine automatisierte Tool-Kette an, die zum einen viele Arbeitsschritte automatisiert und zum anderen den Medienbruch bei der Integrationstest Entwicklung auflöst.
Letzteres wird insbesondere dadurch erreicht, wenn die Integrationstests, wie die Spring Boot-Anwendung selbst, in Java entwickelt werden können.
Die folgende Abbildung zeigt die Tool-Kette, wie wir sie in diesem Artikel aufbauen.
Zunächst wird eine OpenAPI Dokumentation mittels SpringDoc aus der Spring Boot-Implementierung der REST-API erzeugt. Im zweiten Schritt wird passender Client Code mit OpenAPI Generator auf Basis der API-Dokumentation generiert.
Die Implementierung der eigentlichen Testlogik erfordert natürlich nach wie vor manuellen Aufwand.
Im Detail besteht die Tool-Kette aus folgenden Werkzeugen:
- Spring Boot
Implementierung der REST-API (1) - SpringDoc
übernimmt dabei die Generierung der API-Dokumentation als OpenAPI Spezifikation inklusive Swagger UI auf Basis von Annotation in der Spring Boot-Anwendung. (2) - OpenAPI
Generator nutzen wir in einem zweiten Schritt, um aus der mittels SpringDoc generierten OpenAPI Spezifikation den Code für API-Client(s) und Integrationstests zu generieren. (3) - Maven
dient zur Zusammenführung der Werkzeuge und ihrer automatisierten Ausführung. Eine vergleichbare Kette ist natürlich auch mit Gradle denkbar. - Integrationstests
Wie die Integrationstests ausgeführt werden, hängt stark von der jeweiligen Umgebung ab. Beispielsweise können sie innerhalb der Deployment-Pipeline nach dem Deployment auch mit Maven oder Gradle angestoßen werden. (4)
API-Roundtrip – Die Tool-Kette im Detail
Spring Boot API Implementierung
Die User API wird von einem Interface mit entsprechenden Spring-Annotationen definiert. Bei der Namensgebung der Methoden empfiehlt es sich hier schon zu beachten, dass aus dem Methoden-Namen auch die zugehörige operationId in der OpenAPI Spezifikation und später der Methodenname im generierten Client abgeleitet wird.
Wie es sich gehört und für eine saubere OpenAPI Generierung hilfreich ist, wird explizit ein @ResponseStatus angegeben.
Die Implementierung des API Interfaces wird von einem Spring @RestController übernommen. Die Speicherung der User Daten haben wir der Einfachheit halber mit einer nicht-persistenten HashMap realisiert.
Fehlen nur noch die notwendigen Standard Maven-Dependencies und die Implementierung einer @SpringBootApplication und die API kann über http://localhost:8080/api/users aufgerufen werden.
Soweit alles noch Standard. Die eigentliche Roundtrip-Tool-Kette folgt ab hier.
API-Dokumentation Generierung
Während es bei SOAP Web-Services recht etabliert ist eine WSDL bereitzustellen, bzw. automatisch generiert zu bekommen, ist eine solche Infrastruktur bei REST-APIs im Code-First-Ansatz noch nicht out-of-the-box verfügbar.
SpringDoc füllt diese Lücke, indem es anhand der Spring Boot Infrastruktur, Interfaces und Annotationen automatisch eine OpenAPI Spezifikation und eine Swagger-UI Online Dokumentation generieren kann.
Um SpringDoc nutzen zu können, reicht folgende Maven-Dependency:
Hierdurch ist bereits sofort nach einem (Neu-)Start der Spring Boot-Anwendung ein Swagger-UI erreichbar: http://localhost:8080/swagger-ui.html
Ebenso kann auch eine OpenAPI Spezifikation abgerufen werden: http://localhost:8080/v3/api-docs
Beide Dokumentationen lassen sich umfangreich konfigurieren und ergänzen.
Für die inhaltliche Anreicherung der API-Dokumentation stellt SpringDoc verschiedene Annotationen, wie zum Beispiel die @OpenAPIDefinition zur Verfügung
Die Servers-Angabe empfiehlt sich an dieser Stelle, da wir später in der Tool-Kette zur Unabhängigkeit vom lokalen Netzwerk, die API kurz auf einem beliebigen freien Port starten werden und ohne explizite Angabe möglicherweise eine eher ungewohnte PORT Angabe in der API Spezifikation generiert werden würde.
Wenn bereits eine feste URL für die zukünftige Nutzung der API bekannt ist, dann sollte natürlich diese hier eingetragen werden.
Möchte man das JSON der OpenAPI Spezifikation durch ein "pretty-print" mit Einrückungen und Zeilenumbrüchen formatiert haben, so reicht folgende Konfiguration in der Spring Boot application.properties bzw. yaml Datei:
Außerdem lassen sich Swagger-UI und OpenAPI Dokumentation bei Bedarf auch unterhalb des actuator oder eines beliebigen anderen Kontextes platzieren.
Bleibt nur noch die Generierung einer lokalen OpenAPI Spezifikationsdatei, damit wir diese später für die Generierung des Client-Codes nutzen können. SpringDoc bietet hierfür ein eigenes Maven Plugin, womit es anderen vergleichbaren Werkzeugen wie Spring Fox einen Schritt voraus ist.
Die einzige zu beachtende Besonderheit hier ist, dass SpringDoc hierfür die "integration-test" Maven Lifecycle Phase nutzt, in der per Konfiguration kurz eine Spring Boot Anwendungen gestartet und dann die OpenAPI Spezifikation per HTTP heruntergeladen und als lokale Datei gespeichert wird. Hintergrund ist, dass die ganzen SpringBoot Abhängigkeiten nur zur Laufzeit geladen und aufgelöst werden und somit eine laufende Anwendung benötigt wird.
Damit dieses Zusammenspiel funktioniert, wird das SpringDoc Maven Plugin wie im folgenden Beispiel in die "integration-test"-Phase eingehängt.
Neben der Url für den Download der OpenAPI Spezifikation kann bei Bedarf auch noch die Zieldatei über outputFileName und outputDir definiert werden, worauf wir in dem Beispiel verzichtet haben, so dass die Datei per default unter target/openapi.json geschrieben wird.
Um sicherzustellen, dass die Spring Boot Anwendung erfolgreich während der "integration-test"-Phase gestartet werden kann, ermitteln wir zunächst mit dem codehaus build-helper-maven-plugin einen freien Port (${tomcat.http.port}) auf dem wir dann mit dem spring-boot-maven-plugin in der "pre-integration-test"-Phase die Spring Boot-Anwendung starten (<goal>start</goal>) und während der "post-integration-test"-Phase stoppen (<goal>stop</goal>).
Der dynamisch gewählte Tomcat Port sorgt dafür, dass die Maven Tool-Kette auch dann erfolgreich funktioniert, wenn auf dem Standard-Port 8080 bereits eine Anwendung läuft.
Soweit steht nun der erste Schritt in Form der Generierung einer API-Dokumentation. Als Nächstes widmen wir uns der Generierung des Codes für API Client und Integrationstests.
API Client und Integrationstest Generierung
OpenAPI Spezifikationen bieten eine detaillierte Schnittstellen-Beschreibung, aus der in einem zweiten Schritt unserer Tool-Kette Client Code generiert werden kann, der die REST-API Aufrufe als leicht nutzbare Java Schnittstelle bereitstellt.
Hierfür kommt OpenAPI Generator zum Einsatz.
OpenAPI Generator ist eine Ableitung des Swagger Code Gen Projektes und hat sich eine deutlich leichtgewichtigere und schnellere Weiterentwicklung zum Ziel gesetzt. Die bisher veröffentlichten Versionen und die aktive Community des OpenAPI Generators geben den Gründern bislang recht.
Inzwischen existieren über 130 verschiedene Code-Generatoren [code-generatore] sowohl für Client- als auch Server-Seite genauso wie für Dokumentation und andere Format-Standards wie GraphQL oder protobuf.
OpenAPI kommt mit einem Maven Plugin, so dass die Code Generierung direkt in den Gesamtworkflow integriert werden kann. Ein Plugin für Gradle steht ebenfalls zur Verfügung. Im Folgenden aber ein Konfigurationsbeispiel für das Maven-Plugin.
Das Beispiel zeigt eine Konfiguration, bei der der Java-Code-Generator genutzt wird. Zudem wird die java8 DateLibrary verwendet, was Datumsangaben mittels java.util.LocalDate realisiert.
Darüber hinaus werden die verschiedenen packages für die Code-Generierung gesetzt und der Generator so konfiguriert, dass er nur den Client Source Code und keine weitere Dokumentation oder Gradle und Maven Artefakte erzeugt.
Die Infrastruktur-Klassen müssen in der aktuellen Version von OpenAPI Generator leider noch explizit angegeben werden, da eine Feature-Anfrage für eine "Nur Client Code"-Einstellung noch offen ist.
Um, die Code-Generierung mit der Generierung der OpenAPI Spezifikation zu verheiraten, sind zwei Dinge zu beachten:
- Im Maven Parent Modul muss das Submodule für die API vor dem Submodule für die Client-/Integration-Test-Generierung platziert sein
- Der Pfad zur OpenAPI Spezifikation für den Code Generator (<inputSpec>) muss relativ auf den Pfad verweisen, an den die Spezifikation generiert wurde.
Damit der generierte Java-Client-Code mit der "native" Einstellung funktioniert, sind noch eine Reihe von Dependencies notwendig.
Eine minimale Konfiguration hierfür, die je nach verwendeten API-Features zu erweitern ist, steht hier auf GitHub bereit: https://github.com/viadee/api-roundtrip-spring-boot/blob/main/client/pom.xml
Integrationstest Ausführung
Wenn neben dem reinen Client Code daraus auch noch Integrationstests generiert werden sollen, hat sich je nach Setup bewährt diese Tests als Unit Tests zu implementieren, aber bewusst mit dem Maven failsafe und nicht mit dem klassischen surefire Plugin ausführen zu lassen.
Das failsafe Plugin gehört zu surfire, hat aber den Vorteil, dass es standardmäßig in der Maven intergration-test Phase mitläuft.
Zu beachten ist hierbei nur, dass das failsafe Plugin im Standard einen Namensfilter hat, welche Unit-Test-Klassen ausgeführt werden sollen. Diesen Filter haben wir im folgenden Beispiel um das Pattern *IntegrationTest.java ergänzt.
Außerdem sorgt die folgende Maven Konfiguration dafür, dass die Integrationstests nicht vom surefire Plugin während der normalen Testphase ausgeführt werden und der Integrationstest-Code weder deployed noch im Maven-Repository installiert wird.
In dem Beispielprojekt, das wir bereitstellen, haben wir den Integrationstest in ein eigenes Profil gefasst, damit sich beispielsweise besser steuern lässt, in welchem Schritt einer Deployment-Pipeline die Integrationstests durchzuführen sind.
Fazit
REST-APIs haben aufgrund ihrer Einfachheit eine große Verbreitung erreicht. Diese Einfachheit kommt aber auch mit vielen Herausforderungen, insbesondere für die Wartung und Nachhaltigkeit.
Hier hat gerade der OpenAPI Standard einen Meilenstein geschaffen und die darauf basierenden Generatoren und Integrationen schaffen Wege aus dem Wartungsdilemma.
Die im Artikel vorgestellte Tool-Kette basiert vollständig auf freien Standardwerkzeugen und macht sich die ausgereifte Spring Boot Infrastruktur für REST-APIs zunutze. Vergleichbare Automatisierungen können auch mit Frameworks wie Quarkus und Micronaut und ebenso für Clients in anderen Sprachen von JavaScript bis Python erstellt werden.
Für diejenigen, die aktiv die eigene Professionalisierung in der API-Entwicklung und im API-Management vorantreiben möchten, lohnt sich unserer Ansicht nach ein Blick auf OpenAPI, SpringDoc und OpenAPI Generator.
Moderne APIs sind eigenständige Produkte, die maßgeblich ganz neue digitale Geschäftsmodelle und Prozesse ermöglichen. Der immer bewusstere Umgang mit qualitativ hochwertigen APIs hilft dieses große Potential zu erschließen.
Ebenso helfen Automatisierungen, wie die im Artikel vorgestellte Tool-Kette, neue Qualitätsstandards mit deutlich weniger manuellem Aufwand zu erschließen, was auch Ansätze wie APIOps verfolgen.
Darüber hinaus bieten OpenAPI Spezifikationen die Basis von vielen weiteren Anwendungsfällen wie API-Kataloge oder API-Qualitätsanalysen für bessere, homogenere API-Produkte.
zurück zur Blogübersicht