Am schnellsten versteht man Arazzo, wenn man selbst eine Spec erstellt. Dieses Tutorial beschreibt deshalb Schritt für Schritt einen vollständigen Workflow, vom Login über das Anlegen einer Bestellung bis zur Statusprüfung. Das Grundgerüst dafür passt in zwanzig Zeilen.

yaml
arazzo: 1.1.0
info:
  title: Bestellung mit Statusverfolgung
  version: 1.0.0
sourceDescriptions:
  - name: shopApi
    url: ./openapi.yaml
    type: openapi
workflows:
  - workflowId: placeAndTrackOrder
    steps:
      - stepId: login
        operationId: createSession
      - stepId: createOrder
        operationId: createOrder
      - stepId: checkStatus
        operationId: getOrderStatus

Drei Schritte über eine API, noch ohne Datenflüsse und Fehlerbehandlung. Genau dieses Gerüst erweitert der Rest des Tutorials zu einer vollständigen, prüfbaren Beschreibung. Wer zuerst wissen will, was Arazzo grundsätzlich ist und wofür es gedacht ist, findet die Einordnung im Artikel Was ist Arazzo. Hier geht es um die Umsetzung.

Hinweis

Eine Arazzo-Spec beschreibt einen mehrstufigen API-Workflow als YAML- oder JSON-Datei. Sie referenziert über sourceDescriptions eine oder mehrere OpenAPI-Beschreibungen, definiert unter workflows die Schritte eines Ablaufs und legt mit outputs, parameters und successCriteria fest, wie Daten von Schritt zu Schritt fließen und wann ein Schritt als gelungen gilt. Die fertige Datei lässt sich validieren, versionieren und von Werkzeugen wie Testrunnern oder KI-Agenten ausführen.

Der Use Case und die Ausgangslage

Der Beispiel-Workflow bildet einen Vorgang ab, den fast jede Commerce-API kennt. Ein Konsument meldet sich an, legt eine Bestellung an und prüft anschließend deren Status. Drei Endpoints, drei Abhängigkeiten. Der Login liefert ein Token, das die Bestellung braucht. Die Bestellung liefert eine ID, die die Statusabfrage braucht. Genau diese Übergaben sind der Teil, den eine OpenAPI-Spec allein nicht ausdrückt und den Arazzo beschreibt.

Als Ausgangslage dient eine vorhandene OpenAPI-Datei openapi.yaml mit den drei Operationen. Wichtig für das Tutorial sind nur ihre operationId-Werte, denn über sie verbindet Arazzo seine Schritte mit den Operationen.

yaml
# Ausschnitt aus openapi.yaml — nur die operationIds zählen hier
paths:
  /sessions:
    post:
      operationId: createSession
  /orders:
    post:
      operationId: createOrder
  /orders/{orderId}/status:
    get:
      operationId: getOrderStatus

Eine saubere OpenAPI-Spec mit sprechenden operationId-Werten ist damit die halbe Arazzo-Arbeit. Wer dort kryptische oder generierte IDs stehen hat, sollte sie vor dem ersten Workflow aufräumen, weil jede Arazzo-Datei sie wörtlich referenziert.

Die Quellen referenzieren

Am Anfang jeder Arazzo-Datei stehen drei Angaben. Die Versionszeile legt fest, gegen welche Arazzo-Version die Datei geschrieben ist. Der info-Block benennt den Workflow für Menschen. Und sourceDescriptions verbindet die Datei mit den zugrunde liegenden API-Beschreibungen.

yaml
arazzo: 1.1.0
info:
  title: Bestellung mit Statusverfolgung
  version: 1.0.0
  description: Login, Bestellung anlegen, Status verfolgen
sourceDescriptions:
  - name: shopApi
    url: ./openapi.yaml
    type: openapi

Der name der Quelle ist mehr als Dekoration. Sind mehrere Quellen im Spiel, etwa eine zweite API für die Zahlung, adressieren die Schritte ihre Operationen über diesen Namen. Für den Einstieg reicht eine Quelle. Im Tutorial zeigt der Verweis relativ auf eine lokale Datei, in der Praxis verweist er auf die veröffentlichte Spec der jeweiligen API.

Der Login-Schritt und seine Outputs

Der erste Schritt authentifiziert den Konsumenten. Interessant ist an ihm vor allem das Ende, denn dort wird das Token für die folgenden Schritte herausgezogen.

yaml
steps:
  - stepId: login
    operationId: createSession
    requestBody:
      payload:
        apiKey: $inputs.apiKey
    successCriteria:
      - condition: $statusCode == 201
    outputs:
      token: $response.body#/accessToken

Hier arbeiten drei Mechanismen zusammen. Der Ausdruck $inputs.apiKey greift auf die Eingaben des Workflows zu, die später noch definiert werden. Das successCriteria legt fest, dass nur ein Status 201 als Erfolg zählt, alles andere bricht den Schritt ab. Und die outputs extrahieren das Token aus der Antwort und stellen es unter dem Namen token bereit. Ab jetzt kann jeder spätere Schritt darauf zugreifen.

Die Syntax hinter $response.body#/accessToken ist ein JSON Pointer hinter einem Laufzeit-Ausdruck. Der Teil vor der Raute benennt die Quelle, der Teil danach den Pfad ins Dokument. Wer tiefer verschachtelte Strukturen oder XML-Antworten auswerten muss, nutzt seit Arazzo 1.1.0 zusätzlich das Selector Object mit jsonpath oder xpath, eine der Neuerungen, die Was ist neu in Arazzo 1.1 vorstellt.

Die Bestellung und der Datenfluss

Der zweite Schritt verwendet das Token und erzeugt seinerseits die Bestell-ID. Damit zeigt er das Muster, das jeden Arazzo-Workflow trägt, nämlich die Kette aus Output und Zugriff.

yaml
  - stepId: createOrder
    operationId: createOrder
    parameters:
      - name: Authorization
        in: header
        value: Bearer $steps.login.outputs.token
    requestBody:
      payload:
        items: $inputs.items
    successCriteria:
      - condition: $statusCode == 201
    outputs:
      orderId: $response.body#/id

Der Ausdruck $steps.login.outputs.token liest sich von links nach rechts als Pfad durch den Workflow. Aus dem Schritt login das Output-Feld token, eingesetzt als Bearer-Header. Diese Referenzen sind explizit und prüfbar, und genau das unterscheidet die Spec von einer Prosa-Beschreibung, in der die Übergabe nur behauptet wird. Ein Validator erkennt einen Tippfehler im Schrittnamen sofort, ein Wiki-Text verzeiht ihn jahrelang.

Erwähnenswert ist die Doppelrolle der parameters. Sie setzen Header, Query- oder Pfad-Parameter und können Werte aus Inputs, vorherigen Schritten oder Konstanten beziehen. Damit decken sie praktisch jede Stelle ab, an der ein API-Aufruf von außen parametrisiert wird.

Die Statusprüfung mit Wiederholung

Der dritte Schritt fragt den Status der Bestellung ab, und hier kommt eine Anforderung dazu, die in echten Systemen häufig auftritt. Der Status steht selten sofort auf dem Zielwert, weil die Verarbeitung im Hintergrund läuft. Der Schritt darf also nicht beim ersten Versuch aufgeben.

yaml
  - stepId: checkStatus
    operationId: getOrderStatus
    parameters:
      - name: orderId
        in: path
        value: $steps.createOrder.outputs.orderId
      - name: Authorization
        in: header
        value: Bearer $steps.login.outputs.token
    successCriteria:
      - condition: $response.body#/status == "confirmed"
    onFailure:
      - name: retryStatus
        type: retry
        retryAfter: 2
        retryLimit: 10
    outputs:
      finalStatus: $response.body#/status

Das onFailure mit dem Typ retry wiederholt den Schritt bis zu zehnmal im Abstand von zwei Sekunden, bis das Erfolgskriterium erfüllt ist. Damit ist das verbreitete Polling-Muster inklusive seiner Grenzen deklarativ beschrieben. Nach dem zehnten Versuch gilt der Schritt endgültig als gescheitert, und der Workflow endet kontrolliert, ohne endlos zu warten.

Neben retry kennt onFailure auch die Typen goto für den Sprung zu einem anderen Schritt und end für den sofortigen Abbruch. Wer Kompensations-Schritte beschreiben will, etwa das Stornieren der Bestellung nach einem endgültig gescheiterten Folgeschritt, kombiniert goto mit einem eigens definierten Aufräum-Schritt. Die konzeptionelle Seite dieser Fehlerpfade, von Idempotenz bis zum Saga-Muster, behandelt der Überblick zur API-Orchestrierung.

Eine zweite API kommt dazu

Reale Abläufe bleiben selten innerhalb einer API, und genau für diesen Fall ist sourceDescriptions eine Liste. Angenommen, die Zahlung läuft über eine eigene Payment-API mit eigener OpenAPI-Datei. Dann wächst der Kopf der Spec um eine Quelle, und die Schritte adressieren ihre Operationen mit dem Quellnamen als Präfix.

yaml
sourceDescriptions:
  - name: shopApi
    url: ./openapi.yaml
    type: openapi
  - name: paymentApi
    url: ./payment-openapi.yaml
    type: openapi


# Im Workflow: Operation eindeutig über die Quelle adressieren
  - stepId: authorizePayment
    operationId: paymentApi.authorizePayment
    parameters:
      - name: Authorization
        in: header
        value: Bearer $steps.login.outputs.token
    successCriteria:
      - condition: $statusCode == 200
    outputs:
      paymentId: $response.body#/id

Das Präfix ist nur nötig, wenn eine operationId in mehreren Quellen vorkommt oder Verwechslungsgefahr besteht, aber es schadet auch sonst nicht und macht die Herkunft jeder Operation auf einen Blick lesbar. Spätestens an dieser Stelle zeigt sich der eigentliche Wert der Spec. Ein Ablauf über zwei APIs hat im Normalfall keinen gemeinsamen Ort mehr, an dem er beschrieben wäre, weil jede API nur ihre eigene Dokumentation pflegt. Die Arazzo-Datei ist genau dieser Ort.

Mit der zweiten Quelle stellt sich auch die Frage nach dem Eigentümer der Workflow-Datei neu, denn der Ablauf gehört nun keinem der beiden API-Teams allein. Bewährt hat sich die Verortung beim Team des fachlichen Prozesses, das die Datei pflegt und beide API-Teams bei Änderungen einbindet. Spätestens hier zeigt sich, warum die Workflow-Spec ein eigenes Repository braucht. Sie ist ein eigenständiges Asset mit eigener Versionierung und eigenen Review-Prozessen und gehört in keines der beteiligten API-Repositories.

Inputs und Outputs des Workflows

Bisher haben die Schritte auf $inputs zugegriffen, ohne dass diese definiert wären. Der Workflow deklariert seine Eingaben als JSON-Schema und seine Ausgaben als benannte Ausdrücke, womit er nach außen selbst wie eine Funktion aussieht.

yaml
  - workflowId: placeAndTrackOrder
    inputs:
      type: object
      required: [apiKey, items]
      properties:
        apiKey:
          type: string
        items:
          type: array
          items:
            type: object
    outputs:
      orderId: $steps.createOrder.outputs.orderId
      finalStatus: $steps.checkStatus.outputs.finalStatus

Diese Schnittstelle zahlt sich doppelt aus. Ein Testrunner weiß damit, welche Daten er bereitstellen muss und welche Ergebnisse er prüfen kann. Und seit Arazzo 1.1.0 kann ein anderer Workflow diesen hier als Baustein aufrufen und ihm über genau diese Inputs Werte übergeben, womit sich größere Abläufe aus kleineren zusammensetzen lassen.

Validieren, ausführen, einchecken

Eine Arazzo-Datei entfaltet ihren Wert erst, wenn sie geprüft und ausgeführt wird. Dafür haben sich drei Stationen bewährt.

  1. Validierung gegen die Spezifikation. Ein Arazzo-Validator prüft Struktur, Pflichtfelder und die Auflösbarkeit aller Referenzen, also auch, ob jede operationId in der referenzierten OpenAPI-Datei existiert. Diese Prüfung gehört als Job in die CI-Pipeline neben das OpenAPI-Linting.
  2. Ausführung gegen eine Testumgebung. Ein Testrunner arbeitet die Schritte gegen eine laufende API ab, füllt die Inputs mit Testdaten und meldet, an welchem Schritt und welchem Kriterium ein Lauf scheitert. Damit wird aus der Dokumentation ein End-to-End-Test.
  3. Pflege unter Versionskontrolle. Die Arazzo-Datei gehört in ein eigenes Repository, mit eigenen Reviews, Tags und Releases und der Version im info-Block. Sie ist ein eigenständiges Asset mit eigenem Lebenszyklus, und diese Versionierung funktioniert nur sauber, wenn sie sich nicht mit den Release-Ständen einer einzelnen API vermischt.
Beobachtung aus der Praxis

In einem Team, das diesen Dreiklang eingeführt hat, scheiterte der nächtliche Workflow-Lauf einige Wochen später überraschend am Status-Schritt. Die Backend-Kollegen hatten den Statuswert von „confirmed" auf „completed" umbenannt und die Änderung für rein kosmetisch gehalten. Der Lauf machte den Bruch am nächsten Morgen sichtbar, lange bevor ein Konsument ihn bemerkte. Die Wiki-Dokumentation desselben Ablaufs hätte den alten Wert vermutlich noch Monate behauptet.

Für die Pipeline-Integration genügt im einfachsten Fall ein zusätzlicher Job, der beide Prüfungen nacheinander ausführt.

yaml
# CI-Job, schematisch: erst validieren, dann gegen Staging ausführen
arazzo-check:
  stage: test
  script:
    - arazzo validate workflows/place-and-track-order.arazzo.yaml
    - arazzo run workflows/place-and-track-order.arazzo.yaml
        --server https://staging.example.com
        --input apiKey=$STAGING_API_KEY
        --input items=@testdata/items.json

Die Werkzeuglandschaft rund um Arazzo ist jung, aber für Validierung und Ausführung gibt es bereits mehrere Optionen, von Open-Source-Runnern bis zu Editoren mit eingebauter Prüfung. Welche Kombination passt, hängt vor allem davon ab, ob der Lauf in der Pipeline oder am Entwickler-Arbeitsplatz stattfinden soll. Wichtiger als die Werkzeugwahl ist die Regel dahinter. Eine Workflow-Datei, die nirgends ausgeführt wird, altert genauso schnell wie das Wiki, das sie ersetzen sollte.

Typische Fallstricke beim ersten Workflow

Ein paar typische Schwierigkeiten beim Einstieg verdienen einen Hinweis vorab.

Keiner dieser Punkte ist ein Grund, den Einstieg zu verschieben. Sie kosten beim ersten Workflow je eine halbe Stunde Sucherei und sind danach Routine.

Den Workflow lesbar halten

Eine Arazzo-Datei wird von mehr Menschen gelesen als geschrieben, von Konsumenten, Reviewern und in wachsendem Maß von KI-Agenten, die sie als Bauplan nutzen. Ein paar Konventionen halten sie für alle drei Gruppen verständlich.

Die stepId-Werte verdienen dieselbe Sorgfalt wie Funktionsnamen im Code. Ein Schritt namens checkStatus erklärt sich selbst, einer namens step3 erklärt gar nichts, und weil spätere Schritte über diese Namen auf Outputs zugreifen, wandern unklare Namen durch die ganze Datei. Dasselbe gilt für die Output-Namen, die als Schnittstelle zwischen den Schritten fungieren.

Jeder Schritt akzeptiert zusätzlich eine description, und es lohnt sich, sie für das Warum zu nutzen statt für das Was. Dass der Schritt den Status abfragt, sieht jeder an der Operation. Warum er bis zu zehnmal wiederholt wird und weshalb erst „confirmed" als Erfolg zählt, steht sonst nirgendwo. Gerade für Agenten, die den Workflow ausführen, ist diese Begründungsebene wertvoll, weil sie Kontext liefert, den die reine Struktur nicht trägt. Wie APIs insgesamt für maschinelle Konsumenten lesbar werden, behandelt der Artikel AI-Ready APIs.

Bei wachsender Zahl von Workflows hat sich außerdem eine Datei pro Workflow bewährt, benannt nach der workflowId, statt einer Sammeldatei mit zehn Abläufen. Die Einzeldateien lassen sich gezielt versionieren, reviewen und im Katalog einzeln auffindbar machen, und die Verkettung über Workflow-Aufrufe funktioniert über Dateigrenzen hinweg, weil sourceDescriptions auch Arazzo-Dokumente referenzieren kann.

Wohin mit der fertigen Spec

Mit der lauffähigen Datei stellt sich die Frage nach der Sichtbarkeit, und sie entscheidet darüber, ob die Arbeit sich über das eigene Team hinaus auszahlt. In seinem Repository ist der Workflow gut aufgehoben für alle, die an den Abläufen arbeiten. Konsumenten der API schauen dort allerdings selten hinein. Sie suchen im Developer Portal, und dort finden sie üblicherweise die Endpoints, aber nicht die Abläufe.

Der nächste konsequente Schritt ist deshalb, die Workflow-Beschreibung dort zu veröffentlichen, wo auch die API-Dokumentation lebt, also im Katalog des Portals. Dort lässt sich eine Arazzo-Datei genauso verwalten wie eine OpenAPI-Spec, mit Zuständigkeiten, Versionen und einem Audit-Trail, der auch regulierten Anforderungen standhält. Ein Konsument, der die Bestell-API entdeckt, sieht dann direkt daneben den beschriebenen Ablauf und muss die Reihenfolge der Aufrufe nicht mehr aus der Endpoint-Liste erraten. Was das organisatorisch bedeutet und wie Workflows als eigenständige Katalog-Inhalte funktionieren, behandelt der Beitrag Arazzo-Workflows im API-Katalog.

Die fertige Datei im Überblick

Zum Abschluss die vollständige Spec, wie sie nach allen Schritten im Repository liegt. Sie ist bewusst kompakt gehalten und kommt ohne Spezialfälle aus, taugt aber als Vorlage für den ersten eigenen Workflow.

yaml
arazzo: 1.1.0
info:
  title: Bestellung mit Statusverfolgung
  version: 1.0.0
sourceDescriptions:
  - name: shopApi
    url: ./openapi.yaml
    type: openapi
workflows:
  - workflowId: placeAndTrackOrder
    inputs:
      type: object
      required: [apiKey, items]
      properties:
        apiKey: { type: string }
        items: { type: array }
    steps:
      - stepId: login
        operationId: createSession
        requestBody:
          payload:
            apiKey: $inputs.apiKey
        successCriteria:
          - condition: $statusCode == 201
        outputs:
          token: $response.body#/accessToken
      - stepId: createOrder
        operationId: createOrder
        parameters:
          - name: Authorization
            in: header
            value: Bearer $steps.login.outputs.token
        requestBody:
          payload:
            items: $inputs.items
        successCriteria:
          - condition: $statusCode == 201
        outputs:
          orderId: $response.body#/id
      - stepId: checkStatus
        operationId: getOrderStatus
        parameters:
          - name: orderId
            in: path
            value: $steps.createOrder.outputs.orderId
          - name: Authorization
            in: header
            value: Bearer $steps.login.outputs.token
        successCriteria:
          - condition: $response.body#/status == "confirmed"
        onFailure:
          - name: retryStatus
            type: retry
            retryAfter: 2
            retryLimit: 10
        outputs:
          finalStatus: $response.body#/status
    outputs:
      orderId: $steps.createOrder.outputs.orderId
      finalStatus: $steps.checkStatus.outputs.finalStatus
Tipp

Der beste Kandidat für die erste eigene Arazzo-Spec ist ein Ablauf, der bereits als Prosa-Anleitung im Wiki existiert und von Konsumenten nachgefragt wird. Die Übersetzung in eine Spec dauert meist unter einem Tag, und die Abweichungen zwischen Anleitung und tatsächlichem API-Verhalten, die dabei auffallen, sind den Aufwand oft allein wert.

So entwickeln Sie den Workflow weiter

Mit der ersten lauffähigen Spec sind die Anschlussfragen meist schnell auf dem Tisch. Mehrere Quellen für Abläufe über API-Grenzen hinweg funktionieren über zusätzliche Einträge in sourceDescriptions. Event-Schritte, die auf eine Nachricht warten, kommen mit den AsyncAPI-Erweiterungen aus Arazzo 1.1.0 dazu. Und sobald mehrere Workflows entstehen, lohnt es sich, wiederkehrende Teilabläufe als eigene Workflows auszulagern und aufzurufen.

Beschreiben Sie als nächsten Schritt einen zweiten Workflow aus Ihrem Bestand, diesmal einen mit einem echten Kompensations-Fall, etwa einer Stornierung. An genau dieser Stelle zeigt sich, ob die Fehlerpfade Ihrer APIs so klar definiert sind, wie die Dokumentation behauptet.