Datenkontrakte in der Praxis
Fortschritt
0%
ende
Los geht’s
  • Willkommen15′
  • Setup20′
Teil A · Das Quell-Datenprodukt
  • 1.Daten unter Vertrag nehmen60′
  • 2.Evolution von Datenkontrakten30′
  • 3.Datenprodukt beschreiben20′
Teil B · Das Consumer-Aligned Data Product
  • 4.Contract-first entwerfen30′
  • 5.Datenprodukt implementieren25′
  • 6.Consumer-driven Contracts30′
Teil C · Automatisieren
  • 7.CI/CD mit GitHub Actions45′
Teil D · Datenplattformoptional
  • 8.Auf Entropy Data veröffentlichen40′
  • 9.Semantik25′
Abschluss
  • Abschluss10′
Teil A · Das Quell-Datenprodukt

Übung 2 · Evolution von Datenkontrakten

Einen Breaking Change als neue Major-Version veröffentlichen und migrieren.

~30 Min.0 von 11 Schritten erledigt
Zurück
Daten unter Vertrag nehmen
Weiter
Datenprodukt beschreiben
Gepflegt vonEntropy Data

Das Business möchte wissen, wie viele Einheiten eines Artikels pro Bestellung gekauft werden. Das Orders-Team ergänzt in line_items eine Spalte quantity mit Standardwert 1. Harmlos? Nicht für eine Pipeline, die genau drei Spalten erwartet, etwa ein SELECT * in eine feste Zieltabelle. Deshalb veröffentlichst du die Änderung als neue Major-Version: orders_v2, in einem eigenen Datenbankschema, neben orders_v1.

Teil ATeil BSource-aligned DatenproduktOrdersOrder Data Team · PostgreSQLorders_v1 (veraltet)orders_v2 (neu)Input-Ports zur Vereinfachung weggelassenConsumer-aligned DatenproduktSKU SalesPurchasing Analytics Team · SQL viewsku_sales_per_yearData ConsumerEinkaufsteamverhandelt mit Lieferanten
In dieser Übung: eine neue Major-Version orders_v2 mit der Spalte quantity. orders_v1 geht in Rente.
Das lernst du
  • Eine Änderung als neue Major-Version eines Datenkontrakts veröffentlichen
  • Zwei Versionen mit datacontract changelog und datacontract breaking vergleichen
  • Verstehen, was als Breaking Change gilt und wo Werkzeuge an ihre Grenzen stoßen
  • Entfernungen früh ankündigen mit dem Flag deprecated (neu in ODCS 3.2)
  • Den Lebenszyklus eines Kontrakts durchlaufen: draft → active → deprecated → retired
Breaking vs. Non-Breaking Changes

Eine Änderung ist breaking, wenn ein Consumer, der gestern funktioniert hat, heute fehlschlagen kann, ohne selbst etwas geändert zu haben. Typische Breaking Changes:

  • ein Feld oder eine Tabelle entfernen oder umbenennen
  • einen Typ ändern, z. B. BIGINT → TEXT
  • eine Garantie abschwächen, z. B. ein required-Feld wird optional oder eine Qualitätsregel wird gelockert

Ein neues optionales Feld, bessere Beschreibungen oder zusätzliche Quality Checks sind typischerweise non-breaking.

Kontrakte nutzen Semantic Versioning: Breaking Changes erhöhen die Major-Version (1.x → 2.0.0), kompatible Erweiterungen die Minor-Version, Korrekturen die Patch-Version.

Die neuen Daten ansehen

Das Schema orders_v2 erkunden

Das Schema orders_v2 enthält beide Tabellen: orders ist unverändert, line_items hat die neue Spalte quantity.

docker compose exec postgres psql -U workshop -d workshop -c '\dt orders_v2.*' -c 'SELECT * FROM orders_v2.line_items LIMIT 5;'
             List of relations
  Schema   |    Name    | Type  |  Owner
-----------+------------+-------+----------
 orders_v2 | line_items | table | workshop
 orders_v2 | orders     | table | workshop
(2 rows)

            lines_item_id             |               order_id               |      sku      | quantity
--------------------------------------+--------------------------------------+---------------+----------
 94aa82c8-50ba-47fb-994a-9b041b4127af | a8c38fec-2acd-4b55-883b-4b48572d4a26 | D3KT74L5EV46T |        1
 d67c963f-42a4-4aa8-afff-d7869008e3a9 | 9e44da97-4f72-4bcf-821a-9d9500d06651 | E202K62FT     |        3
 270ad2c1-f651-438e-a81a-d77713c1d3a3 | 8fc4621c-66ae-4031-91f1-5313beb9f541 | 1O7RID9Y5QJ   |        1
 cc763a72-cc07-4bc4-8ddf-c88d09db5daa | 98d48daf-3532-4a59-b7c2-3777164bdc65 | 7KJ8466FI39LW |        5
 d7ea7f72-a266-469c-a9ef-60063d5ac243 | 2fd9df43-77e8-4d00-b380-ab270e8b73f8 | 7HXBABF0AOT5  |        2
(5 rows)

v2 erstellen

Den Kontrakt kopieren

Kopiere deinen v1-Kontrakt und öffne die Kopie im Editor:

cp orders_v1.odcs.yaml orders_v2.odcs.yaml
datacontract edit orders_v2.odcs.yaml
Die Version erhöhen

Setze in Fundamentals die neue Major-Version:

FeldWert
IDorders_v2
Version2.0.0
Statusdraft

Die ID ändert sich mit der Major-Version: Consumer wechseln bewusst auf orders_v2, und orders_v1 bleibt verfügbar, bis sie migriert haben.

Den Server auf orders_v2 umstellen

Ändere unter Servers das Schema auf orders_v2.

Ersetze dann in allen SQL-Quality Checks orders_v1. durch orders_v2. (z. B. im Check für customer_email_address). Sonst prüfen sie weiter die alten Tabellen.

Die Kopie hat auch deinen KI-context-Block übernommen. Seine verifiedStatements enthalten SQL-Antworten auf orders_v1. Stelle sie ebenfalls auf orders_v2 um, sonst beantwortet ein KI-Agent Fragen aus der alten Version.

Tipp

Suchen und Ersetzen orders_v1. → orders_v2. in deiner IDE erledigt das in einem Rutsch, für Quality Checks und Verified Statements. Prüfe danach id: und das schema: des Servers.

Die Spalte quantity ergänzen

Ergänze unter Schemas → line_items die neue Property:

PropertyLogical TypePhysical Type
quantityintegerBIGINT

Füge einen Quality Check hinzu, dass quantity immer größer als 0 ist. Nutze das Muster „fehlerhafte Zeilen zählen, null erwarten“.

line_items in orders_v2 mit der neuen Property quantity

v2 testen

Speichere und führe die Tests aus:

datacontract test orders_v2.odcs.yaml
Testing orders_v2.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v2)
╭────────┬───────────────────────────────────────────────┬───────────────────────────────┬─────────╮
│ Result │ Check                                         │ Field                         │ Details │
├────────┼───────────────────────────────────────────────┼───────────────────────────────┼─────────┤
│ passed │ Ensure line_items table has data              │ line_items                    │         │
…
│ passed │ Check that field 'quantity' is present        │ line_items.quantity           │         │
│ passed │ Check that field quantity has physical type   │ line_items.quantity           │         │
│        │ bigint                                        │                               │         │
│ passed │ Ensure quantity is positive                   │ line_items.quantity           │         │
…
╰────────┴───────────────────────────────────────────────┴───────────────────────────────┴─────────╯
🟢 data contract is valid. Run 37 checks. Took 0.63683 seconds.

Alle Checks sollten grün sein, auch der neue Check für quantity.

Versionen vergleichen

Vor einem Release willst du wissen, was sich geändert hat und ob es Consumer bricht. Die CLI vergleicht zwei Kontraktdateien.

Den Changelog anzeigen
datacontract changelog orders_v1.odcs.yaml orders_v2.odcs.yaml
Summary
[ 1 Added ]  [ 16 Updated ]
╭─────────┬─────────────────────────────────────────────────────────────────╮
│ Change  │ Field                                                           │
├─────────┼─────────────────────────────────────────────────────────────────┤
│ Updated │ context.verifiedStatements.How many orders were placed in 2023? │
│ Updated │ context.verifiedStatements.What was the revenue per year?       │
│ Updated │ id                                                              │
│ Updated │ schema.line_items.properties.lines_item_id.quality.[1]          │
│ Added   │ schema.line_items.properties.quantity                           │
…
│ Updated │ servers.Orders                                                  │
│ Updated │ version                                                         │
╰─────────┴─────────────────────────────────────────────────────────────────╯

Details
…

Du bekommst eine Zusammenfassung und eine detaillierte Liste aller Änderungen, auch der umgestellten Quality-Queries und Verified Statements. Eine gute Grundlage für Release Notes.

Auf Breaking Changes prüfen

breaking stuft jede Änderung nach Schweregrad ein:

datacontract breaking orders_v1.odcs.yaml orders_v2.odcs.yaml
echo "exit code: $?"
Summary
[ 11 Warning ]  [ 6 Info ]
╭──────────┬─────────┬─────────────────────────────────────────────────────────────────╮
│ Severity │ Change  │ Field                                                           │
├──────────┼─────────┼─────────────────────────────────────────────────────────────────┤
│ INFO     │ Updated │ context.verifiedStatements.How many orders were placed in 2023? │
│ INFO     │ Updated │ context.verifiedStatements.What was the revenue per year?       │
│ INFO     │ Updated │ id                                                              │
│ WARNING  │ Updated │ schema.line_items.properties.lines_item_id.quality.[1]          │
│ INFO     │ Added   │ schema.line_items.properties.quantity                           │
…
│ INFO     │ Updated │ servers.Orders                                                  │
│ INFO     │ Updated │ version                                                         │
╰──────────┴─────────┴─────────────────────────────────────────────────────────────────╯
…
exit code: 0

quantity wird als INFO gemeldet: Eine zusätzliche Spalte ist schemakompatibel. Die geänderten Quality-Queries sind WARNINGs, weil die Checks jetzt gegen andere Tabellen laufen. Nichts ist ein ERROR, daher ist der Exit-Code 0.

Jetzt provozierst du einen echten Breaking Change. Lege eine Kopie an, ändere den physicalType von quantity auf text (oder lösche customer_id) und vergleiche:

cp orders_v2.odcs.yaml orders_v2.before.odcs.yaml
# jetzt orders_v2.odcs.yaml bearbeiten: physicalType von quantity auf text setzen
datacontract breaking orders_v2.before.odcs.yaml orders_v2.odcs.yaml
echo "exit code: $?"
Summary
[ 1 Error ]
╭──────────┬─────────┬───────────────────────────────────────╮
│ Severity │ Change  │ Field                                 │
├──────────┼─────────┼───────────────────────────────────────┤
│ ERROR    │ Updated │ schema.line_items.properties.quantity │
╰──────────┴─────────┴───────────────────────────────────────╯

Details
╭──────────┬─────────┬────────────────────────────────────────────────────┬───────────┬───────────┬────────────────────────────────╮
│ Severity │ Change  │ Path                                               │ Old Value │ New Value │ Message                        │
├──────────┼─────────┼────────────────────────────────────────────────────┼───────────┼───────────┼────────────────────────────────┤
│ ERROR    │ Updated │ schema.line_items.properties.quantity.physicalType │ bigint    │ text      │ Changed type at                │
│          │         │                                                    │           │           │ schema.line_items.properties.… │
│          │         │                                                    │           │           │ from 'bigint' to 'text'        │
╰──────────┴─────────┴────────────────────────────────────────────────────┴───────────┴───────────┴────────────────────────────────╯
exit code: 1

Diesmal gibt es einen ERROR und Exit-Code 1. Dieser Exit-Code macht den Check zum Gate in CI/CD, siehe CI/CD mit GitHub Actions.

Mach es rückgängig und führe die Tests erneut aus:

mv orders_v2.before.odcs.yaml orders_v2.odcs.yaml
datacontract test orders_v2.odcs.yaml
Testing orders_v2.odcs.yaml
…
🟢 data contract is valid. Run 37 checks. Took 0.63683 seconds.
Werkzeuge sehen Schemas, nicht Consumer

datacontract breaking hält quantity für unproblematisch. Warum trotzdem eine neue Major-Version?

Weil „breaking“ davon abhängt, wie Consumer die Daten nutzen. Ein Consumer, der SELECT * in eine strikte Tabelle lädt oder die exakte Spaltenliste prüft, wird brechen. Das Werkzeug prüft Kompatibilitätsregeln. Seine Consumer muss der Producer trotzdem kennen, und eine Major-Version ist eine bewusste, vorsichtige Entscheidung.

Consumer-driven Contracts zeigt, wie Consumer ihre tatsächlichen Abhängigkeiten explizit machen.

Die Migration durchführen

Der Lebenszyklus eines Kontrakts

Eine Kontraktversion durchläuft diese Zustände:

  1. draft: in Arbeit, nicht darauf aufbauen
  2. active: veröffentlicht, Consumer können sich darauf verlassen
  3. deprecated: noch verfügbar, Consumer sollen migrieren; ergänze eine SLA-Property endOfSupport mit Datum
  4. retired: nicht mehr verfügbar

Eine vollständige Migration: v2 auf active setzen, v1 mit Enddatum abkündigen, das im Support-Kanal ankündigen und v1 stilllegen, sobald niemand mehr darauf zugreift.

Einzelne Felder abkündigen (neu in ODCS 3.2)

Nicht jede Änderung braucht sofort eine neue Major-Version. Mit deprecated: true an einem Schema-Objekt oder einer Property kündigst du an: „Dieses Feld fällt mit der nächsten Major-Version weg, bau nicht mehr darauf.“ Das Feld bleibt dokumentiert und getestet, bestehende Consumer laufen weiter. Es später zu entfernen ist trotzdem ein Breaking Change und braucht die nächste Major-Version.

Ein Feld abkündigen

Das Orders-Team will customer_id in einer künftigen v3 streichen. Kündige es jetzt an. Lege zuerst eine Kopie der aktuellen Version an:

cp orders_v2.odcs.yaml orders_v2.before.odcs.yaml

Ergänze in orders_v2.odcs.yaml an der Property customer_id die Zeile deprecated: true:

YAML
- name: customer_id
  deprecated: true
  physicalType: text
  logicalType: string

Vergleiche beide Versionen:

datacontract breaking orders_v2.before.odcs.yaml orders_v2.odcs.yaml
Summary
[ 1 Info ]
╭──────────┬────────┬──────────────────────────────────────╮
│ Severity │ Change │ Field                                │
├──────────┼────────┼──────────────────────────────────────┤
│ INFO     │ Added  │ schema.orders.properties.customer_id │
╰──────────┴────────┴──────────────────────────────────────╯
…
│ INFO     │ Added  │ schema.orders.properties.customer_id.deprecated │           │ True      │ Added contract at              │
…

Nur INFO: Abkündigen ist non-breaking. Lösche orders_v2.before.odcs.yaml danach.

v1 abkündigen (zur Übung)

Setze in orders_v1.odcs.yaml status: deprecated und ergänze eine SLA-Property endOfSupport:

YAML
slaProperties:
  # ... retention, frequency
  - property: endOfSupport
    value: "2026-12-31"
    description: orders_v1 is replaced by orders_v2. Please migrate until end of 2026.

Prüfe, dass der Kontrakt gültig bleibt:

datacontract lint orders_v1.odcs.yaml
╭────────┬────────────────────────────────────────────┬───────┬─────────╮
│ Result │ Check                                      │ Field │ Details │
├────────┼────────────────────────────────────────────┼───────┼─────────┤
│ passed │ Data contract is valid against ODCS v3.2.0 │       │         │
╰────────┴────────────────────────────────────────────┴───────┴─────────╯
🟢 data contract is valid. Run 1 checks. Took 0.122955 seconds.
v2 veröffentlichen und v1 stilllegen

Als Abkürzung springst du direkt zum Endzustand:

  • setze orders_v2.odcs.yaml auf status: active
  • setze orders_v1.odcs.yaml auf status: retired

Führe die Tests für v2 ein letztes Mal aus:

datacontract test orders_v2.odcs.yaml
Testing orders_v2.odcs.yaml
…
🟢 data contract is valid. Run 37 checks. Took 0.63683 seconds.
Kurzer Check
Welche Änderung am aktiven Kontrakt orders_v2 ist für Consumer ein Breaking Change?

Bonus

  • Exportiere beide Versionen als HTML und vergleiche: datacontract export html orders_v2.odcs.yaml --output orders_v2.odcs.html
  • Sag den Schweregrad voraus, bevor du datacontract breaking ausführst: ein Feld umbenennen, required: true entfernen, eine neue Tabelle hinzufügen.
  • Entferne das abgekündigte customer_id aus einer Kopie von v2 und führe datacontract breaking aus. Das Flag macht das Entfernen nicht kompatibel.