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
| Feld | Inhalt | Pflicht |
|---|---|---|
| openapi | Versionsnummer der Spezifikation | ja |
| info | Titel, Version und Beschreibung der API | ja |
| servers | Basisadressen der Umgebungen | nein |
| paths | Pfade und ihre Operationen | eines der drei |
| webhooks | vom Anbieter ausgehende Aufrufe | eines der drei |
| components | wiederverwendbare Bausteine | eines der drei |
| security, tags | Vorgaben und Gliederung | nein |
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.
| in | wofür |
|---|---|
| path | Teile der Adresse |
| query | Filter und Seitenaufteilung |
| header | Angaben wie den Idempotenzschlüssel |
| cookie | Ausnahmen |
| querystring | die 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
| Gruppe | Schlüsselwörter |
|---|---|
| beliebiger Typ | type, enum, const |
| Zahlen | multipleOf, maximum, minimum, exclusiveMaximum |
| Zeichenketten | maxLength, minLength, pattern |
| Arrays | maxItems, minItems, uniqueItems, minContains |
| Objekte | required, dependentRequired, maxProperties |
| Anmerkungen | title, 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üft | Anwendung prüft |
|---|---|
| Feld ist vorhanden und eine Zeichenkette | Abo existiert und ist aktiv |
| Anzahl liegt zwischen 1 und 4 | Kontingent des Lieferfensters reicht |
| Datum ist ein gültiges Datum | Datum liegt nicht in der Vergangenheit |
| Wert steht in der Aufzählung | Kunde 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
| Rolle | Entscheidet über | Wird gehört bei |
|---|---|---|
| Providerteam | Umsetzung und Zeitplan | jeder Änderung |
| API-Verantwortung | Kompatibilität und Stil | Freigabe |
| Consumerteams | eigenen Bedarf | Bruch und Frist |
| Sicherheit | Anmeldeverfahren und Rechte | neuen 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.
| Artefakt | Erzeugen lohnt | Vorsicht bei |
|---|---|---|
| Client-SDK | fast immer | eigenen Anpassungen im Code |
| Server-Stub | am Projektanfang | Wiederholung im Bestand |
| Modellklassen | ja, im Build | handgepflegter Logik darin |
| Dokumentation | immer | manuellen 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
| Stufe | Frage | Läuft gegen |
|---|---|---|
| Unit | Rechnet die Logik richtig? | nichts Externes |
| Komponente | Verhält sich der Dienst richtig? | eigene Anwendung |
| Contract | Passen Anfrage und Antwort zusammen? | Vertrag oder Pact |
| Integration | Reden zwei echte Teile miteinander? | echte Nachbarn |
| End-to-End | Funktioniert 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
| Merkmal | Pact | Spring Cloud Contract |
|---|---|---|
| Herkunft des Vertrags | aus dem Consumer-Test | als Datei im Projekt |
| Richtung | consumergetrieben | beides möglich |
| Consumer-Sprachen | viele | JVM im Vordergrund |
| Verteilung | Pact Broker | Artefakt-Repository oder Git |
| Stubs für Consumer | über Broker | WireMock 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
| Änderung | Antwort | Anfrage |
|---|---|---|
| neues optionales Feld | kompatibel | kompatibel |
| neues Pflichtfeld | kompatibel | Bruch |
| Feld entfernt | Bruch | kompatibel |
| Feld umbenannt | Bruch | Bruch |
| Typ geändert | Bruch | Bruch |
| Aufzählungswert ergänzt | oft Bruch | kompatibel |
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
infowird nie erhöht, obwohl sich die API ändert. format: emailgilt als Prüfung, obwohl das Werkzeug nur eine Anmerkung sammelt.requiredfehlt, und jedes Feld ist unbemerkt optional.oneOfmit ü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