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

Seite 2: Spring Boot mit API First

Inhaltsverzeichnis

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.

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