Andreas Code Blog

Über Softwareentwicklung mit Web- und Java-Technologien

OpenAPI: Von der API-Dokumentation bis zum Code

Für die Implementierung von Schnittstellen ist eine gute Software-Dokumentation unerlässlich. Sie sollte alle notwendigen Informationen für Konsumenten einer API enthalten, den aktuellen Stand abbilden und einfach verständlich sein.

In diesem Artikel geht es um eine Möglichkeit der effektiven Schnittstellen-Dokumentation und wie sie auch zur Code-Generierung genutzt werden kann: OpenAPI.

 

Was ist OpenAPI?

Die OpenAPI-Spezifikation (OAS), früher auch bekannt als Swagger, definiert ein Beschreibungsformat für HTTP/REST-basierte APIs. Es ermöglicht die formale Beschreibung von:

  • Endpunkten und ihren Methoden (HTTP-Verben)
  • Ein- und Ausgabeparameter jeder Methode (z.B. Request-Parameter, Content-Header und Response-Payload)
  • Authentifizierungsmechanismen (z.B. Json Web Tokens oder OpenID Connect)

OAS ist Plattform- und Programmiersprachen-unabhängig, sodass es für Mensch und Maschine lesbar ist. Es ist z.B. möglich den Code für eine API, das mit OAS beschrieben wurde, mithilfe von Tools automatisiert generieren zu lassen. Dies ist sowohl für den Anbieter als auch für den Konsumenten einer API möglich.

Nachfolgendes Beispiel zeigt ein solches OpenAPI-Dokument. Das hier gewählte Darstellungsformat ist YAML (alternativ zu JSON):

openapi: 3.0.0
info:
  title: Workshop API
  version: 1.0
paths:
  /workshops:
    get:
      description: Verfügbare Workshops anzeigen
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Workshop'
components:
  schemas:
    Workshop:
      type: object
      description: Ein Workshop mit begrenzten Plätzen
      properties:
        titel:
          type: string
        verfuegbarePlaetze:
          type: number

Beschreibung: Das Dokument nutzt die OAS in der Version 3.0.0. Die API trägt den Titel „Workshop API“ und liegt in der Version 1.0 vor. Es gibt einen Endpunkt mit dem relativen Pfad „/workshops“, der die verfügbaren Workshops durch Verwendung der HTTP-Methode „GET“ ausgibt. Die Methode kann mit dem HTTP-Status-Code „200“ beantwortet werden. Weitere Status-Codes sind zu diesem Zeitpunkt unbekannt oder nicht definiert. Wird die Anfrage mit „200“ beantwortet kann der Response-Payload dem Media-Type „application/json“ entsprechen. Der Datentyp des Payloads ist hier ein „array“, alternativ zu primitiven Datentypen und JSON Objekten. Die Beschreibung der Elemente des „arrays“ ist wiederum als eigenes und wiederverwendbares Schema referenziert – hier ein „Workshop“ vom Typ JSON Object mit den beiden Feldern „titel“ und „verfuegbarePlaetze“ vom Typ „string“ und „number“. Die Beschreibung eines Schemas folgt der Definition des JSON Schema.

Code oder Dokumentation zuerst?

Bei der Implementierung von HTTP basierten APIs im Zusammenspiel mit OpenAPI unterscheidet man zwischen den folgenden zwei Ansätzen:

  1. Code-First: Zuerst wird der Code der API geschrieben. Danach entsteht das entsprechende OpenAPI-Dokument.
  2. Dokumentation-First: Zuerst wird das OpenAPI-Dokument der API geschrieben. Danach entsteht der entsprechende Code der API

Implementierung mit Code-First

Ein rudimentäres OpenAPI-Dokument und eine dazugehörige grafische und interaktive Benutzeroberfläche, allgemein bekannt als „Swagger-Ui“, lassen sich sehr schnell mit wenig Aufwand erstellen.

Dafür ist im Grunde nur der „springdoc-starter“ als Maven Dependency einzubinden:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>

Die Benutzeroberfläche lässt sich unter folgendem Endpunkt aufrufen:

/swagger-ui.html

Das Open-API-Dokument selbst ist abrufbar unter:

/v3/api-docs

Ohne Nacharbeit an der API-Implementierung ist die OpenAPI-Beschreibung etwas dürftig. Um dies zu ändern kann man mithilfe von Annotationen aus dem „io. swagger. v3.oas. annotations“-Package eine detailliertere Beschreibung der API vornehmen – inklusive verschiedenen HTTP-Status-Codes, Response-Modellen, Security-Mechanismen, etc.

Zum Deaktivieren der von Spring bereitgestellten Endpunkte für „swagger-ui“ oder die „api-docs“, kann man folgende Konfiguration in den Spring-Properties setzen:

springdoc.swagger-ui.enabled=false
springdoc.api-docs.enabled=false

Zusätzlich bietet das folgende Maven-Plugin die Möglichkeit, automatisch zur Build-Zeit eine aktuelle Version des OpenAPI-Dokuments unter dem Pfad „interfaces“ abzulegen. Somit erhält man eine Kopie des Dokuments, das neben dem produktiven Code im Git-Repository abgelegt und auch ohne laufende Anwendung genutzt werden kann:

<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.4</version>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <apiDocsUrl>.../v3/api-docs.yaml</apiDocsUrl>
        <outputFileName>openapi-generated.yaml</outputFileName>
        <outputDir>interfaces</outputDir>
    </configuration>
</plugin>

Implementierung mit Dokumentation-First

Hierbei entsteht die API-Dokumentation noch vor der Implementierung, z.B. im Rahmen der Anforderungsanalyse oder bei der Konzeption. Beteiligte Personen wie z.B. API-Anbieter und Konsumenten können mithilfe des Dokuments ihre Anforderungen an die gewünschte API sichtbar machen und als Diskussionsgrundlage nutzen.

Der Vorteil hierbei ist, dass das nun vorliegende OpenAPI-Dokument zur Code-Generierung genutzt werden kann. Ein populäres Tool, das verschiedene Programmiersprachen und Frameworks unterstützt ist der OpenAPI Generator (https://github.com/OpenAPITools/openapi-generator). Mit ihm kann man sowohl Client- und Serverseitigen Code generieren lassen.

Schauen wir uns zunächst einmal an was wir tun müssen, um den Code für die oben dokumentierte „Workshop-API“ serverseitig für Java und Spring zu generieren. Als erstes wird der Generator als Maven-Plugin in der pom.xml eingebunden:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.5.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>
                    ${project.basedir}/src/main/resources/api.yaml
                </inputSpec>
                <generatorName>spring</generatorName>
                <configOptions>...</configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

Über die Konfiguration teilen wir dem Generator mit, wo das OpenAPI-Dokument abgelegt ist („inputSpec“) und welcher Generator und damit welches Framework für die Codegenerierung genutzt werden soll („generatorName“). Weitere Generator-spezifische Konfigurationsparameter sind unter „configOptions“ zu setzen. Für Spring sind diese z.B. hier dokumentiert: https://openapi-generator.tech/docs/generators/spring.

Die Generierung des Codes kann mittels „maven compile“ angestoßen werden. Das Ergebnis lässt sich im Projektverzeichnis unter „target/generated-sources/openapi“ ansehen. Generiert wurden u.a. Spring-Controller-Klassen und Interfaces mit den dazugehörigen Endpunkten und Parametern aus dem OpenAPI-Dokument. Außerdem wurde für das in der Response festgelegte JSON-Objekt eine entsprechende Klasse mit sämtlichen Feldern sowie Getter- und Setter-Methoden generiert.

Entsprechend dem gewählten Generator enthält der generierte Code u.a. importierte Klassen aus dem Package „org.springframework“, „io.swagger.v3“, usw. Damit der Compiler diese Abhängigkeiten auflösen kann, müssen noch die entsprechende Maven-Dependencies importiert werden. Mittels weiterer „configOptions“ („useSpringBoot3“ oder „useJakartaEe“) lässt sich z.B. einstellen, dass statt dem „javax“ der neue „jakarta“ Namespace für die „Bean-Validation“-Imports verwendet werden soll. Der somit generierte Code kann nun genutzt werden, um die API mit Geschäftslogik zu verknüpfen.

Was muss als Konsument dieser API clientseitig getan werden? Dazu kann das gleiche Plugin genutzt werden. Auch hier wird wieder unter „input-spec“ der Pfad zum Open-API-Dokument angegeben werden. Außerdem muss man sich für einen Client-Generator entscheiden. Wir verwenden hier „Java“. Welche Java-Bibliothek tatsächlich genutzt werden soll kann über den Konfigurations-Parameter „library“ unter den üblichen „configOptions“ eingestellt werden. Unterstützt werden eine ganze Reihe verschiedener Java-Bibliotheken, wie zum Beispiel „Resttemplate“ und „Webclient“ aus dem Spring-Universum aber auch „Jersey“, „OpenFeign“ oder der „native HttpClient“. Wie zuvor lässt sich die Generierung mittels „maven compile“ ausführen. Die so generierten Klassen können anschließend im eigenen Code genutzt werden.

Ein vorbereitetes Codebeispiel zur Anwendung des Generators für Client- und Server-Code findet man hier: https://github.com/agmyrek/demo-openapi-spring.

Single Source of Truth

Welcher Ansatz ist nun der Richtige? Dazu möchte ich auf den folgenden Aspekt eingehen: Die „Single Source of Truth“. Beim „Dokumentation-First“-Ansatz entspricht das OpenAPI-Dokument, welches die Schnittstelle entsprechend dem Standard beschreibt, die „echte Quelle“. Das bedeutet, dass der Schnittstellen-Code, der später in der Code-Basis und damit in Produktion landet, von der Dokumentation abgeleitet wird. Die Dokumentation nimmt hier also einen sehr hohen Stellenwert ein und dient nicht mehr nur als Informationsquelle. Außerdem ist die zusätzliche Abhängigkeit zum Generator nicht zu vernachlässigen. Im Gegensatz dazu ist beim „Code-First“-Ansatz die „Single Source of Truth“ der eigens implementierte Code und nicht das OpenAPI-Dokument.

Fazit

Mithilfe von OpenAPI existiert eine formale und standardisierte Möglichkeit APIs zu beschreiben. Das OpenAPI-Dokument kann bereits in frühen Phasen der Anforderungsanalyse zur Konzeption und Abstimmung vor der Implementierung genutzt werden. Ein Synergieeffekt entsteht, wenn das Dokument analog dem „Dokumentation-First“-Ansatz auch später zur Code-Generierung genutzt wird. So kann Aufwand bei Implementierung gespart werden. Besonders dann, wenn es viele Konsumenten einer API gibt. Dabei muss allerdings die Abhängigkeit zum Code-Generator und damit die zusätzliche Komplexität sowie der erhöhte Wartungsaufwand in Kauf genommen werden. Der „Code-First“-Ansatz ist einfacher in der Umsetzung und vermeidet ungewollte Überraschungen durch den Code-Generator. Unabhängig vom gewählten Ansatz ist OpenAPI eine hervorragende und nützliche Form zur Dokumentation von APIs.

Referenzen

Openapi-Spezifikation: https://spec.openapis.org/oas/latest.html

OpenAPI-Generator Github: https://github.com/OpenAPITools/openapi-generator

Übersicht der Generatoren für Client und Server: https://openapi-generator.tech/docs/generators

Beispielcode: https://github.com/agmyrek/demo-openapi-spring