Datum
December 10, 2024
Kategorie
API
Lesedauer
15 Min.

Eine API kommt selten allein: Mit Arazzo API-Workflows definieren

APIs sind aus der heutigen digitalen Wirtschaft nicht mehr wegzudenken. Schon 2019 haben API-Aufrufe 83 Prozent des Internet-Traffics ausgemacht, größtenteils von individuellen Anwendungen in der Cloud – ganz klar eine Folge digitaler Transformation. Es ist in keiner Weise von einem rückläufigen Trend auszugehen. Im Gegenteil, die API-Economy entwickelt sich weiter rasant und wir können davon ausgehen, dass APIs zu den Schlüsselfaktoren für den Erfolg digitaler Geschäftsmodelle zählen. Durch die Möglichkeit zur Bereitstellung von Daten und Funktionen sind APIs Wegbereiter digitaler Öffnung. Wer sich öffnet, bekommt die Chance auf Bildung eines Ökosystems oder einer Plattform:

APIs erlauben es, ein umfassendes Ökosystem zu schaffen, in dem verschiedene Partner, Entwicklerinnen und Entwickler sowie Drittanbieter eingebunden und ermutigt werden, beispielsweise eigene Anwendungen und Dienste zu beizusteuern. Neue Dienste können entwickelt und die Funktionalität der gesamten Plattform kann erweitert werden. Interoperabilität wird vereinfacht und es entstehen zusätzliche Einnahmequellen auf dem Weg zu Innovation und Flexibilität. Das kann auch die Einbindung fortschrittlicher Technologien wie künstlicher Intelligenz umfassen. Das vielleicht beste Beispiel dafür sind die zahlreichen Dienste, die rund um die APIs von OpenAI entstehen.

Es zeigt sich also, dass in geschäftlicher Hinsicht der Mehrwert nicht durch eine einzelne API allein geschaffen wird. Integration lautet das Zauberwort: Mehrere APIs werden miteinander verbunden und koordiniert, um ein nahtloses und effizientes System zu schaffen, in dem Daten und Funktionen über Systemgrenzen hinweg genutzt werden können.

Nahtlos? Wie geht denn das? Wie kann ich das darstellen und beschreiben? Bis vor kurzem war diese Frage tatsächlich gar nicht so einfach und ohne Weiteres zu beantworten.

Für eine individuelle API liegt die Sache absolut klar: Mit OpenAPI (früher bekannt als Swagger) haben wir eine standardisierte Spezifikation für die Beschreibung von Web APIs zur Verfügung. In menschen- und maschinenlesbarer Form können wir beschreiben wie die API funktioniert, welche Endpunkte existieren, welche Parameter benötigt und welche Datenformate verwendet werden. Mit anderen Worten: OpenAPI macht es uns möglich, genau zu beschreiben, was eine API bietet und wie sie funktioniert – in der Regel auf der Ebene einzelner Endpunkte.

Erfordert die Funktionalität das Ineinandergreifen mehrerer Endpunkte, um geschäftlichen Mehrwert zu erzielen, stoßen wir an Grenzen. Wir müssen einen Workflow abbilden, können das mit OpenAPI allein jedoch nur, indem wir die Beschreibung der einzelnen Endpunkte mit Prosa anreichern, so dass ein dazugehöriger Ablauf nachvollziehbar wird.

Beispiel: Datei-Upload und Verarbeitung

Schauen wir uns das anhand eines einfachen Beispiels an.

Wir stellen uns vor, wir möchten irgendeine Datei hochladen, um diese später weiterverarbeiten zu lassen – sei es für eine Bildkomprimierung, die Texterkennung in einem Scan oder das Konvertieren eines Formats. Dieser Prozess lässt sich mit APIs einfach automatisieren und elegant orchestrieren.

Im Folgenden schauen wir uns Schritt für Schritt an, wie ein solcher Workflow spezifiziert und umgesetzt werden kann. Er umfasst die folgenden Schritte:

  1. Datei hochladen
  2. Datei verarbeiten
  3. Verarbeitungsstatus abrufen

Eine einfache OpenAPI-Spezifikation für eine API mit der beschriebenen Funktionalität könnte im YAML-Format beispielsweise so aussehen:

1openapi: 3.0.0
2info:
3  title: Datei-Verarbeitungs-API
4  version: 1.0.0
5  description: |
6    Diese API ermöglicht die Automatisierung eines Workflows
7    für den Datei-Upload, die Verarbeitung und die Statusabfrage. 
8    Ein typischer Workflow umfasst folgende Schritte:
9    
10    1. **Datei hochladen**: Eine Datei wird über den `/upload`-Endpunkt
11    hochgeladen. Die API gibt eine eindeutige Datei-ID zurück.
12    2. **Datei verarbeiten**: Die hochgeladene Datei wird über den
13    `/process`-Endpunkt verarbeitet. Der Verarbeitungsstatus wird zurückgegeben.
14    3. **Verarbeitungsstatus abrufen**: Mit dem `/status/{fileId}`-Endpunkt
15    kann der aktuelle Status der Datei abgefragt werden.
16
17    Beispiel-Workflow:
18    - Eine Datei wird hochgeladen.
19    - Der Workflow wartet, bis die Datei erfolgreich verarbeitet wurde.
20    - Am Ende wird eine Bestätigung über den Abschluss des Prozesses
21    zurückgegeben.
22
23paths:
24  /upload:
25    post:
26      summary: Datei hochladen
27      description: |
28        Lädt eine Datei hoch und gibt eine eindeutige Datei-ID
29        zurück, die für die weiteren Schritte verwendet wird.
30        Der erste Schritt im Workflow ist der Datei-Upload.
31        Diese Datei-ID ist notwendig für die Verarbeitung und Statusabfrage.
32      operationId: uploadFile
33      requestBody:
34        required: true
35        content:
36          multipart/form-data:
37            schema:
38              type: object
39              properties:
40                file:
41                  type: string
42                  format: binary
43                  description: Die Datei, die hochgeladen werden soll.
44      responses:
45        '200':
46          description: Datei erfolgreich hochgeladen.
47          content:
48            application/json:
49              schema:
50                type: object
51                properties:
52                  fileId:
53                    type: string
54                    description: Eindeutige ID, die die
55                    hochgeladene Datei identifiziert.
56              example:
57                fileId: "abc123"
58        '400':
59          description: Ungültige Anfrage, z. B. fehlende Datei.
60  /process:
61    post:
62      summary: Datei verarbeiten
63      description: |
64        Startet die Verarbeitung einer hochgeladenen Datei
65        anhand ihrer ID.
66        Dieser Schritt im Workflow wird ausgelöst, sobald der
67        Upload abgeschlossen ist. Die Verarbeitung erfolgt asynchron,
68        und der Verarbeitungsstatus kann später über den `/status`-Endpunkt abgefragt werden.
69      operationId: processFile
70      requestBody:
71        required: true
72        content:
73          application/json:
74            schema:
75              type: object
76              properties:
77                fileId:
78                  type: string
79                  description: Die ID der zu verarbeitenden Datei.
80            example:
81              fileId: "abc123"
82      responses:
83        '200':
84          description: Datei erfolgreich zur Verarbeitung eingereicht.
85          content:
86            application/json:
87              schema:
88                type: object
89                properties:
90                  status:
91                    type: string
92                    enum: [processing, completed, failed]
93                    description: Status der Verarbeitung.
94              example:
95                status: "processing"
96        '400':
97          description: Ungültige Anfrage, z. B. fehlende Datei-ID.
98  /status/{fileId}:
99    get:
100      summary: Verarbeitungsstatus abrufen
101      description: |
102        Gibt den aktuellen Status der Verarbeitung einer Datei zurück.
103        Dieser Schritt im Workflow wird verwendet, um den Abschluss des
104        Verarbeitungsprozesses zu prüfen. Sobald der Status "completed"
105        erreicht ist, ist die Datei vollständig verarbeitet.
106      operationId: getProcessingStatus
107      parameters:
108        - name: fileId
109          in: path
110          required: true
111          schema:
112            type: string
113          description: Die ID der Datei, deren Verarbeitungsstatus
114          abgefragt werden soll.
115      responses:
116        '200':
117          description: Der aktuelle Verarbeitungsstatus der Datei.
118          content:
119            application/json:
120              schema:
121                type: object
122                properties:
123                  status:
124                    type: string
125                    enum: [processing, completed, failed]
126                    description: Der aktuelle Status der Datei.
127              example:
128                status: "completed"
129        '404':
130          description: Datei nicht gefunden.
131          content:
132            application/json:
133              schema:
134                type: object
135                properties:
136                  error:
137                    type: string
138                    description: Fehlerbeschreibung.
139              example:
140                error: "Datei nicht gefunden."

OpenAPI für die Beschreibung von API-Workflows?

Obwohl OpenAPI grundsätzlich maschinenlesbar ist – in manchen Fällen sogar von Maschinen besser gelesen werden kann als von Menschen – stellen wir fest, dass das Beispiel oben eigentlich nur noch (bestenfalls) menschenlesbar ist. Die Informationen zur Abfolge von Requests können wir der Dokumentation zwar entnehmen, jedoch ist die Beschreibung des Workflows in keiner Weise standardisiert. Und damit ist diese Spezifikation für die Beschreibung des Workflows oder für die Dokumentation eines Integrationsszenarios nur eingeschränkt hilfreich.

Und stellen wir uns dann vor, dass unsere API nicht nur genau die Endpunkte enthält, die wir für unseren kleinen Beispiel-Workflow benötigen, sondern stattdessen eine Vielzahl von Endpunkten für die unterschiedlichsten Workflows, stößt diese Art der Beschreibung sofort an ihre Grenzen und ist ganz schnell auch für Menschen nicht mehr hilfreich.

Arazzo für die Beschreibung von API-Workflows!

An dieser Stelle kommt Arazzo als neue Spezifikation der OpenAPI Initiative für die Beschreibung von API-Workflows ins Spiel. Das Ziel des neuen Standards ist die maschinenlesbare Beschreibung von API-Workflows. Sie orientieren sich an Use Cases und sind somit eine Reihe von API-Aufrufen, die gemeinsam Mehrwert schaffen und Geschäftszielen dienen.

Mit anderen Worten: Mit Arazzo spezifizierte Workflows sind deterministische Rezepte für die Verwendung von APIs. Wir können genau ausdrücken, wie sie verwendet werden sollen. Diese Art der expliziten Informationen kommt früher oder später auch diversen KI-Tools zu Gute. Die exakte Beschreibung von notwendigen Schritten, die für einen Anwendungsfall aus einem Geschäftskontext erforderlich sind, kann von künstlicher Intelligenz abgearbeitet werden und dadurch den Mehrwert des geschäftlichen Anwendungsfalls bieten.

Oder wir können uns von Code-Generierung auf Grundlage von OpenAPI-Spezifikationen verabschieden. Bisher waren die Ergebnisse generierte Clients, die die Operationen aus der API-Dokumentation 1:1 bereitgestellt haben. Wie oder in welcher Reihenfolge die Operationen dann aber zu verwenden waren, blieb unklar und musste händisch implementiert werden. Mit entsprechendem Tool-Support kann auch hier die Arazzo-Workflow-Spezifikation ihren Beitrag leisten und gezielte Code-Generierung möglich machen. Die im generierten SDK enthaltene Funktion ist dann nicht mehr uploadFile() sondern kann executeFileProcessingWorkflow() sein. Die Interaktion mit der API ist direkt am durch die API gebotenen Mehrwert ausgerichtet und macht eine neue Abstraktionsebene spezifizierbar.

Beispiel: Datei-Verarbeitung als Arazzo-Workflow

Kommen wir zurück zum Beispiel des Datei-Uploads mit anschließender Verarbeitung. Im Folgenden werden wir den bereits umrissenen Workflow mit Hilfe von Arazzo spezifizieren.

Dabei stellen wir ganz klar fest, dass die Spezifikation die Handschrift der OpenAPI-Initiative trägt und vertraute Konzepte verwendet. Das senkt die Einstiegshürde für die Arbeit mit der API-Workflow-Spezifikation.

Schritt 1: Definition der Arazzo-Spezifikation

Beginnen wir mit der Definition der grundlegenden Metadaten des Workflows. Diese umfasst den Namen des Workflows, eine Beschreibung und die Version. Genau wie eine OpenAPI-Spezifikation beginnt eine Arazzo-Spezifikation mit einem info-Block für Metainformationen zum Workflow. Die Felder title und version müssen dabei angegeben werden.

Anschließend folgt eine Liste von API-Beschreibungen (sourceDescriptions), die für den Workflow erforderlich sind. In unserem Beispiel ist das nur eine OpenAPI-Spezifikation:

1arazzo: '1.0.0'
2info:
3  title: Datei-Verarbeitungs-Workflow
4  version: '1.0.0'
5  description: |
6    Dieser Workflow automatisiert den Prozess des Hochladens, Verarbeitens und
7    Überprüfens des Status einer Datei.
8sourceDescriptions:
9  - name: fileProcessingAPI
10    url: https://example.com/openapi.yaml
11    type: openapi

Schritt 2: Grundlegende Definition des Workflows

Im nächsten Abschnitt folgt dann die Beschreibung der Workflows. Ja, eine Arazzo-Spezifikation kann durchaus auch gleich mehrere Workflows basierend auf gemeinsamen APIs enthalten. Es muss also nicht für jeden Workflow eine eigene Spezifikation erstellt werden. Vor diesem Hintergrund zahlen sich auch die bereits aus OpenAPI bekannten, wiederverwendbaren components aus.

In der Arazzo-Workflow-Spezifikation ist ein Workflow eine strukturierte Beschreibung einer Abfolge von Schritten, die automatisiert ausgeführt werden. Ein Workflow wird innerhalb des workflows-Objekts definiert, das folgende zentrale Bestandteile hat:

  • workflowId: Ein eindeutiger Bezeichner für den Workflow.
  • summary: Eine kurze Zusammenfassung des Workflows.
  • description: Eine ausführliche Beschreibung des Workflows, die auch den Kontext und Zweck erläutert.
  • inputs: Die Eingabedaten, die der Workflow benötigt, um ausgeführt zu werden.
  • steps: Eine Liste von Schritten, die in einer definierten Reihenfolge ausgeführt werden.

Die Spezifikation eines Workflows beginnt also mit einer ID, einer Zusammenfassung und Beschreibung:

1workflows:
2  - workflowId: fileUploadProcessing
3    summary: Automatisiert den Datei-Upload und die Verarbeitung.
4    description: |
5      Dieser Workflow lädt eine Datei hoch, startet deren Verarbeitung und
6      überprüft den Abschluss der Verarbeitung.

Schritt 3: Definieren der Eingabe-Parameter

Ein Workflow benötigt bestimmte Parameter, um ausgeführt zu werden. Im Beispiel ist dies die Datei, die verarbeitet werden soll. Diese Parameter werden als inputs definiert:

1inputs:
2      type: object
3      properties:
4        file:
5          type: string
6          format: binary
7          description: Die Datei, die hochgeladen und verarbeitet werden soll.

Schritt 4: Hinzufügen der Schritte

Anschließend folgen die einzelnen Schritte des Workflows: Die einzelnen Aktionen, die der Workflow ausführt, werden als Schritte innerhalb des steps-Objekts definiert. Jeder Schritt hat eine eindeutige stepId, eine Beschreibung und Verweise auf die entsprechenden API-Endpunkte.

Jeder Schritt ist dabei ebenfalls über eine ID eindeutig identifizierbar und referenziert gleichzeitig eine Operation einer API.

Schritt 4.1: Datei hochladen

Die operationId stellt zusammen mit dem parameters-Objekt den Bezug zur API-Spezifikation her, wobei der Wert von values im folgenden Beispiel auf die definierten Eingabe-Parameter des Workflows zurückgreift:

1steps:
2        - stepId: uploadFile
3          description: Lädt die Datei hoch und erhält eine eindeutige Datei-ID.
4          operationId: uploadFile
5          parameters:
6            - name: file
7              in: body
8              value: $inputs.file
9          successCriteria:
10            - condition: $statusCode == 200
11          outputs:
12            fileId: $response.body.fileId

successCriteria definiert dann die Bedingungen, unter denen der Schritt als erfolgreich abgeschlossen gilt. Und outputs sammelt relelevante Ausgaben, die für folgende Schritte benötigt werden. Im Beispiel ist das das Feld fileId aus der Antwort des API-Aufrufs für das Hochladen der Datei.

Schritt 4.2: Datei verarbeiten

Der Output fileId aus dem ersten Schritt wird im Folgenden als Eingabe-Parameter im Schritt für die Verarbeitung der hochgeladenen Datei verwendet:

1- stepId: processFile
2          description: Startet die Verarbeitung der hochgeladenen Datei.
3          operationId: processFile
4          parameters:
5            - name: fileId
6              in: body
7              value: $steps.uploadFile.outputs.fileId
8          successCriteria:
9            - condition: $statusCode == 200
10          outputs:
11            status: $response.body.status

In diesem Schritt wird das Feld status aus der Antwort als Ausgabe definiert.

Schritt 4.3: Verarbeitungsstatus prüfen

Nachdem im vorangegangenen Schritt die Verarbeitung der Datei gestartet wurde, wird im nächsten Schritt der Status der (asynchron ablaufenden) Verarbeitung geprüft.

Im einfachsten Fall kann der Schritt dafür wie folgt definiert werden:

1- stepId: checkStatus
2          description: Überprüft den aktuellen Verarbeitungsstatus der Datei.
3          operationId: getProcessingStatus
4          parameters:
5            - name: fileId
6              in: path
7              value: $steps.uploadFile.outputs.fileId
8          successCriteria:
9            - condition: $statusCode == 200
10            - condition: $response.body.status == 'completed'
11          outputs:
12            finalStatus: $response.body.status

Allerdings müssen wir davon ausgehen, dass die Verarbeitung nicht sofort erfolgreich abgeschlossen ist, d.h. status in der ersten Antwort bereits auf completed gesetzt ist, wie es die successCriteria vorsehen.

Das bedeutet, wir müssen wiederholte Abfragen vorsehen. Dafür können wir die Definition des Schritts checkStatus anpassen, indem wir mittels onFailure einen Retry einbauen:

1- stepId: checkStatus
2        description: Überprüft den aktuellen Verarbeitungsstatus der Datei.
3        operationId: getProcessingStatus
4        parameters:
5          - name: fileId
6            in: path
7            value: $steps.uploadFile.outputs.fileId
8        successCriteria:
9          - condition: $statusCode == 200
10          - condition: $.outputs.status == 'completed'
11        outputs:
12          status: $response.body.status
13        onFailure:
14          - name: retryCheckStatus
15            type: retry
16            stepId: checkStatus
17            retryAfter: 5
18            retryLimit: 10
19            criteria:
20              - condition: $.outputs.status == 'processing'
21          - name: terminateWorkflow
22            type: end
23            criteria:
24              - condition: $.outputs.status == 'failed'

Sofern die Abfrage des Verarbeitungsstatus der Datei nicht mit dem HTTP-Status 200 OK beantwortet wird und das Feld status in der Antwort nicht auf completed gesetzt ist, wird der Schritt nach 5 Sekunden wiederholt (höchstens zehnmal), sofern status noch auf processing gesetzt ist. Im Fall von failed wird der Workflow abgebrochen.

Vollständige Spezifikation des Workflows

Hier ist die vollständige Workflow-Spezifikation:

1arazzo: '1.0.0'
2info:
3  title: Datei-Verarbeitungs-Workflow
4  version: '1.0.0'
5  description: |
6    Dieser Workflow automatisiert den Prozess des Hochladens,
7    Verarbeitens und Überprüfens des Status einer Datei.
8sourceDescriptions:
9  - name: fileProcessingAPI
10    url: https://example.com/openapi.yaml
11    type: openapi
12workflows:
13  - workflowId: fileUploadProcessing
14    summary: Automatisiert den Datei-Upload und die Verarbeitung.
15    description: |
16      Dieser Workflow lädt eine Datei hoch, startet deren Verarbeitung
17      und überprüft den Abschluss der Verarbeitung, indem die Statusprüfung
18      mehrfach wiederholt wird.
19    inputs:
20      type: object
21      properties:
22        file:
23          type: string
24          format: binary
25          description: Die Datei, die hochgeladen und verarbeitet werden soll.
26    steps:
27      - stepId: uploadFile
28        description: Lädt die Datei hoch und erhält eine eindeutige Datei-ID.
29        operationId: uploadFile
30        parameters:
31          - name: file
32            in: body
33            value: $inputs.file
34        successCriteria:
35          - condition: $statusCode == 200
36        outputs:
37          fileId: $response.body.fileId
38
39      - stepId: processFile
40        description: Startet die Verarbeitung der hochgeladenen Datei.
41        operationId: processFile
42        parameters:
43          - name: fileId
44            in: body
45            value: $steps.uploadFile.outputs.fileId
46        successCriteria:
47          - condition: $statusCode == 200
48        outputs:
49          status: $response.body.status
50
51      - stepId: checkStatus
52        description: Überprüft den aktuellen Verarbeitungsstatus der Datei.
53        operationId: getProcessingStatus
54        parameters:
55          - name: fileId
56            in: path
57            value: $steps.uploadFile.outputs.fileId
58        successCriteria:
59          - condition: $statusCode == 200
60          - condition: $.outputs.status == 'completed'
61        outputs:
62          status: $response.body.status
63        onFailure:
64          - name: retryCheckStatus
65            type: retry
66            stepId: checkStatus
67            retryAfter: 5
68            retryLimit: 10
69            criteria:
70              - condition: $.outputs.status == 'pending'
71          - name: terminateWorkflow
72            type: end
73            criteria:
74              - condition: $.outputs.status == 'failed'

Möglichkeiten zur Beschreibung von Workflow-Logik

Das Beispiel hat gezeigt, dass Arazzo uns mit onSuccess und onFailure in der Definition von Schritten einfache Möglichkeiten zum Abbilden von Logik innerhalb des Workflows bietet. Neben retry sind mit goto auch Verzweigungen zu anderen Schritten oder sogar anderen Workflows möglich.

Genauso können in komplexeren Szenarien auch Abhängigkeiten zwischen verschiedenen Workflows definiert werden: Das Attribut dependsOn einer Workflow-Beschreibung in workflows erlaubt es, Workflows anzugeben, die abgeschlossen sein müssen, bevor der entsprechende Workflow ausgeführt werden kann. Entsprechend können sich Parameter-Werte auf andere Workflows beziehen.

Die Fähigkeit, Workflows zu verketten und aufeinander abzustimmen, macht die Spezifikation besonders mächtig und bietet eine klare Struktur für Automatisierungsprozesse und Integrationsszenarien. Diese deterministische Beschreibung kann uns auch beim Testen unserer APIs zugute kommen, da Abhängigkeiten und Verzweigungen explizit definiert sind.

Ausblick

Bisher ist die Arazzo-Spezifikation noch sehr neu. Dadurch sind natürlich noch nicht alle Wünsche erfüllt oder alle denkbaren Szenarien abgebildet. Beispielsweise hätte es die Unterstützung für andere APIs als in OpenAPI spezifierte synchrone HTTP-APIs fast in die Version 1.0.0 des Arazzo-Standards geschafft. Das ist dann jedoch zugunsten einer stabilen ersten Version vorerst verworfen worden. Eventgetriebene APIs werden in einem nächsten Schritt nichtsdestotrotz ihren Weg in die Spezifikation des Standards finden.

Ziel ist auf jeden Fall eine große Verbreitung von Arazzo für die Beschreibung von API-Workflows zu erreichen. Die Relevanz ist da, das Interesse auch.

Um den Standard so richtig zum Leben zu erwecken, ist natürlich entsprechender Tooling-Support hilfreich, wenn nicht sogar erforderlich. Mit Blick auf die breite Unterstützung der verwandten OpenAPI-Spezifikation ist davon auszugehen, dass kompatible Tools nicht allzu lange auf sich warten lassen werden.

Mit Redocly ist bereits ein Linter für Arazzo verfügbar, sowohl als Tool für die Kommandozeile als auch als Extension in Visual Studio Code. Damit sind die ersten Weichen für die Adoption des neuen Standards gestellt, spezifikationskonforme Workflows können erstellt werden und weitere Tools werden folgen. Die Redocly-Roadmap gibt bereits einen Vorgeschmack darauf.

Wer jetzt auf den Geschmack gekommen ist, findet weitere Informationen sowie eine Sammlung von Beispielen im Arazzo-Repository der OpenAPI-Initiative. Kommt auch gern auf uns zu, um gemeinsam die Möglichkeiten zu entdecken, die sich mit API-Workflows und Arazzo als offener standardisierter Möglichkeit zur Definition ergeben!