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

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

In Pocket speichern vorlesen Druckansicht
Hand die Schnüre greift

(Bild: Lightspring/Shutterstock.com)

Lesezeit: 16 Min.
Von
  • Oliver Glas
Inhaltsverzeichnis

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, 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.

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 einem Git Repository herunterladen und nutzt den OpenAPI Generator für Spring Boot, der ein harter Fork des Generators im Swagger Editor 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 und Consumer Driven Contract Testing.

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, 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:

  • Anpassungen an der API finden im OpenAPI-Dokument (OAS3, openapi.yaml) statt.
  • Der Build generiert mit einem Maven Plug-in Java Models und Interfaces aus dem OAS3 Dokument.
  • Springdoc erstellt beim Build automatisch die API-Dokumentation.
  • Eine Anpassung an der API führt zu einem erwarteten Fehler im Anwendungscode, bis dieser entsprechend angepasst wurde.
  • APIs antworten auch ohne Implementierung sofort mit statischen Beispielwerten (Statuscode 501 statt 200).
  • Da die APIs sofort erreichbar sind, werden sie auch sofort testbar (Shift-Left Testing).

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

(Bild: Oliver Glas)

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), 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:

  • useSpringBoot3=true Spring Boot Version 3.1.3 anstelle von 2.7
  • apiFirst=true Der Schalter legt API First fest und fügt in der Datei pom.xml das Maven Plug-in vom OpenAPI Generator hinzu. Dadurch kann bei jedem Build der Boilerplate Code wie Models und Interfaces neu erstellt werden. Unter Umständen muss man das Verzeichnis vorbereitung/bin/petstore_spring_apifirst/target/generated-sources/openapi/src/main/java noch zum Classpath hinzufügen bzw. in IntelliJ als Generated Sources Root markieren.
  • skipDefaultInterface=false Dieser Defaultwert erstellt Java Default Interfaces, die bereits API Requests mit statischen Beispielwerten beantworten. Diese werden nachher bei der Entwicklung im Controller mit @Override überschrieben. Das hat den Vorteil, dass die Anwendung sofort compile- und lauffähig ist, und die Methodensignaturen der Interfaces separat vom späteren Controller überschrieben werden.
  • returnSuccessCode=false Diese Einstellung ist für das Testen interessant ist: Die Java Default Interfaces antworten mit dem HTTP-Statuscode 501 "Not implemented". Man kann mit returnSuccessCode=true festlegen, dass die Anwendung mit HTTP-Code 200 antwortet, sodass der Stub sich zum Testen eignet.

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 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.