Start / Cheat Sheets

Cheat Sheet

API-Vertrag und Contract Testing — Cheat Sheet

Stand: · Moderne API-Entwicklung mit REST, OpenAPI und Contract Testing

OpenAPIJSON SchemaContract TestingPact

Der Spickzettel zum Seminar Moderne API-Entwicklung mit REST, OpenAPI und Contract Testing, zweiter Teil: der Vertrag als Datei — wie er aufgebaut ist, was ein Schema prüfen kann und wie er sich ändern darf, ohne Consumer zu brechen. Ressourcenschnitt, HTTP-Semantik und Fehlerformat stehen auf dem ersten Blatt, API-Design und HTTP. Maßgeblich sind die Spezifikationen selbst — OpenAPI, JSON Schema und die Dokumentation von Pact und Spring Cloud Contract; hier steht die Auswahl, die im Alltag zählt. Das Blatt hält Entscheidungen fest, die Methode bringt es nicht bei.

Aufbau eines OpenAPI-Dokuments

FeldInhaltPflicht
openapiVersionsnummer der Spezifikationja
infoTitel, Version und Beschreibung der APIja
serversBasisadressen der Umgebungennein
pathsPfade und ihre Operationeneines der drei
webhooksvom Anbieter ausgehende Aufrufeeines der drei
componentswiederverwendbare Bausteineeines der drei
security, tagsVorgaben und Gliederungnein

Die Version in info gehört der API, das Feld openapi der Spezifikation — beide werden gern verwechselt.

openapi: 3.2.0
info:
  title: Hofkiste API
  version: 2.3.0
servers:
  - url: https://api.hofkiste.example/v2
    name: produktion
tags:
  - name: Bestellungen

Das Feld name am Server ist neu in 3.2 und benennt die Umgebung für Werkzeuge. In components gehört alles Wiederholte: schemas, parameters, responses, requestBodies, securitySchemes, examples, headers, links, callbacks, pathItems.

Modul: OpenAPI 3.2 kennenlernen

Operationen und Parameter

get:
  operationId: bestellungLesen
  parameters:
    - name: bestellId
      in: path
      required: true
      schema: { type: string }
  responses:
    "200":
      $ref: "#/components/responses/Bestellung"

Die operationId ist der Name, unter dem Codegeneratoren die Methode anlegen — sie sollte stabil bleiben.

inwofür
pathTeile der Adresse
queryFilter und Seitenaufteilung
headerAngaben wie den Idempotenzschlüssel
cookieAusnahmen
querystringdie gesamte Abfragezeichenkette als ein Wert, neu in 3.2

Ein Parameter hat entweder schema oder content, niemals beides.

Modul: OpenAPI 3.2 kennenlernen

JSON Schema: Schlüsselwörter

GruppeSchlüsselwörter
beliebiger Typtype, enum, const
ZahlenmultipleOf, maximum, minimum, exclusiveMaximum
ZeichenkettenmaxLength, minLength, pattern
ArraysmaxItems, minItems, uniqueItems, minContains
Objekterequired, dependentRequired, maxProperties
Anmerkungentitle, description, default, deprecated, readOnly

readOnly und writeOnly trennen, was gelesen und was geschrieben werden darf — ohne zwei Schemas.

format ist eine Anmerkung, keine Prüfung. Prüfende Auswertung muss die Implementierung anbieten und ist standardmäßig aus; wer echte Prüfung braucht, nimmt pattern und Constraints.

type: object
required: [aboId, kistenGroesse]
properties:
  aboId:
    type: string
    pattern: "^a-[0-9]{4}$"
  kistenGroesse:
    enum: [klein, mittel, gross]
  anzahl:
    type: integer

Modul: Datenmodelle mit JSON Schema beschreiben

Varianten und Grenzen des Schemas

oneOf:
  - $ref: "#/components/schemas/StandardKiste"
  - $ref: "#/components/schemas/WunschKiste"
  - $ref: "#/components/schemas/ProbierKiste"
discriminator:
  propertyName: art
  mapping:
    standard: "#/components/schemas/StandardKiste"

Der discriminator erspart dem Consumer das Durchprobieren aller Varianten — sein Feld muss in required stehen. defaultMapping als Rückfallebene ist neu in 3.2. Ein Schema pro fachlichem Begriff, nicht pro Endpunkt.

Schema prüftAnwendung prüft
Feld ist vorhanden und eine ZeichenketteAbo existiert und ist aktiv
Anzahl liegt zwischen 1 und 4Kontingent des Lieferfensters reicht
Datum ist ein gültiges DatumDatum liegt nicht in der Vergangenheit
Wert steht in der AufzählungKunde darf diese Kistenart bestellen

Fällt eine Prüfung in die rechte Spalte, gehört sie in die Anwendung — und ihre Antwort ins Fehlerformat. default beschreibt, was der Server einsetzt; eine Prüfung ist es nicht.

Modul: Datenmodelle mit JSON Schema beschreiben

Ownership und Codegenerierung

RolleEntscheidet überWird gehört bei
ProviderteamUmsetzung und Zeitplanjeder Änderung
API-VerantwortungKompatibilität und StilFreigabe
Consumerteamseigenen BedarfBruch und Frist
SicherheitAnmeldeverfahren und Rechteneuen Endpunkten

Die Zuständigkeit gehört ins Repository, nicht in eine Präsentation. Ein Style Guide entscheidet wiederkehrende Fragen einmal statt in jedem Review — und taugt nur etwas, wenn er automatisch geprüft wird.

ArtefaktErzeugen lohntVorsicht bei
Client-SDKfast immereigenen Anpassungen im Code
Server-Stubam ProjektanfangWiederholung im Bestand
Modellklassenja, im Buildhandgepflegter Logik darin
Dokumentationimmermanuellen Nachbesserungen

Alles Erzeugte gehört in den Build, nicht als Kopie ins Repository — sonst pflegt man beides. Und es wird nie von Hand nachbearbeitet, sondern über den Vertrag geändert.

Module: Design-first, Code-first oder Hybrid · Dokumentation, Mocks und Codegenerierung

Teststufen

StufeFrageLäuft gegen
UnitRechnet die Logik richtig?nichts Externes
KomponenteVerhält sich der Dienst richtig?eigene Anwendung
ContractPassen Anfrage und Antwort zusammen?Vertrag oder Pact
IntegrationReden zwei echte Teile miteinander?echte Nachbarn
End-to-EndFunktioniert der Ablauf im Ganzen?alles

Je weiter unten, desto schneller und stabiler — je weiter oben, desto teurer der rote Lauf. Contract Tests sind eine eigene Stufe, weil sie die Gegenseite nicht laufend brauchen und Brüche vor dem Deployment finden. Sie prüfen die Struktur der Nachrichten, nicht die Geschäftslogik — und ersetzen keine funktionalen Tests des Providers.

Modul: Teststrategie für APIs entwickeln

Pact: Broker und Freigabe

pact-broker record-deployment \
  --pacticipant kunden-app \
  --version 2.3.0 --environment produktion
pact-broker can-i-deploy \
  --pacticipant kunden-app \
  --version 2.4.0 --to-environment produktion

Ohne record-deployment weiß der Broker nicht, welche Stände draußen sind — und kann nichts vergleichen. can-i-deploy prüft die Kandidatenversion gegen die dort laufenden Stände; fehlt ein erfolgreiches Verifikationsergebnis, lautet die Antwort nein. Mehrere Consumer werden einzeln geprüft, --retry-while-unknown wartet auf laufende Providerläufe.

Der Pact entsteht aus dem Consumer-Test, nicht von Hand — und ersetzt die Beschreibung der API ausdrücklich nicht.

Modul: Consumer-driven Contract Testing mit Pact

Spring Cloud Contract

Contract.make {
  request {
    method 'GET'
    url '/bestellungen/9f2c'
  }
  response {
    status OK()
    body([bestellId: $(regex('[0-9a-f]{4}'))])
  }
}

Derselbe Vertrag erzeugt den Providertest und den Stub, gegen den die Consumer laufen. Matcher statt fester Werte — sonst hängt er an Testdaten.

@SpringBootTest
@AutoConfigureStubRunner(
  ids = "de.hofkiste:bestell-service:+:stubs:8090",
  stubsMode = StubRunnerProperties.StubsMode.LOCAL)
class BestelluebersichtTest {
}

LOCAL nimmt Stubs aus dem lokalen Repository, REMOTE lädt sie aus dem entfernten.

Modul: Provider Contracts mit Spring Cloud Contract

Pact oder Spring Cloud Contract

MerkmalPactSpring Cloud Contract
Herkunft des Vertragsaus dem Consumer-Testals Datei im Projekt
Richtungconsumergetriebenbeides möglich
Consumer-SprachenvieleJVM im Vordergrund
VerteilungPact BrokerArtefakt-Repository oder Git
Stubs für Consumerüber BrokerWireMock aus dem Vertrag

In gemischten Landschaften entscheidet meist, welche Sprachen die Consumer sprechen — nicht, welches Werkzeug bekannter ist. Zweite Frage: ob Freigaben an einer Matrix hängen sollen wie bei can-i-deploy.

Modul: Provider Contracts mit Spring Cloud Contract

Kompatibel oder Bruch

ÄnderungAntwortAnfrage
neues optionales Feldkompatibelkompatibel
neues PflichtfeldkompatibelBruch
Feld entferntBruchkompatibel
Feld umbenanntBruchBruch
Typ geändertBruchBruch
Aufzählungswert ergänztoft Bruchkompatibel

Ein neuer Aufzählungswert bricht Consumer, die alle Fälle abschließend behandeln — deshalb ankündigen. Und wer nur die Antwortrichtung prüft, übersieht die Hälfte. RFC 9205 sieht für Weiterentwicklung eine eigene Link-Relation, einen eigenen Medientyp oder ein eigenes Header-Feld vor — nicht, die Protokollversion zur Bedingung zu machen.

Modul: API-Evolution, Auslieferung und KI-Unterstützung

Deprecation und Sunset

HTTP/1.1 200 OK
Deprecation: @1788169599
Sunset: Sun, 30 Jun 2027 23:59:59 UTC
Link: <https://api.hofkiste.example/migration>;
      rel="deprecation"; type="text/html"

Deprecation (RFC 9745) nennt den Zeitpunkt der Abkündigung, Sunset (RFC 8594) das Ende der Erreichbarkeit — Sunset darf nie vor Deprecation liegen. Die Link-Relation deprecation führt auf die Dokumentation für Menschen. Clients müssen auch ohne diese Angaben funktionieren: Sie sind ein Hinweis, keine Bedingung. Dazu gehört ein Migrationsleitfaden, der den Weg von alt nach neu zeigt — nicht nur das Neue beschreibt.

Modul: API-Evolution, Auslieferung und KI-Unterstützung

Typische Fallen

  • Der Vertrag existiert doppelt — als Datei und in den Annotationen.
  • Design-first wird ausgerufen, aber niemand prüft die Implementierung dagegen.
  • Die Version in info wird nie erhöht, obwohl sich die API ändert.
  • format: email gilt als Prüfung, obwohl das Werkzeug nur eine Anmerkung sammelt.
  • required fehlt, und jedes Feld ist unbemerkt optional.
  • oneOf mit überschneidenden Varianten trifft nie genau eine.
  • Der erzeugte Client wird eingecheckt und dann von Hand repariert.
  • Der Mock antwortet immer erfolgreich, und Fehlerpfade bleiben ungetestet.
  • Linting steht auf Warnung, und niemand liest die Ausgabe.
  • Provider States werden frei formuliert und sind beim Provider nicht herstellbar.
  • Die Pact-Datei liegt im Repository des Consumers und erreicht den Provider nie.
  • Contract Tests laufen nach dem Deployment statt davor.
  • Ein KI-erzeugter Vertrag wird übernommen, weil er plausibel aussieht — erfundene Felder wandern in die Dokumentation und werden zur Zusage.

Zum Seminar Moderne API-Entwicklung mit REST, OpenAPI und Contract Testing