c't 12/2021
S. 142
Wissen
OpenAPI
Bild: Albert Hulm

Dokumentationsverpackung

Große APIs nach dem Standard OpenAPI dokumentieren

Eine OpenAPI-Dokumentation beschreibt die Endpunkte eines REST-APIs. Wer große APIs ­programmiert, verstrickt sich aber schnell in einer riesigen und unhandlichen YAML-Datei. Zum Glück gibt es Werkzeuge, die große Dokumentationen sinnvoll aufteilen und Komponenten wiederverwenden.

Von Manuel Ottlik

APIs sind das Herzstück vieler servergestützter Anwendungen. Sie beliefern Webseiten, Desktop-Anwendungen und mobile Apps mit Daten. Für APIs, die auf dem Web-Protokoll HTTP basieren, hat sich das Paradigma REST durchgesetzt, das wir bereits ausführlich beschrieben haben [1]. Ein REST-Endpunkt setzt sich aus einem HTTP-URI (zum Beispiel https://api.example.org/things/) und der HTTP-­Methode (GET, POST, PUT, PATCH, DELETE) zusammen. Die HTTP-Methode beeinflusst, was mit den Daten passiert. Mit GET bezieht man Daten, DELETE löscht einen Datensatz.

Die Funktionsweise eines REST-APIs könnten sich die Nutzer fast selbst erschließen. Zum guten Stil als API-Entwickler gehört es aber, ein API in einer für Menschen und Maschinen lesbaren Form (in einer Interface Description Language, IDL) zu dokumentieren. Für REST-APIs ist OpenAPI das Dokumentationsformat der Wahl – ausführlich haben wir das in [2] bereits vorgestellt. Geschrieben in YAML oder JSON, erklärt eine OpenAPI-Dokumentation jeden Endpunkt: Das Open­API-Dokument verrät, welche Datenstruktur man ans API senden muss und welche Antwort man erwarten kann. Das Schöne an OpenAPI: Weil es maschinenlesbar ist, gibt es viele Werkzeuge, die die Dokumentation einlesen und damit Nützliches anstellen können. OpenAPI-Tools generieren Testfälle für Integrationstests, ansehnliche Dokumentationen für Nutzer oder sogar Gerüste des Programmcodes für Clients und Server. Eine Auflistung nützlicher Werkzeuge aus dem OpenAPI-Kosmos finden Sie in der Liste „Awesome OpenAPI“ über ct.de/yqw4.

OpenAPI aufteilen

Je größer und komplexer die APIs werden, je mehr Endpunkte sich ansammeln, desto länger wird auch die Beschreibung dieser Endpunkte. YAML ist ja nicht gerade dafür bekannt, besonders platzsparend gestaltet zu sein. So wie man auch Programmcode für eine größere Anwendung nicht in einer einzigen Datei runterschreiben möchte, wünscht sich manch API-Entwickler auch in seiner Dokumentation mehr Übersicht. Die Mechanismen zum Aufteilen von Open­API-Dokumentationen sind aber längst nicht so weit verbreitet wie bei gängigen Programmiersprachen.

Als Beispiel soll ein simples API dienen, das einen Raumplan bereitstellt. Das API verwaltet Räume, Gebäude und Etagen. Für jede der drei Ressourcen sind die für REST üblichen sogenannten CRUD-­Operationen (Create, Read, Update, Delete) implementiert. Komplett abdrucken können wir dessen OpenAPI-Dokumentation hier nicht. Denn schon dieses verhältnismäßig einfache API mit nur 3 Objekten bringt bereits 583 Zeilen YAML aufs Papier. Alle Beispiele zu diesem Artikel haben wir Ihnen in einem GitHub-Repository zusammengestellt (zu finden über ct.de/yqw4). Die unkomprimierte Datei liegt im Ordner 01-single-file im Repository.

Der Schlüssel zum Aufteilen ist das Schlüsselwort $ref, das OpenAPI vorsieht. Damit kann man auf Elemente in derselben Datei verweisen, aber auch andere Dateien einbinden und sogar Dateien aus dem Internet nachladen. Für Referenzen innerhalb derselben Datei nutzt man die JSON-Reference Syntax mit vorangestelltem Doppelkreuz, also etwa #/components/parameters/roomName:

paths:
  /rooms:
    get:
      description: get all rooms
      operationId: getRooms
      tags:
        - rooms
      parameters:
        - $ref: "#/components/«
                 »parameters/roomName"

Befindet sich die Ressource in einer anderen Datei oder auf einem anderen Server, müssen der Pfad oder die URL vor das Doppelkreuz gesetzt werden, etwa so:

parameters:
 - $ref: "room.yml#/components/«
                 »parameters/RoomName"
 - $ref: "https://example.org/room.yml#/components/«
               »parameters/RoomNumber"

Mit diesem Wissen können Sie die fast 600 Zeilen lange Datei entschlacken und für jede Ressource eine einzelne Datei erstellen (room.yml, building.yml, floor.yml), die jeweils parameters, responses und schemas enthält – letztendlich bleiben nur die tatsächlichen Endpunkte in der Haupt-Datei openapi.yml. Eine vollständig aufgeräumte Dokumentation finden Sie im Repository im Order 02-multiple-files. Wenn Sie noch einen Schritt weiter gehen möchten und das API wirklich groß ist, können Sie sogar die einzelnen Endpunkte der Ressourcen aus der Hauptdatei entnehmen und in die Dateien für die einzelnen Objekte auslagern.

Im Kasten unten sehen Sie, was dann noch in der Datei openapi.yml liegen bleibt: Metainformationen und Verweise auf andere Dateien. Das komplett entschlackte Beispiel finden Sie im Ordner 03-multiple-files-with-paths.

Standard-Komponenten auslagern

Wenn Sie mehrere APIs designen und implementieren, die ähnlichen Standards folgen, wird es auch projektübergreifende OpenAPI-Komponenten geben, die sich wiederholen – Kopieren und Einfügen ist da nicht der Weisheit letzter Schluss. Im aktuellen Beispiel erbt etwa jedes Objekt von dem Schema DefaultObject, weil dort die id sowie die zwei Zeitstempel createdAt und updatedAt zentral definiert werden:

schemas:
  DefaultObject:
    type: object
    description: default object
    properties:
      id:
        type: integer
        readOnly: true
        description: id of the object
        example: 1

Es bietet sich also an, diese wiederverwendbaren Komponenten an einer zen­tralen Stelle abzulegen, an der mehrere Spezifikationen von ihnen Gebrauch machen können. Je nachdem, wie Ihre Infrastruktur aufgebaut ist, haben Sie dafür mehrere Möglichkeiten: Sie könnten die YAML-Dateien mit den gemeinsamen Komponenten für alle API-Projekte zum Beispiel auf einen Webserver legen und dann per $ref mit einer URL darauf verweisen. Diese Strategie wird zuweilen in Unternehmen eingesetzt.

Wenn Sie allerdings größeren Wert auf Versionierung legen, bekommen Sie ein Problem: Immer wenn sich die Datei auf dem Server ändert, ändern sich automatisch alle Schnittstellenbeschreibungen all Ihrer APIs. Um zu verhindern, dass dadurch korrekte Dokumentationen plötzlich fehlerhaft sind, bietet sich ein Git-Repository an, in das Sie alle Komponenten legen und dann bei jeder OpenAPI-Spezifikation als sogenanntes Git Submodule einbinden. Das Submodule können Sie dann auf ein Versions-Tag festnageln. Wenn Sie noch nie von Git Submodules gehört haben, finden Sie unter ct.de/yqw4 einen Link zur Git-Dokumentation.

OpenAPI zusammenführen

Eine in mehrere Dateien zerlegte Open­API-Dokumentation hat allerdings einen gewissen Preis: Denn nicht alle Open­API-Werkzeuge können mit mehreren Dateien umgehen und die $ref-Verweise korrekt auflösen – deswegen sollten Sie die einzelnen Dateien zusammenfassen und als maschinenlesbare Variante ausliefern. Die einzelnen Dateien nutzen Sie zum bequemen Bearbeiten, die gerenderte Variante veröffentlichen Sie. Das hat gleich mehrere Vorteile: Jedes OpenAPI-Werkzeug kann mit der Spezifikation arbeiten, ohne Referenzen über unterschiedliche Dateien, vielleicht sogar URLs auflösen zu müssen. Außerdem verringert sich die Dateigröße der fertigen Datei, da nur die Abschnitte importiert werden, die tatsächlich in der Hauptdatei referenziert wurden. Die Dateigröße können Sie sogar noch weiter verringern, indem Abschnitte, die mehrfach importiert werden, nur beim ersten Mal in voller Länge importiert werden. An allen anderen Stellen wird dann per $ref auf das erste Vorkommen in der Datei gezeigt. Wenn die Werkzeuge, die Sie verwenden, überhaupt nicht mit der $ref-Notation umgehen können, können Sie die Bezüge auch komplett aus dem Dokument entfernen lassen, das Dokument also vollständig dereferenzieren.

Zusammenführung automatisieren

Keine Angst – die Zusammenführung müssen Sie nicht per Hand vornehmen. Das Werkzeug, mit dem die OpenAPI-Spezifikation wieder in einer Datei vereint wird, ist ein NPM-Paket und nennt sich swagger-cli (siehe ct.de/yqw4). Das Paket enthält ein Kommandozeilenwerkzeug, das Open­API validieren und zusammenführen kann. NPM-Nutzer können sich das Werkzeug lokal installieren und eine Open­API-Dateien lokal zusammenführen:

npm i -g @apidevtools/swagger-cli
swagger-cli bundle openapi.yml --outfile oas-rendered.yml--format yaml 

Statt das Werkzeug lokal zu installieren und per Hand auszuführen, sollten Sie die Aufgabe zügig einer CI/CD-Lösung übergeben. Dann können Sie den Arbeitsschritt nie vergessen und er funktioniert automatisch für alle, die an dem Projekt arbeiten.

Ein Rezept für GitHubs Automatisierungslösung GitHub Actions [3], die Sie automatisch benutzen können, wenn Sie Ihre Dokumentation bei GitHub ablegen, finden Sie im Beispiel-Repository in der Datei.github/workflows/handle-openapi.yml – im Kasten unten ist es leicht gekürzt abgedruckt. Nutzer von GitLab oder anderen CI/CD-Werkzeugen können den Code übernehmen und anpassen.

Der Workflow erledigt drei Dinge: Erst checkt er das Repository aus, danach führt er das OpenAPI mit swagger-cli bundle zusammen und stellt es im dritten Schritt als Zip-Archiv bereit. Der zweite Schritt definiert Umgebungsvariablen für Quell- und Zieldatei und legt das Ziel-­Dateiformat auf yaml fest (hier könnten Sie auch json eintragen). Im Abschnitt run: folgt die eigentliche Aktion mit swagger-cli. Damit das NPM-Paket nicht gesondert installiert werden muss, kommt der Befehl npx zum Einsatz, der NPM-Pakete direkt herunterlädt und ausführt.

Was Sie mit dem gerenderten Open­API-File anstellen, hängt von Ihrer Anwendung ab. Wenn Sie wie im nächsten Schritt zum Beispiel einen Docker-Container mit Ihrem API bauen oder ein Paket für eine Programmiersprache erzeugen, können Sie einfach Ihre Workflow-Schritte ergänzen. Sie arbeiten mit dem Dateisystem aus Schritt zwei weiter und können daher auf die fertige OpenAPI-Spezifikation zugreifen.

Im Beispiel wird sie mit der Aktion actions/upload-artifact@v2 zu einer Zip-Datei verarbeitet und bei GitHub abgelegt, damit man sie einsehen kann. Wenn Sie im Beispiel-Repository (Link siehe ct.de/yqw4) auf den Reiter Actions klicken, sollten Sie vier erfolgreiche Durchläufe sehen. Klicken Sie auf einen davon und laden Sie dann unter Artifacts das Zip mit dem Namen bundled-openapi herunter.

Für Mensch und Maschine

Schon bei kleinen REST-APIs nervt es gewaltig, durch hunderte Zeilen YAML zu scrollen und möglicherweise haben Sie OpenAPI schon für dieses Chaos verflucht. Mit $ref gibt es aber eine komfortable Möglichkeit, die Schnittstellenbeschreibung übersichtlich aufzuteilen. Bedenken, dass andere Werkzeuge damit nicht zwangsläufig klarkommen, müssen Sie nicht haben, wenn Sie die Rohdaten vor der Auslieferung wieder zu einer Datei zusammenführen. Ein optimaler Ausgleich zwischen Maschinenlesbarkeit und Entwicklerkomfort. (jam@ct.de)

OpenAPI-Werkzeuge und Beispiel: ct.de/yqw4

Kommentieren