zurück zum Artikel

API-First-Lifecycle in einem Spring-Boot-Projekt mit Maven

Oliver Glas
Hand die Schnüre greift

(Bild: Lightspring/Shutterstock.com)

API First schafft klare Strukturen in Webanwendungen über den gesamten Lebenslauf des Projekts hinweg. Anwendung und Clients lassen sich automatisiert erzeugen.

Schnittstellen komplett zu definieren, bevor die eigentliche Programmierung beginnt, hat Vorteile: Zum einen erreichen Entwicklerinnen und Entwickler mit dem API-First-Ansatz eine hundertprozentig exakte Beschreibung der API für die Dokumentation, was mit einem Code-First-Ansatz nicht zu erreichen ist.

Oliver Glas

(Bild: 

Oliver Glas

)

Oliver Glas arbeitet als Full-Stack-Entwickler in Zürich. Angefangen hat er mit der Programmierung auf dem C64. Professionell konzentriert er sich auf die Entwicklung von Webservices in verschiedenen Sprachen, darunter Go und Python. Schwerpunkt ist Java im Backend und Typescript im Frontend. Seit einigen Jahren hat er das Thema OpenAPI entdeckt. Eine gute Möglichkeit, sich im Opensource Bereich zu engagieren, und ab und an Vorträge oder Workshops zu leiten.

Zum anderen eröffnet sich mit API First eine Fülle von Tools [1], die wesentlich zur Zuverlässigkeit und Effektivität in einem IT-Projekt beitragen können. Weitere Vorteile im gesamten Lifecycle des Webservices erschließen sich erst, wenn man konsequent damit arbeitet. Bei Projekten für externe Kunden ist API First immer von Vorteil.

In diesem Artikel geht es um die Integration von API First mit OpenAPI 3 (OAS3) in die Entwicklungszyklen eines Webservice als Spring-Boot-3-Anwendung mit Maven. Hinzu kommen Erläuterungen der Parameter mit Hinweisen für ein vollständiges Customizing. Eine kurze Einführung ins Thema API First finden Interessierte im Ratgeber: API-Programmcode aus der OpenAPI-Dokumentation generieren [2].

Voraussetzung für das hier gezeigte Beispiel ist eine Spring-Boot-Entwicklungsumgebung mit Java SDK 17 und Maven. Mit entsprechenden Anpassungen würde das Beispiel auch mit Gradle und Spring Boot 2 funktionieren, Java 17 ist jedoch Voraussetzung für Spring Boot 3. Eine Entwicklungsumgebung wie IntelliJ oder Eclipse ist sehr hilfreich. Der Beispielcode lässt sich mit git clone von [3] eine [4]m Git Repository [5] herunterladen und nutzt den OpenAPI Generator für Spring Boot [6], der ein harter Fork des Generators im Swagger Editor [7] ist.

Der Fork war entstanden, da die Swagger betreibende Firma Smartbear einige Jahre den Sprung auf die aktuelle Version OAS3 verweigert hatte. Swagger generiert also veralteten Sourcecoode und erzeugt, Stand Januar 2024, immer noch eine Spring-Boot-2-Version der Anwendung mit Java 1.7.

Heise-Konferenz rund um APIs: betterCode() API

Die Online-Konferenz betterCode() API bietet einen Rundumschlag um das Thema API mit zwölf Vorträgen und zwei zusätzlichen Workshops, zu Konzepten, Entwurf, Design, Testen und Pflegen der Schnittstellen. Einige Vortragende stellen Beispiele aus der Praxis vor, und die Workshops zum Mitmachen und Einüben am 4. und 6. Juni widmen sich dem API Thinking [8] und Consumer Driven Contract Testing [9].

Highlights aus dem Programm sind:

Die Open-Source-Community pflegt den geforkten OpenAPI-Generator hingegen aktiv, ständig kommen neue Generatoren für neue Sprachen oder Frameworks heraus. Die Community behebt gemeldete Bugs schnell und implementiert regelmäßig neue Features. Der Swagger Editor selbst, ohne den veralteten Generator, ist für die Bearbeitung von OpenAPI jedoch sehr zu empfehlen.

Als OpenAPI-Dokument verwendet das Beispiel den üblichen Petstore [14], der ursprünglich ebenfalls von Smartbear stammt. Er wurde so angepasst, dass er Beispielwerte und nur JSON statt XML enthält.

Das Ziel des Projekts ist es, API First fest in den Lifecycle von Spring Boot einzubauen, um damit automatisiert eine hundertprozentig exakte Dokumentation der API zu erhalten. Auch mit Code First ist es möglich, die Dokumentation automatisiert zu erstellen (Stichwort Springdoc), jedoch ist das Risiko hoch, dass Springdoc und andere Libraries die API nicht hundertprozentig korrekt interpretieren. Insbesondere betrifft das Pfadparameter, die für Missverständnisse sorgen können. Eine präzise Dokumentation ergibt sich erst dann, wenn die Softwarearchitektinnen und -architekten zuerst die API definieren, um dann Interfaces sowie Models als technisch festgelegte Abhängigkeit in standardisierter Form im Sourcecode generiert haben.

Aus dem API-First-Ansatz ergibt sich folgender Ablauf: Entwicklerinnen und Entwickler nehmen Anpassungen zuerst im OpenAPI-Dokument vor. Der Build-Prozess generiert die entsprechenden Java-Interfaces und -Models automatisch. Wenn die API noch nicht vollständig oder nicht korrekt im Code implementiert wurde, soll der Compile-Prozess fehlschlagen.

Das OpenAPI-Dokument in Yaml oder JSON wird damit zum zentralen Bestandteil der Entwicklung, wobei der Generator beide Formate versteht. Es kann sowohl für Backend als auch für das Frontend verwendet werden. In einem Monorepo teilen sich Frontend und Backend das gleiche Dokument. Damit erhält man einen Single source of truth.

Bei den Begrifflichkeiten sollte man unterscheiden zwischen der Spezifikation OAS3 und dem OAS3-Dokument. Während die Spezifikation den OAS3-Standard beschreibt, ist die konkrete Definition einer API das OAS3-Dokument. In unserem Beispiel heißt dieses openapi.yaml, der Name ist jedoch frei wählbar.

Zusammenfassend ergeben sich folgende Eigenschaften des Spring-Boot-Projekts:

Schema API First

Bei API First entwerfen Entwicklerinnen und Entwickler erst die Stuktur einer Schnittstelle, bevor sie mit dem Programmieren beginnen.

(Bild: Oliver Glas)

API First im Vergleich zu Code First

Der Vorteil eines API-First-Projekts im Unterschied zu Code First: Die Dokumentation am Schluss wird automatisch und exakt erstellt.

(Bild: Oliver Glas)

Als Erstes wird die API designt, wobei es nicht erforderlich ist, dass das Dokument gleich im ersten Schritt fertig ist. Jedoch bleibt es integraler Bestandteil eines sich wiederholenden Entwicklungszyklus. Entwicklerinnen und Entwickler setzen neue Anforderungen nicht zuerst im Code um, sondern fügen sie in das OAS3-Dokument (hier: openapi.yaml) ein. Die generierten Interfaces und Models bilden immer eine exakte Abbildung der OAS3-Dokumentation.

Das Beispiel-Projekt nutzt ein vorhandenes Dokument, das als Petstore-Beispiel heruntergeladen wird. Vor dem ersten Entwicklungszyklus kann man aus dem Entwurf des Dokumentes einen Stub erstellen: Eine initiale funktionsfähige Anwendung, bei der noch keinerlei Businesslogik dahintersteht.

Der aus der OpenAPI-Dokumentation erzeugte Stub dient hier als Vorlage (anstelle von start.spring.io [16]), um daraus die eigentliche Anwendung zu entwickeln. Der Vorteil gegenüber start.spring.io ist, dass alle definierten APIs, Annotationen für die automatisierte Erstellung der OAS3-Dokumentation sowie das Maven Plug-in für den OpenAPI Generator in der pom.xml von Anfang an hinzugefügt wurden. Das Plug-in enthält den gleichen Generator wie die für den Stub verwendete jar Datei.

Bei der späteren Integration von API First in den Entwicklungsprozess wird der Code bei jeder Anpassung an der API neu generiert – anders als beim Stub aber nur die Interfaces und Models.

Der Stub bietet auch den Vorteil, dass man die Anwendung direkt nach dem Design der API testen kann (Shift-Left). Das Maven Plug-in wird so eingestellt, dass eine Anpassung der API in openapi.yaml dazu führt, dass die Interfaces des Controllers nicht mehr korrekt implementiert werden und zu einem Kompilierfehler führen. Das stellt sicher, dass Entwicklerinnen und Entwickler bei jeder wesentlichen Änderung der API auch den Code entsprechend anpassen müssen. Der vom Plug-in generierte Code findet sich per Default im Build-Verzeichnis target, und ist dort gut aufgehoben, da target nicht ins Git Repository eingecheckt wird. Ansonsten würde jeder Build dazu führen, dass alle generierten Dateien neu eingecheckt werden, obwohl sich nur das Erstellungsdatum geändert hat. Bei Bedarf kann der Pfad auch mit dem Parameter <output> im Maven Plug-in angepasst werden.

Nach dieser theoretischen Übersicht geht es nun los mit dem praktischen Teil, dem API-Dokument und dem Stub. Nach dem Klonen des Beispielprojekts von GitHub finden sich die Dateien für die Vorbereitung im gleichnamigen Unterverzeichnis vorbereitung. Die beiden checkfiles (.sh für Linux, .ps1 für Windows) laden den Generator und das OpenAPI-Dokument (Petstore) herunter. Das Skript generate.* generiert den Stub. Beim Generieren definieren im Skript die Schalter -i.* die Input-Datei (openapi.yaml), -g.* den Generatornamen (spring) und -o.* das Output-Verzeichnis. Man sollte für den Stub mit -additional-properties.* zusätzliche Parameter definieren, wie etwa die Java-Paketnamen (Zeilen 3-8 im Skript). Damit der Stub nach dem Generieren sofort lauffähig ist, sollte skipDefaultInterface=false.* gesetzt sein, andernfalls müssten die Methoden vom Interface erst im Controller implementiert sein, um kompilieren zu können.

Hier eine Beschreibung der wichtigsten Parameter:

Da das Skript das generierte Verzeichnis petstore_spring_apifirst jedes Mal zuerst löscht, können Anwenderinnen und Anwender hier mit den Parametern herumspielen. Am Schluss verschiebt man den gewünschten Code mit dem Verzeichnis petstore_spring_apifirst in das Projektverzeichnis und verwendet es als Spring-Boot-Anwendung. Das spart das Generieren mit start.spring.io [17] und stellt das richtige Maven-Plug-in in pom.xml zur Verfügung, um mit API First zu arbeiten.

Im Beispiel-Repository liegt der zu bearbeitende Stub im Unterverzeichnis app (Monorepo Style). Dort sind noch Anpassungen nötig, um eine passende API-First-Implementierung zu erhalten. Aus diesem Stub wird dann die eigentliche Anwendung weiterentwickelt.

Nun hat man einen Spring-Boot-Stub, der alle in openapi.yaml definierten API Endpunkte enthält. mvn spring-boot:run startet die Anwendung. Springdoc erstellt automatisch das zugehörige OAS3-Dokument und stellt es unter http://localhost:8080/swagger-ui/index.html als Swagger UI zur Verfügung.

Damit hat man jedoch API First noch nicht integriert. Ist die Additional Property skipDefaultInterface=false gesetzt, antworten die APIs bereits mit den eingetragenen Beispielwerten aus openapi.yaml. Daher ist auch das Verwenden des Felds example in openapi.yaml immer zu empfehlen. Auch andere Tools wie Postman verwerten die Beispielwerte. Entwicklerinnen und Entwickler können nun die API z.B. mit Postman testen und bekommen den HttpStatus.NOT_IMPLEMENTED zurück (= 501).

Der Statuscode 501 ist sinnvoll, da dies dem aktuellen Entwicklungsstand entspricht. Der Controller muss nicht sofort alle Methodensignaturen vom Interface überschreiben. Das Beantworten von Requests übernimmt, solange die Klasse ApiUtil.java (die dann später überflüssig wird) mit statischen Beispielwerten aus der openapi.yaml. Erst wenn die Methoden im Controller implementiert wurden, setzt man damit den Statuscode auf 200 bzw. 201.

Nun kommt die Kontinuität ins Spiel: Denn haben Entwicklerinnen und Entwickler einmal die Parameter in openapi.yaml definiert und angefangen die eigentliche Anwendung zu entwickeln, ändern und erweitern sich die Anforderungen an die API in Zyklen. Dabei wäre es unpraktisch, mit dem Generieren von vorn zu beginnen und die bereits implementierte Logik im Controller wieder zu überschreiben. Auf der anderen Seite soll openapi.yaml dem tatsächlichen Stand der Anwendung entsprechen und Single Source of Truth bleiben. Das kann man mit einer kontinuierlichen Integration in zwei Schritten erreichen:

Als Erstes kopiert man openapi.yaml in das resources-Verzeichnis von Spring Boot, sodass sie im Classpath enthalten ist und dem Maven Plug-in zur Verfügung steht. Zum Zweiten verwendet der OpenAPI-Generator das Maven Plug-in. Durch den Parameter apiFirst=true in den additional-properties ist dieses bereits beim Stub in pom.xml vorhanden und muss nur noch angepasst werden. Im Plug-in findet die eigentliche Integration von API First statt.

Ähnlich wie beim Stub gibt es Additional Properties, die in pom.xml <configOptions> heißen:

  1. In <inputSpec> sollte der Pfad zu der zu verwendenden openapi.yaml bereits korrekt angegeben sein.
  2. Der Parameter skipDefaultInterface ist für die erste Entwicklungsphase nützlich, da sonst der Code wegen der fehlenden Implementierung der Interfaces nicht kompilierbar wäre. Später lässt sich das auf <skipDefaultInterface>true</skipDefaultInterface> umstellen. Das ist deshalb zu empfehlen, weil die Default Interfaces die Klasse ApiUtil.java benötigen, die von manchen statischen Sourcecode-Scannern als Sicherheitslücke angesehen werden. Nach der Umstellung sollten Anwenderinnen und Anwender diese Klasse löschen.
  3. Die Paketnamen für die Models und Interfaces definieren die Parameter <apiPackage> und <modelPackage>.
  4. Ist Spring Boot 3 gewünscht, gibt es den Parameter <useSpringBoot3>, andernfalls bleibt es bei Version 2.7.x.
  5. Wichtig ist noch <interfaceOnly>true</interfaceOnly>, damit die nun manuell gepflegten Controller-Klassen in der Anwendung nicht mehr generiert werden.
  6. <generateSupportingFiles>false</generateSupportingFiles> verhindert das Generieren von Controllern, pom.xml und ähnlichem.

In der IDE müssen Entwicklerinnen und Entwickler das entsprechende Verzeichnis noch zum Classpath hinzufügen, oder in IntelliJ den Pfad target/generated-sources/openapi/src/main/java als Generated Sources Root hinzufügen.

Beispielprojekt in GitHub

Interessierte finden die Skripte des Beispielprojekts in einem GitHub-Repository.

Ein Anpassungszyklus der API-First-Anwendung beginnt beim OpenAPI und der Datei openapi.yaml. Dann erfolgt der Build mit mvn clean compile oder auch mit mvn clean generate-sources, wobei Letzteres im mvn compile enthalten ist. Erst jetzt passen die Entwicklerinnen und Entwickler die Controller-Klassen und die Businesslogik an.

Der Nachteil bei API First ist, dass der automatisch generierte Code manchmal nicht den eigenen Bedürfnissen entspricht. Es gibt zwar Konfigurationsmöglichkeiten, jedoch möchten Programmiererinnen und Programmierer beispielsweise oft dem Java-POJO Methoden hinzufügen oder Getter/Setter weglassen. Außer einzelne Dateien vom Generieren auszunehmen, bietet der OpenAPI Generator weitere Möglichkeiten, Code anzupassen. So lassen sich Mustache Templates herausziehen, oder sogar eigene Generatoren schreiben [18]. Diese Themen gehen jedoch über den Rahmen dieses Artikels hinaus.

Zusätzlich einen Client zu generieren ist nicht weiter schwierig. Im Maven-Plug-in erstellt man dafür ein weiteres Tag <execution> mit einer eindeutigen ID. Im Plug-in wird unter <executions> eine weitere <execution> hinzugefügt. Zum Beispiel <execution><id>sampleclient</id></execution>. Nun folgt die komplette weitere Konfiguration, je nach Client sowie gegebenenfalls in <inputSpec> ein weiteres OpenAPI-Dokument.

API First ist nun fest in den Lifecycle der Spring-Boot-Anwendung implementiert. Das garantiert technisch, dass die API-Dokumentation zu hundert Prozent exakt und aktuell ist. Es gibt auch im Team keine Missverständnisse mehr, ob etwa ein Parameter entweder ein String mit Y/N oder ein Boolean-Wert ist. Das Team kann jederzeit auf die Dokumentation verweisen und muss sich nicht explizit um das Updaten der Dokumentation kümmern.

Das im Artikel beschriebene ist nur ein kleiner Teil dessen, was der OpenAPI Generator leistet. Das Open-Source-Projekt ist sehr spannend und dynamisch mit großer und aktiver Community. Es bietet eine gute Gelegenheit für Entwicklerinnen und Entwickler, sich an dem sinnvollen Projekt zu beteiligen. Der Sourcecode ist in Java mit Mustache-Templates geschrieben. Auch das Entwickeln neuer Generatoren ist nicht schwierig.

Insgesamt gibt es viele weiße Flächen auf der Landkarte von OpenAPI, die noch zu befüllen sind. Nur als Beispiel: Im oben beschriebenen OpenAPI Generator gibt es einen Datenbank-Schema-Generator für MySQL, aber nicht für PostgreSQL. Es gibt einen Generator für Angular, aber nicht für React. Der Python FastAPI Generator ist noch Beta und unvollständig, Django fehlt völlig. Hier bieten sich schöne Möglichkeiten für Entwicklerinnen und Entwickler, sich das eigene Portfolio mit einem großen Opensource-Projekt zu bereichern. Es braucht circa drei Commits, um in der Liste der Mitentwickelnden zu erscheinen.

(who [19])


URL dieses Artikels:
https://www.heise.de/-9639880

Links in diesem Artikel:
[1] https://openapi.tools/
[2] https://www.heise.de/ratgeber/Anleitung-API-Programmcode-aus-der-OpenAPI-Dokumentation-generieren-4974001.html
[3] https://github.com/networkinss/hello-apifirst-springboot.git
[4] https://github.com/networkinss/hello-apifirst-springboot.git
[5] https://github.com/networkinss/hello-apifirst-springboot.git
[6] https://openapi-generator.tech/docs/generators/spring
[7] https://editor.swagger.io/
[8] https://api.bettercode.eu/veranstaltung-21988-se-0-api-thinking-entwerfen-sie-effektive-und-nutzerfreundliche-apis.html
[9] https://api.bettercode.eu/veranstaltung-22090-se-0-consumer-driven-contract-testing-fuer-microservice-landschaften.html
[10] https://api.bettercode.eu/veranstaltung-22010-se-0-aktueller-stand-der-api-entwicklung-veraenderung-potenzial-und-ausblicke.html
[11] https://api.bettercode.eu/veranstaltung-22011-se-0-api-versioning-fallstricke-und-loesungsansaetze.html
[12] https://api.bettercode.eu/veranstaltung-21915-se-0-advanced-api-design-for-scalable-and-fault-tolerant-data-intensive-distributed-system.html
[13] https://api.bettercode.eu/veranstaltung-21922-se-0-rest-apis-from-hell---wahre-geschichten-ueber-misserfolge.html
[14] https://raw.githubusercontent.com/networkinss/SampleOpenAPICollection/master/petstore/petstore_oas3_inss.yaml
[15] https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-maven-plugin/README.md
[16] https://start.spring.io/
[17] https://start.spring.io/
[18] https://openapi-generator.tech/docs/customization
[19] mailto:who@heise.de