OpenAPI Spezifikationen in CI vergleichen und Breaking Changes verhindern

Vergleiche zwei OpenAPI-Spezifikationsversionen, um Breaking API Changes vor dem Merge zu erkennen. Nutze oasdiff oder openapi-diff in CI und schließe dann die Kontraktlücke mit der Apidog CLI.

Ashley Goolam

Ashley Goolam

16 June 2026

OpenAPI Spezifikationen in CI vergleichen und Breaking Changes verhindern

enterprise.banner.title

enterprise.banner.feature1

enterprise.banner.feature2

enterprise.banner.feature3

enterprise.banner.ctaB

Ein Pull Request bearbeitet die Datei openapi.yaml. Die CI-Checks sind grün. Die Spezifikation ist gültig, sie liniert sauber, und zwei Prüfer genehmigen sie. Drei Tage später beginnt ein mobiler Client Null-Pointer-Abstürze zu werfen, weil ein Antwortfeld, das früher da war, verschwunden ist. Niemand hat es absichtlich entfernt. Jemand hat eine Eigenschaft während einer Refaktorierung umbenannt, und nichts in der Überprüfung hat es bemerkt.

Das ist die Lücke, die ein einfacher Validator nie sieht. Eine Spezifikation kann perfekt wohlgeformt sein und trotzdem jeden Konsumenten, der von ihr abhängt, zerstören. Der einzige Weg, dies zu wissen, ist, die neue Spezifikation mit der Version zu vergleichen, die sie ersetzt, Änderung für Änderung, und eine Frage zu stellen: Würde dies einen Client, der gestern funktionierte, zum Absturz bringen? Dieser Vergleich ist ein OpenAPI-Diff, und ihn als Merge-Gate auszuführen, ist eine der ertragreichsten Prüfungen, die Sie einem API-Repository hinzufügen können.

Schaltfläche

Was ein OpenAPI-Diff tatsächlich vergleicht

Ein OpenAPI-Diff nimmt zwei Spezifikationen, eine Basis und einen Kopf, und meldet, was sich zwischen ihnen geändert hat. Die Basis ist normalerweise die Spezifikation in Ihrem Zielbranch (was live ist). Der Kopf ist die Spezifikation, die Ihr Pull Request vorschlägt. Ein gutes Diff-Tool gibt nicht nur ein textuelles Delta aus, wie es git diff tun würde. Es versteht die OpenAPI-Struktur, sodass es den Unterschied zwischen kosmetischen Bearbeitungen und vertragsbrechenden Änderungen erkennen kann.

Hier ist die Unterscheidung, die wichtig ist. Einige Änderungen sind additiv und sicher:

Bestehende Clients funktionieren bei all diesen weiterhin. Sie senden, was sie immer gesendet haben, und lesen, was sie immer gelesen haben. Andere Änderungen sind abwärtsinkompatibel, und diese sind es, die schmerzen:

Die Aufgabe eines OpenAPI-Diff-Tools besteht darin, jeden Pfad, Parameter, Schema und jede Antwort in beiden Dokumenten zu scannen und jede Änderung in einen dieser Eimer zu sortieren. Diese Klassifizierung ist der ganze Punkt. Ein rohes Zeilen-Diff vergräbt ein entferntes required-Feld unter fünfzig Zeilen Neuformatierung. Ein strukturelles Diff zeigt es als Breaking Change auf und sagt Ihnen, unter welchem Pfad es sich befindet.

Wenn Sie das zugrundeliegende mentale Modell dafür verstehen möchten, warum einige Änderungen brechen und andere nicht, behandelt der Leitfaden zum Versionieren und Deprecaten von APIs in großem Maßstab die Kompatibilitätsregeln ausführlich. Das Diff-Tool ist der Weg, diese Regeln mechanisch durchzusetzen, anstatt zu hoffen, dass ein Prüfer sie sich merkt.

oasdiff: Das Open-Source-Arbeitstier

oasdiff ist das Open-Source-Tool, zu dem die meisten Teams greifen. Es ist ein einzelnes Go-Binary, es ist schnell und wurde speziell für die Frage nach Breaking Changes entwickelt. Es liest OpenAPI 3.0- und 3.1-Dokumente und bietet Ihnen je nach gewünschtem Vergleichsergebnis einige Unterbefehle.

Die drei, die Sie am häufigsten verwenden werden:

Für ein Merge-Gate ist breaking das Wichtigste. Zeigen Sie es auf Ihre Basisspezifikation und Ihre Kopf-Spezifikation:

oasdiff breaking base-openapi.yaml head-openapi.yaml --fail-on ERR

base-openapi.yaml ist die Spezifikation aus dem Zielbranch und head-openapi.yaml ist die im Pull Request. Der Unterbefehl breaking gibt nur die inkompatiblen Änderungen aus. Das Flag --fail-on ERR macht dies zu einem Gate: Es bewirkt, dass der Befehl mit einem Exit-Code ungleich Null beendet wird, wenn er eine Änderung findet, die als ERR klassifiziert wurde. Ein Exit-Code ungleich Null ist das universelle Signal, das CI als Fehler interpretiert.

Dieses Schweregradmodell ist es wert, verstanden zu werden. oasdiff sortiert Breaking Changes in Ebenen, wobei ERR die ernsthafte ist, eine Änderung, die Clients beschädigen wird. WARN deckt Änderungen ab, die einige Clients je nach ihrer Implementierung beschädigen könnten, und INFO ist informativ. Sie entscheiden, wo Sie die Grenze ziehen. --fail-on ERR blockiert nur die eindeutigen Brüche. --fail-on WARN ist strenger und fängt auch die "Vielleicht"-Fälle ab.

Wenn Sie den lesbaren Überblick für ein Changelog oder einen PR-Kommentar und nicht nur ein Pass/Fail wünschen, ist der Unterbefehl changelog die freundlichere Ausgabe:

oasdiff changelog base-openapi.yaml head-openapi.yaml

oasdiff hat einige wirklich nützliche Features. Es führt ein Endpunkt-Matching durch, das auch bei umbenannten Pfadparametern funktioniert, sodass es {userId}, das zu {id} wird, nicht als Löschen-plus-Hinzufügen kennzeichnet, wenn der Pfad ansonsten identisch ist. Es kann allOf-Schemata vor dem Vergleich zusammenführen, sodass Vererbung keinen unnötigen Lärm erzeugt. Und es gibt mehr als nur Klartext aus: HTML, JSON, YAML und Markdown sind alle über die Ausgabe-Flags verfügbar, was es einfach macht, das Ergebnis in eine CI-Annotation oder ein generiertes Changelog einzuspeisen. Für ein Tool, das Sie in fünf Minuten in eine Pipeline integrieren können und dem Sie vertrauen können, konservativ zu sein, was es als "breaking" bezeichnet, ist es kaum zu übertreffen.

openapi-diff: Die JVM-Alternative

Wenn Ihr Stack bereits auf der JVM läuft, ist OpenAPITools/openapi-diff eine solide zweite Option und es lohnt sich, sie zu kennen. Es ist ein Java-basiertes Tool (Java 8 und höher), das zwei OpenAPI 3.x-Spezifikationen vergleicht und die Unterschiede als HTML, Markdown, AsciiDoc, JSON oder Konsolentext darstellt. Sie können es von einem erstellten Jar aus, über Maven, über Homebrew oder als Docker-Image ausführen, sodass es ohne großen Aufwand in eine Reihe von Build-Setups passt.

Sein Vergleich geht tief in Parameter, Antworten, Endpunkte und HTTP-Methoden, und es zieht die gleiche Linie, die jedem wichtig ist: Änderungen, die die Abwärtskompatibilität bewahrt haben, versus Änderungen, die sie gebrochen haben. Die CLI ist unkompliziert:

openapi-diff old-openapi.yaml new-openapi.yaml --fail-on-incompatible

Das Flag --fail-on-incompatible beendet den Prozess nur mit einem Exit-Code ungleich Null, wenn eine Änderung die Abwärtskompatibilität verletzt hat, was genau das gewünschte Gate-Verhalten ist. Es gibt ein strengeres --fail-on-changed, wenn Sie lieber bei jeder Änderung fehlschlagen möchten, und einen --state-Modus, der einfach no_changes, compatible oder incompatible ausgibt, wenn Sie eine einwortige Antwort für Skripte benötigen.

Wo es glänzt, ist die gerenderte Ausgabe. Die HTML- und Markdown-Berichte sind sauber und detailliert, was openapi-diff zu einer starken Wahl macht, wenn Sie ein Diff-Artefakt wünschen, das ein Mensch tatsächlich liest, und nicht nur einen CI-Exit-Code. Der Kompromiss ist die JVM-Abhängigkeit und ein langsamerer Start als ein Go-Binary. Wenn Ihr Team bereits ein Java-Shop ist, sind diese Kosten gleich Null und das Tool passt sofort. Wenn nicht, ist oasdiff die leichtere Wahl. Beide beantworten die Frage nach Breaking Changes gut; wählen Sie das Tool, das zu der Laufzeit passt, die Sie bereits pflegen.

Verdrahtung des Diffs in CI als Merge-Gate

Ein Diff, das Sie von Hand ausführen, fängt nichts ab, denn der Moment, in dem Sie vergessen, es auszuführen, ist der Moment, in dem der Bruch ausgeliefert wird. Das Gate muss in der Pipeline leben und bei jedem Pull Request ausgelöst werden, der die Spezifikation berührt.

Die eine Besonderheit in CI ist, dass Sie beide Versionen der Spezifikation gleichzeitig benötigen: die Basis aus dem Zielbranch und den Kopf aus dem PR. Der PR-Checkout liefert Ihnen den Kopf. Die Basis ziehen Sie direkt aus der Git-Historie, ohne einen zweiten Checkout:

name: openapi-diff
on:
  pull_request:
    paths:
      - "openapi.yaml"

jobs:
  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Get base spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml
      - name: Install oasdiff
        run: |
          curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
      - name: Diff for breaking changes
        run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

Einige Details tragen hier das Gewicht. fetch-depth: 0 holt die vollständige Historie ab, damit git show den Basisbranch erreichen kann. Die Zeile git show origin/<base>:openapi.yaml liest die Spezifikation, wie sie auf dem Zielbranch existiert, und schreibt sie in eine Datei, kein zusätzlicher Klon erforderlich. Der paths-Filter bedeutet, dass der Job nur läuft, wenn sich die Spezifikation tatsächlich ändert, sodass nicht verwandte PRs nicht dafür bezahlen. Und der letzte Schritt ist das Gate: Wenn oasdiff breaking eine Änderung auf ERR-Ebene findet, wird der Exit-Code ungleich Null gesetzt, der Job wird rot, und der PR zeigt einen fehlgeschlagenen Check an, bevor jemand auf Merge klickt.

Der Autor sieht genau, welche Änderung die Kompatibilität gebrochen hat, auf welchem Pfad, während der Code noch überprüft wird. Das ist der gesamte Wert. Der Bruch wird zum günstigsten Zeitpunkt abgefangen, anstatt im Absturzbericht eines Kunden.

Nicht jede Breaking Change ist natürlich ein Fehler. Manchmal liefern Sie eine absichtliche Hauptversion aus, und der Bruch ist beabsichtigt. Das saubere Muster ist, standardmäßig zu blockieren und für Ausnahmen eine explizite Überschreibung zu verlangen: ein Label für den PR, eine Versionserhöhung in info.version oder einen separaten genehmigten Workflow. Auf diese Weise ist ein Bruch immer eine absichtliche Entscheidung, nie ein Versehen, das durchgerutscht ist. Der Leitfaden zur API-Versionsstrategie beschreibt, wann ein Bruch eine neue Hauptversion verdient und wann er einfach vermieden werden sollte.

Die Lücke, die ein Diff nicht schließen kann

Hier ist die Grenze jedes oben genannten Tools, und sie ist wichtig. Ein Diff vergleicht zwei Dateien. Es sagt Ihnen, dass das neue Dokument abwärtskompatibel mit dem alten Dokument ist. Es sagt nichts darüber aus, ob Ihr laufender Dienst tatsächlich mit einem von beiden übereinstimmt.

Das ist ein anderer Fehler, und er ist der, der in der Produktion am härtesten zuschlägt. Die Spezifikation verspricht ein Feld created_at; die Implementierung hat still und heimlich vor drei Sprints aufgehört, es zurückzugeben. Die Spezifikation besagt, dass ein Endpunkt 200 zurückgibt; der Live-Dienst gibt unter einer Bedingung, die niemand getestet hat, 500 zurück. Der Diff ist sauber, weil beide Spezifikationsversionen übereinstimmen. Der Vertrag und der Code tun es nicht. Ein statischer Diff kann dies nicht wissen, da er nie mit der API kommuniziert.

Diese Lücke zu schließen bedeutet, die Live-API gegen den Vertrag zu testen, und nicht nur den Vertrag gegen sich selbst zu vergleichen. Sie generieren Tests aus der Spezifikation, führen sie gegen den laufenden Dienst aus und stellen sicher, dass reale Antworten den dokumentierten Formen entsprechen. Das ist Vertragstests, und es ist die Ebene, die Abweichungen zwischen dem, was Sie aufgeschrieben haben, und dem, was Sie tatsächlich ausgeliefert haben, erkennt.

Schließung mit Apidog und der Apidog CLI

Apidog wurde für diese Schleife entwickelt, was es zu einem natürlichen Begleiter des Diff-Schritts und nicht zu einem Ersatz dafür macht. Sie importieren oder synchronisieren Ihre OpenAPI-Spezifikation in ein Apidog-Projekt, und Apidog kann Testszenarien direkt aus der Spezifikation generieren, mit Zusicherungen, die aus dem Schema abgeleitet werden. Die Tests überprüfen, ob die tatsächlichen Antworten den dokumentierten Typen, erforderlichen Feldern und Statuscodes entsprechen. Sie erstellen und pflegen diese Szenarien visuell, anstatt parallel eine Reihe von Testskripten manuell zu schreiben, die bei jeder Vertragsänderung auseinanderdriften.

Da Apidog Design, Mocking und Testen in einem einzigen Arbeitsbereich zusammenhält, bleibt die Spezifikation in all diesen Bereichen die einzige Quelle der Wahrheit. Sie können Apidog herunterladen und eine bestehende Spezifikation importieren, um die Schleife an Ihrer eigenen API auszuprobieren. Wenn Sie sich noch nicht entschieden haben, wie Sie die Spezifikation überhaupt versionsübergreifend kontrollieren wollen, passt die Anleitung zum Versionskontrollieren einer OpenAPI-Spezifikation mit Git gut zu diesem Workflow.

Die Apidog CLI ist das, was diese Szenarien kopflos in Ihrer Pipeline ausführt. Es ist ein npm-Paket:

npm install -g apidog-cli

Sie führen ein Szenario anhand der ID aus, richten es auf die Umgebung aus, die Sie validieren möchten, und fordern einen CI-freundlichen Bericht an:

apidog run \
  --access-token $APIDOG_ACCESS_TOKEN \
  -t <scenarioId> \
  -e <environmentId> \
  -r junit,cli \
  --out-dir ./apidog-reports

Das Access Token authentifiziert den Lauf und lebt in einem CI-Secret, niemals in einer committeten Datei. Das Flag -t wählt das Szenario aus, -e wählt die Umgebung aus, und -r junit,cli gibt maschinenlesbares JUnit XML für Ihr CI-Dashboard sowie lesbare Terminalausgabe für das Build-Log aus. Sie raten nicht bei den IDs: Sie kopieren den genauen Befehl, mit den bereits ausgefüllten echten Szenario- und Umgebungs-IDs, von der CI/CD-Registerkarte des Szenarios in Apidog. Wenn Sie die gesamte Optionsoberfläche wünschen, dokumentiert der vollständige CLI-Leitfaden jedes Flag, und apidog run --help druckt sie auf Anfrage.

Das Gate-Verhalten folgt dem gleichen Prinzip wie das Diff. Wenn eine Assertion fehlschlägt, weil eine Live-Antwort nicht mehr dem Vertrag entspricht, beendet apidog run den Vorgang mit einem Exit-Code ungleich Null. CI liest den Exit-Code, markiert den Schritt als fehlgeschlagen und blockiert den Merge. Keine zusätzliche Konfiguration. Solange der Run-Schritt in der Pipeline ist, stoppt eine Vertragsregression die Linie genauso wie ein Breaking-Change-Diff.

Die vollständige Pre-Merge-Sequenz

Fügen Sie die beiden Hälften zusammen, und Sie erhalten eine Pipeline, die beide Arten von Brüchen erkennt. Der Diff fängt Änderungen ab, die einen Client durch Lesen der Spezifikation zerstören würden. Der Vertragstest fängt einen Dienst ab, der die Spezifikation nicht mehr einhält, indem er die laufende API testet. Führen Sie sie als separate Jobs aus:

jobs:
  breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml
      - run: curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
      - run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

  contract-conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g apidog-cli
      - name: Run contract tests
        run: |
          apidog run \
            --access-token "$APIDOG_ACCESS_TOKEN" \
            -t 605067 \
            -e 1629989 \
            -r junit,cli \
            --out-dir ./apidog-reports
        env:
          APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: apidog-report
          path: ./apidog-reports

Die beiden Jobs laufen parallel. Der Diff-Job liest Dateien und benötigt nichts außer Git, sodass er in Sekunden abgeschlossen ist. Der Konformitäts-Job benötigt eine erreichbare Umgebung, daher läuft er normalerweise gegen einen bereitgestellten Staging-Build. Das if: always() beim Hochladen sorgt dafür, dass der Bericht auch dann fließt, wenn die Tests fehlschlagen, was genau der Zeitpunkt ist, an dem Sie ihn lesen möchten. Wenn einer der Jobs rot wird, ist der PR blockiert. Für weitere Informationen zum Ausführen der CLI in realen Pipelines gehen der Apidog CLI GitHub Actions Guide und der umfassendere CI/CD Pipeline Walkthrough tiefer auf die Verdrahtung ein.

Schaltfläche

Praktizieren Sie API Design-First in Apidog

Entdecken Sie eine einfachere Möglichkeit, APIs zu erstellen und zu nutzen