Übung 1 · Daten unter Vertrag nehmen
Schreibe deinen ersten ODCS-Datenkontrakt und teste ihn gegen PostgreSQL.
Schreibe deinen ersten ODCS-Datenkontrakt und teste ihn gegen PostgreSQL.
Du bist Owner der Orders-Daten in der E-Commerce-Plattform deines Unternehmens.
Sie liegen in PostgreSQL in zwei Tabellen: orders (Zeitstempel, Summen, Kundeninformationen) und line_items (die Positionen jeder Bestellung, verknüpft über order_id).
Deine Konsumenten fragen immer wieder: Auf welche Spalten kann ich mich verlassen? Was bedeutet order_total? Wen frage ich, wenn etwas komisch aussieht?
Beantworte das einmal, in einem Datenkontrakt.
Stell sicher, dass die Datenbank läuft (siehe Setup), und öffne einen SQL-Prompt:
docker compose exec postgres psql -U workshop -d workshoppsql (17.10 (Debian 17.10-1.pgdg13+1))
Type "help" for help.
workshop=#Liste die Tabellen auf und sieh dir ein paar Zeilen an:
\dt orders_v1.*
SELECT * FROM orders_v1.orders LIMIT 5;
SELECT * FROM orders_v1.line_items LIMIT 5;Das Ergebnis sieht etwa so aus:
order_id | order_timestamp | order_total | customer_id | customer_email_address
--------------------------------------+------------------------+-------------+----------------------+------------------------
a8c38fec-2acd-4b55-883b-4b48572d4a26 | 2020-01-01 00:00:00+00 | 29747 | 6GSHKOZIEN | [email protected]
9e44da97-4f72-4bcf-821a-9d9500d06651 | 2020-01-01 10:37:00+00 | 55156 | ZN661MOMVMQXRJ | [email protected]
8fc4621c-66ae-4031-91f1-5313beb9f541 | 2020-01-01 20:14:00+00 | 85365 | NF0PRHKQP9W9Q0MTC87P | [email protected]Keine Ausgabe? Prüfe auf Tippfehler: psql bleibt bei falsch geschriebenen Schema- oder Tabellennamen stumm.
Verlasse den Prompt mit \q.
Lege im Root des Repositorys einen Kontrakt an und öffne ihn im Data Contract Editor:
datacontract edit orders_v1.odcs.yamlFile 'orders_v1.odcs.yaml' does not exist. Initialize a new data contract? [y/N]: y
📄 data contract written to orders_v1.odcs.yaml
Editing: /Users/you/learn.datacontract.com/orders_v1.odcs.yaml
Data Contract Editor running at http://localhost:4243
Press Ctrl+C to stopDie CLI fragt, ob sie die Datei anlegen soll. Bestätige mit y. Die neue Datei nutzt ODCS apiVersion: v3.2.0.
Der Editor öffnet sich im Browser. Speichern schreibt direkt in die Datei auf der Festplatte.
Setze in der Form-Ansicht die Grunddaten:
| Feld | Wert |
|---|---|
| Name | Orders |
| ID | orders_v1 |
| Version | 1.0.0 (so lassen) |
| Status | draft (so lassen) |
Gehe zu Servers und füge einen Server hinzu. Er sagt den Werkzeugen, wo die Daten liegen, damit sie sie testen können:
| Feld | Wert |
|---|---|
| Server | Orders |
| Type | postgres |
| Host | localhost |
| Port | 5433 |
| Database | workshop |
| Schema | orders_v1 |
Gehe zu Schemas und lege zwei Schemas mit ihren Properties an.
orders
| Property | Logical Type | Physical Type |
|---|---|---|
order_id | string | TEXT |
order_timestamp | date | TIMESTAMPTZ |
order_total | integer | BIGINT |
customer_id | string | TEXT |
customer_email_address | string | TEXT |
line_items
| Property | Logical Type | Physical Type |
|---|---|---|
lines_item_id | string | TEXT |
order_id | string | TEXT |
sku | string | TEXT |
Klicke auf Save (oben rechts). Lass den Editor offen.
Der Test prüft deinen Kontrakt gegen die echte Datenbank: Gibt es die beschriebenen Tabellen, Spalten und Typen? So fällt auf, wenn Dokumentation und Realität auseinanderlaufen.
Die .env-Datei des Repositorys enthält die Zugangsdaten (workshop / workshop). Die CLI liest sie im Root des Repositorys automatisch.
datacontract test orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v1)
╭────────┬────────────────────────────────────────────────────────────────┬───────────────────────────────┬─────────╮
│ Result │ Check │ Field │ Details │
├────────┼────────────────────────────────────────────────────────────────┼───────────────────────────────┼─────────┤
│ passed │ Check that field 'lines_item_id' is present │ line_items.lines_item_id │ │
│ passed │ Check that field lines_item_id has physical type TEXT │ line_items.lines_item_id │ │
│ passed │ Check that field 'order_id' is present │ line_items.order_id │ │
│ passed │ Check that field order_id has physical type TEXT │ line_items.order_id │ │
…
│ passed │ Check that field 'order_total' is present │ orders.order_total │ │
│ passed │ Check that field order_total has physical type BIGINT │ orders.order_total │ │
╰────────┴────────────────────────────────────────────────────────────────┴───────────────────────────────┴─────────╯
🟢 data contract is valid. Run 16 checks. Took 0.52 seconds.Alle Checks sollten grün sein. Schlagen alle fehl, läuft die Datenbank vermutlich nicht: docker compose up -d.
Tests nützen nur, wenn sie fehlschlagen können. Ändere in orders_v1.odcs.yaml den physicalType von customer_email_address auf integer und teste erneut:
datacontract test orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v1)
╭────────┬──────────────────────────────────────────────────────┬───────────────────────────────┬────────────────────────────────────────────────────╮
│ Result │ Check │ Field │ Details │
├────────┼──────────────────────────────────────────────────────┼───────────────────────────────┼────────────────────────────────────────────────────┤
│ failed │ Check that field customer_email_address has physical │ orders.customer_email_address │ expected physical type 'integer' but the column is │
│ │ type integer │ │ 'text' │
│ passed │ Check that field 'lines_item_id' is present │ line_items.lines_item_id │ │
…
╰────────┴──────────────────────────────────────────────────────┴───────────────────────────────┴────────────────────────────────────────────────────╯
🔴 data contract is invalid, found the following errors:
1) orders.customer_email_address Check that field customer_email_address has physical type integer: expected physical type 'integer' but the column is 'text'Welcher Check schlägt fehl, und warum? Probier andere Fehler aus: eine falsch geschriebene Spalte, eine fehlende Tabelle. Mach die Änderungen danach rückgängig, bis alle Tests wieder grün sind.
Wähle, wie du arbeitest:
datacontract edit orders_v1.odcs.yaml.orders und ecommerce.Bearbeite unter Schemas das Feld orders.customer_email_address:
[email protected],confidential (personenbezogene Daten),true.Speichere und finde die neuen Keys in der YAML-Datei.
datacontract test orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
…
│ passed │ Check that field customer_email_address has no missing values │ orders.customer_email_address │ │
…
🟢 data contract is valid. Run 17 checks. Took 0.55 seconds.Findest du den neuen Check? Ein required-Feld ergibt einen Check auf fehlende Werte.
Optional: Rendere den Kontrakt als HTML und öffne orders_v1.odcs.html im Browser:
datacontract export html orders_v1.odcs.yaml --output orders_v1.odcs.htmlWritten result to orders_v1.odcs.htmlDrücke aus, dass line_items.order_id auf orders.order_id verweist. So wissen Konsumenten, dass der Join sicher ist.
Ergänze das an der Property order_id im Schema line_items:
schema:
# ...
- name: line_items
properties:
# ...
- name: order_id
logicalType: string
physicalType: TEXT
relationships:
- type: foreignKey
to: orders.order_idSchema-Checks prüfen die Struktur, Qualitätschecks den Inhalt.
Ergänze an customer_email_address einen SQL-Check, dass jede E-Mail ein @ enthält:
schema:
- name: orders
properties:
# ...
- name: customer_email_address
# ... (Typen, Beispiele, Klassifizierung)
quality:
- type: sql
description: Ensure email addresses are valid
query: SELECT COUNT(*) FROM orders_v1.orders WHERE customer_email_address NOT LIKE '%@%';
mustBe: 0Die Abfrage zählt ungültige Zeilen, mustBe: 0 stellt sicher, dass es keine gibt. Führe die Tests aus und finde den neuen Check:
datacontract test orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
…
│ passed │ Ensure email addresses are valid │ orders.customer_email_address │ │
…
🟢 data contract is valid. Run 18 checks. Took 0.58 seconds.Ergänze weitere Regeln und teste nach jeder:
order_total ist nie negativorder_id in line_items existiert in ordersorders ist nicht leer (Tipp: mustBeGreaterThan: 0)Für Regeln, die sich (noch) nicht automatisieren lassen, nimm einen Text-Check:
quality:
- type: text
description: Order total is in cents
- type: sql
description: Ensure that ...
query: SELECT COUNT(*) FROM ... WHERE ...;
mustBe: 0Alle Optionen: ODCS-Referenz zur Datenqualität.
KI-Agenten, LLM-Chats und BI-Assistenten fragen Daten zunehmend selbst ab.
Sie lesen das Schema, wissen aber nicht, dass order_total in Cent ist, welche Fragen eine geprüfte Antwort haben oder was sie nie tun dürfen.
Genau dafür gibt es in ODCS 3.2 den Block context.
Öffne im Editor links Context (oder bearbeite das YAML) und ergänze:
context:
instructions: >-
Orders of the e-commerce platform and their line items.
order_total is in cents. Timestamps are in UTC.
Use this data for order volume and revenue analysis.
verifiedStatements:
- question: How many orders were placed in 2023?
answer: SELECT COUNT(*) FROM orders_v1.orders WHERE EXTRACT(YEAR FROM order_timestamp) = 2023;
- question: What was the revenue per year?
answer: SELECT EXTRACT(YEAR FROM order_timestamp)::int AS year, SUM(order_total) / 100.0 AS revenue FROM orders_v1.orders GROUP BY 1 ORDER BY 1;
constraints:
- constraint: Never output customer_email_address or customer_id. Aggregate the data instead.
tags: ['pii']Prüfe deine verifizierte Antwort, bevor du sie veröffentlichst. Führe die Abfrage aus:
docker compose exec postgres psql -U workshop -d workshop -c "SELECT COUNT(*) FROM orders_v1.orders WHERE EXTRACT(YEAR FROM order_timestamp) = 2023;" count
-------
876
(1 row)Kontext an einem Schema-Objekt erklärt, wie man genau diese Tabelle nutzt: Granularität, Joins, Filter. Ergänze unter Schemas → orders:
schema:
- name: orders
context:
instructions: One row per order. Join line_items on order_id to get the purchased SKUs.
# ...Mach dasselbe für line_items, z. B. „One row per purchased SKU in an order.“
Menschen suchen mit ihren eigenen Worten: „purchases“, „Bestellungen“, „Artikelnummer“. ODCS 3.2 hält sie als synonyms an Schema-Objekten und Properties fest, damit Kataloge und Agenten die richtige Tabelle finden.
Ergänze Synonyme im Editor oder im YAML. Die locale (ein BCP-47-Tag) markiert ein Synonym in einer anderen Sprache:
schema:
- name: orders
synonyms:
- synonym: purchases
- synonym: Bestellungen
locale: de
# ...
- name: line_items
synonyms:
- synonym: order lines
properties:
# ...
- name: sku
synonyms:
- synonym: article number
- synonym: Artikelnummer
locale: deValidiere den Kontrakt gegen das Schema von ODCS 3.2:
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.13 seconds.Starte deinen KI-Coding-Agenten (Claude Code, Codex, Copilot, ...) im Repository und frag:
Using the data contract
orders_v1.odcs.yaml, how many orders were placed in 2023? Then list the email addresses of the customers with the most orders.
Beobachte, was er tut:
Konsumenten müssen wissen, wem die Daten gehören und wo sie Hilfe bekommen. Ergänze diese Keys auf oberster Ebene:
team:
name: order_data_team
members:
- username: [email protected]
role: Owner
support:
- channel: "#order-data-help"
url: https://example.slack.com/archives/order-data-help
tool: slackWie lange werden die Daten aufbewahrt, und wie aktuell sind sie? Ergänze auf oberster Ebene slaProperties:
slaProperties:
- property: retention
value: 10
unit: y
element: orders.order_timestamp
description: Orders are deleted after 10 years
- property: frequency
value: 1
unit: d
description: Data updated dailyFühre nur die Service-Level-Checks aus:
datacontract test --checks slaProperties orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v1)
╭────────┬──────────────────────────────────────────────────┬─────────────────┬─────────╮
│ Result │ Check │ Field │ Details │
├────────┼──────────────────────────────────────────────────┼─────────────────┼─────────┤
│ passed │ Retention of orders.order_timestamp < 315360000s │ order_timestamp │ │
╰────────┴──────────────────────────────────────────────────┴─────────────────┴─────────╯
🟢 data contract is valid. Run 1 checks. Took 0.52 seconds.Ergänze ein freshness-Service-Level: Neue Bestellungen sollen innerhalb von 24 Stunden ankommen.
slaProperties:
- property: freshness
value: 24
unit: h
element: orders.order_timestamp
description: New orders arrive within 24 hours
# ... retention and frequency as beforeFühre die Service-Level-Checks erneut aus:
datacontract test --checks slaProperties orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v1)
╭────────┬─────────────────────────────────────────────┬─────────────────┬─────────────────────────────────────────────╮
│ Result │ Check │ Field │ Details │
├────────┼─────────────────────────────────────────────┼─────────────────┼─────────────────────────────────────────────┤
│ failed │ Freshness of orders.order_timestamp < 24h │ order_timestamp │ Freshness is 33337535s, which exceeds the │
│ │ │ │ threshold of 86400s │
│ passed │ Retention of orders.order_timestamp < │ order_timestamp │ │
│ │ 315360000s │ │ │
╰────────┴─────────────────────────────────────────────┴─────────────────┴─────────────────────────────────────────────╯
🔴 data contract is invalid, found the following errors:
1) order_timestamp Freshness of orders.order_timestamp < 24h: Freshness is 33337535s, which exceeds the threshold of
86400sDer Check schlägt fehl: Die neueste Bestellung ist vom September 2025. Die Workshop-Daten sind ein fester Stand, also nie aktuell. In Produktion meldet genau dieser Check, wenn eine Pipeline keine Daten mehr lädt.
Entferne den freshness-Eintrag wieder, damit dein Kontrakt (und später deine CI-Pipeline) grün bleibt.
Dein Kontrakt ist fertig. Setze den status auf active: Ab jetzt können sich Konsumenten darauf verlassen.
Führe die Tests ein letztes Mal aus:
datacontract test orders_v1.odcs.yamlTesting orders_v1.odcs.yaml
Server: Orders (type=postgres, host=localhost, port=5433, database=workshop, schema=orders_v1)
╭────────┬───────────────────────────────────────────────────────────────────┬───────────────────────────────┬─────────╮
│ Result │ Check │ Field │ Details │
├────────┼───────────────────────────────────────────────────────────────────┼───────────────────────────────┼─────────┤
│ passed │ Ensure line_items table has data │ line_items │ │
│ passed │ Ensure every line_items.order_id exists in orders │ line_items │ │
…
│ passed │ Ensure email addresses contain @ │ orders.customer_email_address │ │
…
╰────────┴───────────────────────────────────────────────────────────────────┴───────────────────────────────┴─────────╯
🟢 data contract is valid. Run 34 checks. Took 0.65 seconds.datacontract export sql orders_v1.odcs.yaml (alle Export-Formate)datacontract lint orders_v1.odcs.yamldatacontract catalog