REST-API mit OpenAPI Spezifikation und Swagger generieren

REST-APIs können als OpenAPI Spezifikation menschen- und maschinenlesbar beschrieben werden. Die maschinenlesbare OpenAPI Spezifikation (vormals bekannt als Swagger Spezifikation) dient Code-Generatoren als Vorlage zum Generieren von Client- oder Server-Code in viele verschiedenen Programmiersprachen. Neben den Basics zeige ich euch hier, wie man den Code-Generator mit Maven verwendet, um API Implementierung und Spezifikation immer synchron zu halten.

API first & Code Generatoren

Beim API first Konzept geht es darum, dass man von Anfang an, die API im Scope der Entwicklung hat. Die API wird vor der Implementierung definiert und kann auch schon mit anderen Teams ausgetauscht werden, welche den Service über die API nutzen. Wird die API als OpenAPI Spezifikation im json oder yaml Format definiert, können mehrere Teams den Client-Code zeitgleich mit der Service-Implementierung entwickeln.

Als Unterstützung für den API first Ansatz gibt es Code Generatoren, die für euch ein Client SDK zum Benutzen einer API in diversen Programmiersprachen oder Server Stubs im gewünschten Technologie Stack generieren.

Das generierte Client SDK ist sicherlich eine gute Starthilfe, wenn ihr mit einer für euch neuen Programmiersprache arbeitet oder generell noch nicht so erfahren im Programmieren von REST-Aufrufen seid. Wie es ohne Code Generator mit OAuth2-Absicherung funktioniert, zeige ich z.B. hier: spring-oauth.html

Beim generierten Server Stub solltet ihr beachten, dass vom Generator der erste Technologie-Stack eures Services festgelegt wird. Dabei sollte man aus meiner Sicht vorsichtig sein, da der generierte Technologie-Stack häufig leicht veraltet ist und man z.B. mit Spring Boot, die Dependencies deutlich aufgeräumter pflegen kann. Daher bevorzuge ich bei einfachen APIs die Projekt Generatoren des gewählten Frameworks, z.B. https://start.spring.io/ für Spring.

Der generierte Code an sich ist je nach Programmiersprache gut lesbar (siehe z.B. Modell Klassen in Kotlin) oder voll von Boilerplate Code (siehe z.B. Modell Klassen in Java). Es ist sehr wichtig, dass ihr den generierten Code getrennt von eurem restlichen Code behandelt. Generierter Code sollte immer nur vom Generator geschrieben und nicht vom Entwickler angepasst werden. Daher hat dieser Artikel 2 Teile:

  1. Code Generatoren für Einsteiger: Swagger Editor, OpenApi & Swagger CodeGen. Probiert die Generatoren aus, schaut euch genau an was ihr bekommt und überlegt euch, ob das eurer Startpunkt für die Entwicklung sein soll oder nicht. Ein Versuch kann auch nicht schaden.
  2. Code Generatoren als Maven Plugin (in die CICD Pipeline integriert): In dieser Variante begeistern mich die Code Generatoren, da API Spezifikation und API Code immer synchron bleiben! Überspring ruhig die nächsten beiden Abschnitte, wenn es euch in erster Linie, um diese Variante geht.

Swagger Editor: Online Code Generator

Wenn ihr eine OpenAPI Spezifikation habt, könnt ihr euch den Client-Code bzw. ein Client SDK automatisch generieren lassen. Der schnellste Weg dazu ist der Swagger Editor, eine Webseite zum Bearbeiten von API Spezifikationen und zur Online Code Generation.

https://editor.swagger.io/

Einfach json oder yaml Datei links im Editor laden, dann sollten (im kompatiblen Browser) die beiden beiden Menu-Punkte "Generate Server" und "Generate Client" erscheinen mit denen ihr den Code in der Programmiersprache bzw. dem Framework (z.B. Spring oder Micronaut) eurer Wahl generieren könnt.

Swagger & OpenAPI Code Generator

Beim Swagger Editor hat man leider keine Möglichkeiten die Generation des Codes zu konfigurieren. Man kann nur die Programmiersprache oder das Framework auswählen, aber z.B. nicht in welchem Package sich die generierten Java-Klassen befinden sollen. Mit Swagger Codegen kann man das tun, siehe dazu https://github.com/swagger-api/swagger-codegen#readme.

Der OpenAPI Code Generator ist ein alternatives Projekt zum Generieren von Code anhand einer OpenAPI Spezifikation, das auf eine frühere Version des Swagger Code Generators aufsetzt. Weitere Details dazu findet ihr hier: https://github.com/OpenAPITools/openapi-generator/blob/master/README.md

In beiden Links findet ihr einen Link, um ein ausführbares jar-File herunterzuladen, mit dem ihr dann ein Client SDK oder einen Server Stub generieren könnt. Mit folgendem Befehl könnt ihr z.B. einen Kotlin Server Stub erstellen:

    java -jar openapi-generator-cli-6.2.1.jar generate \
        -i ./openapi.yaml \
        -g kotlin-server \
        -o petstore

  • java -jar führt eine ausführbare jar-Datei aus. Die openapi-generator-cli-6.2.1.jar ist in der zuvor verlinkten README.md verlinkt. (In Zukunft gibt es hier bestimmt neuere Versionen als 6.2.1, den Befehl dann entsprechen anpassen.)
  • generate ist der Befehl zum Generieren von Code. help wäre eine Alternative zum Anzeigen des Hilfetextes.
  • -i ./openapi.yaml zeigt an, wo die OpenAPI Spezifikation ist. Ich habe die Datei openapi.yaml einfach aus dem https://editor.swagger.io/ geladen, um dann die API des Demo Projektes Petstore zum Testen zu haben.
  • Mit -g kann man festlegen, was man generieren möchte. kotlin-server generiert einen Kotlin Server Stub. Alternativen und viele weitere Details findet ihr hier:
    https://github.com/OpenAPITools/openapi-generator/wiki/Server-stub-generator-HOWTO 
  • -o legt den Speicherort des generierten Codes fest.

Hier noch ein 2. Beispiel bei dem ich einen Spring Server Stub in Java generiere und zusätzlich eine Konfigurationsdatei config.json übergebe. In der Konfigurationsdatei habe ich exemplarisch das Package für die generierten Java-Klassen selbst definiert.

    java -jar openapi-generator-cli-6.2.1.jar generate \
        -i ./openapi.yaml \
        -g spring \
        -o ./petstore2 \
        -c ./config.json

    cat ./config.json
    
{"basePackage":"de.bsi.petstore"}

Weitere Konfigurationsparameter findet ihr in den zuvor verlinkten How-To Artikel oder im nächsten Abschnitt beim Einsatz als Maven Plugin.
Die jar-Datei des Swagger Code Generators wird übrigens analog bedient.
 

Code Generatoren als Maven Plugin

Der Swagger und der OpenAPI Code Generator können auch als Maven Plugin in den Build Prozess und in die CICD Pipeline integriert werden. Entscheidend dafür ist die Maven Konfiguration in der pom.xml Datei. In meinem Demo-Projekt zu diesem Artikel lasse ich die Modell Klassen anhand der Petstore OpenAPI Spezifikation generieren. Das funktioniert mit dieser Plugin-Konfiguration innerhalb der pom.xml:  

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>6.2.1</version>
    <executions>
        <execution>
	    <goals>
		<goal>generate</goal>
	    </goals>
	    <configuration>
		<inputSpec>${project.basedir}/src/main/resources/petstore-openapi.yaml</inputSpec>
		<generatorName>spring</generatorName>
		<configOptions>
		    <sourceFolder>petstore</sourceFolder>
		    <dateLibrary>java8</dateLibrary>
		    <useSpringBoot3>true</useSpringBoot3>
		</configOptions>
		<modelPackage>de.bsi.openapi.petstore.model</modelPackage>
		<generateModels>true</generateModels>
		<generateApis>false</generateApis>
		<generateSupportingFiles>false</generateSupportingFiles>
	    </configuration>
	</execution>
    </executions>
</plugin>
  • Wie bei Maven Dependencies legen groupId, atifactId und version das zu verwendende Plugin fest. Hier könnten wir auch als alternatives Plugin den Swagger Code Generator verwenden:
    <groupId>io.swagger.codegen.v3</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId><version>3.0.36</version>
    Das würde wenige, zusätzliche Anpassungen an den verwendeten Konfigurations-Parametern erfordern. Da der Swagger Code-Generator im Januar 2023 Spring Boot in der Version 3 nicht unterstützt, habe ich mich hier für den OpenAPI Code-Generator entschieden.
  • Mit useSpringBoot3 wird die Code Generierung kompatible zu Spring Boot Version 3 aktiviert.
  • Durch das Goal generate wird das Plugin in der generate-sources Phase des Maven Lifecycles ausgeführt, siehe auch https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html.
  • inputSpec verweist auf die OpenAPI Spezifikation und entspricht dem -i Parameter aus dem vorherigen Abschnitt.
  • generatorName legt fest was generiert werden soll und entspricht dem -g Parameter.
  • Innerhalb des target/generated-sources/openapi Verzeichnisses werden die generierten Klassen gespeichert. Mit dem Parameter sourceFolder wird noch ein weiterer Teil des Pfades spezifiziert, indem sich dann die package Verzeichnisse befinden.
  • modelPackage legt den package Namen der generierten Modell Klassen fest. Da ich nur Modell Klassen generiere, benutze ich hier nur diesen Parameter zum Festlegen von package Namen.
  • generateModels, generateApis und generateSupportingFiles definieren was generiert werden soll. In meinem Beispiel nur die Modell Klassen der OpenAPI Spezifikation, daher sind die beiden anderen Parameter auf false gesetzt.
  • Java 17 wird Stand Januar 2023 von beiden Code Generatoren unterstützt.
Die anderen Parameter und weiter führende Erklärungen zu den Maven Plugins findet ihr hier:

Für Eclipse habe ich den Maven Build mit diesen Goals durchgeführt: 
mvn clean package eclipse:eclipse
.

  • Die beiden Goals clean und package löschen zuerst die alten Builds und Bauen dann neu, dabei wird auch das Swagger Codegen Plugin ausgeführt. 
  • Mit eclipse:eclipse fügt ihr die generierten Sourcen innerhalb des target Verzeichnisses der Projekt Source Konfiguration hinzu, so dass die generierten Klassen in der IDE auch direkt genutzt werden können (das könnte man auch in den Projekt Properties im Java Build Path von Hand konfigurieren).
    In IntelliJ funktioniert das automatisch.

Weitere Dependencies und Controller

Die restlichen Dependencies in der Maven Konfiguration ergeben sich aus den generierten Klassen und dem von euch gewünschten Technologie-Stack. Mein Demo-Projekt zu diesem Artikel findet ihr hier: https://github.com/elmar-brauch/openapi-codegen

Mir war es wichtig, dass ich auf die Spring reactive Bibliotheken aufsetze, siehe dazu webflux.html. Damit die generierten Modell Klassen kompilieren, musste ich noch eine weitere Dependency aufnehmen: 
    <dependency>
        <groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.0.2</version>
    </dependency>
    <dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.4</version>
    </dependency>

Die Java-Klassen und JUnit Tests zum Demo-Projekt zeigen, dass selbst geschriebener Code und generierter Code nicht im Konflikt zueinander stehen. Wiederholtes Generieren der Modell Klassen macht keine Controller Klassen kaputt. Controller Klassen generiere ich nicht mit meiner Maven Plugin Konfiguration, da ich die Vorteile der reaktiven Programmierung nutzen möchte.

Fazit

Wenn ich Code Generatoren verwende sind mir folgende Punkte wichtig:

  • Moderner Technologie Stack, der zu den Bedürfnissen des Teams passt (und nicht zu 100% aus dem Code Generator abgeleitet ist).
  • Integration in die CICD Pipeline.
  • API Spezifikation und generierter Code müssen automatisiert synchron gehalten werden.
  • Keine händisches Anpassen des generierten Codes, um z.B. Compile-Fehler zu beseitigen.
Im hier gezeigten Demo-Projekt sind diese Punkte erfüllt, so dass ich mit dem Code Generator zufrieden bin. Allerdings gefallen mir auch einige Punkte nicht so gut:
  • Die hier generierten Java Modell Klassen haben unnötig viel Boilerplate Code. Ich fände es besser, wenn der Code Generator diese z.B. mit Lombok Annotationen oder Java 14 Records vermieden hätte.
Lohnt sich die Verwendung des Code Generators?
Ja, wenn ihr die OpenAPI Spezifikation zuerst erstellt (API first) und wenn eure Modell relativ viele Klassen hat. Die API Modelle der Microservices in meinem Team sind deutlich umfangreicher als beim hier gezeigte Petstore. Daher setzen wir die Code Generatoren auch in der Praxis ein.

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection