# Evolution von Datenkontrakten

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

Source: https://learn.datacontract.com/de/evolution/

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`.

_Scenario: the Orders data product (output ports orders_v1 and orders_v2) is consumed by the SKU Sales data product, which the purchasing team uses._

**You will learn:**
- 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`.

macOS / Linux:

```bash
docker compose exec postgres psql -U workshop -d workshop -c '\dt orders_v2.*' -c 'SELECT * FROM orders_v2.line_items LIMIT 5;'
```

Windows (PowerShell):

```powershell
docker compose exec postgres psql -U workshop -d workshop -c '\dt orders_v2.*' -c 'SELECT * FROM orders_v2.line_items LIMIT 5;'
```

Output:

```text
             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:

macOS / Linux:

```bash
cp orders_v1.odcs.yaml orders_v2.odcs.yaml
datacontract edit orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
Copy-Item 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:

| Feld | Wert |
|---|---|
| ID | `orders_v2` |
| Version | `2.0.0` |
| Status | `draft` |

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.

> **Tip**
>
> 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:

| Property | Logical Type | Physical Type |
|---|---|---|
| `quantity` | `integer` | `BIGINT` |

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

**Solution: Property quantity**

```yaml
- name: quantity
  physicalType: bigint
  logicalType: integer
  quality:
    - type: sql
      description: Ensure quantity is positive
      query: SELECT COUNT(*) FROM orders_v2.line_items WHERE quantity <= 0;
      mustBe: 0
```

![line_items in orders_v2 mit der neuen Property quantity](https://learn.datacontract.com/screenshots/editor-quantity.webp)

### v2 testen

Speichere und führe die Tests aus:

macOS / Linux:

```bash
datacontract test orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract test orders_v2.odcs.yaml
```

Output:

```text
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

macOS / Linux:

```bash
datacontract changelog orders_v1.odcs.yaml orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract changelog orders_v1.odcs.yaml orders_v2.odcs.yaml
```

Output:

```text
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:

macOS / Linux:

```bash
datacontract breaking orders_v1.odcs.yaml orders_v2.odcs.yaml
echo "exit code: $?"
```

Windows (PowerShell):

```powershell
datacontract breaking orders_v1.odcs.yaml orders_v2.odcs.yaml
echo "exit code: $LASTEXITCODE"
```

Output:

```text
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 **WARNING**s, 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:

macOS / Linux:

```bash
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: $?"
```

Windows (PowerShell):

```powershell
Copy-Item 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: $LASTEXITCODE"
```

Output:

```text
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](https://learn.datacontract.com/de/ci-cd/).

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

macOS / Linux:

```bash
mv orders_v2.before.odcs.yaml orders_v2.odcs.yaml
datacontract test orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
Move-Item -Force orders_v2.before.odcs.yaml orders_v2.odcs.yaml
datacontract test orders_v2.odcs.yaml
```

Output:

```text
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](https://learn.datacontract.com/de/consumer-driven/) 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:

macOS / Linux:

```bash
cp orders_v2.odcs.yaml orders_v2.before.odcs.yaml
```

Windows (PowerShell):

```powershell
Copy-Item 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:

macOS / Linux:

```bash
datacontract breaking orders_v2.before.odcs.yaml orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract breaking orders_v2.before.odcs.yaml orders_v2.odcs.yaml
```

Output:

```text
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:

macOS / Linux:

```bash
datacontract lint orders_v1.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract lint orders_v1.odcs.yaml
```

Output:

```text
╭────────┬────────────────────────────────────────────┬───────┬─────────╮
│ 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:

macOS / Linux:

```bash
datacontract test orders_v2.odcs.yaml
```

Windows (PowerShell):

```powershell
datacontract test orders_v2.odcs.yaml
```

Output:

```text
Testing orders_v2.odcs.yaml
…
🟢 data contract is valid. Run 37 checks. Took 0.63683 seconds.
```

**Solution: orders_v2.odcs.yaml**

```yaml title=orders_v2.odcs.yaml
version: 2.0.0
kind: DataContract
apiVersion: v3.2.0
id: orders_v2
name: Orders
status: active
domain: ecommerce
description:
  purpose: Order data for analytics and reporting
  limitations: Contains PII (customer email addresses)
tags: ['orders', 'ecommerce']
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_v2.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_v2.orders GROUP BY 1 ORDER BY 1;
  constraints:
  - constraint: Never output customer_email_address or customer_id. Aggregate the data instead.
    tags: ['pii']
servers:
- server: postgres
  type: postgres
  host: localhost
  port: 5433
  database: workshop
  schema: orders_v2
schema:
- name: orders
  synonyms:
  - synonym: purchases
  - synonym: Bestellungen
    locale: de
  context:
    instructions: One row per order. Join line_items on order_id to get the purchased SKUs.
  physicalType: table
  logicalType: object
  physicalName: orders
  properties:
  - name: order_id
    physicalType: text
    logicalType: string
    required: true
    primaryKey: true
    quality:
    - type: text
      description: Must be a valid UUID
    - type: sql
      description: Ensure order_id is unique
      query: SELECT COUNT(*) FROM (SELECT order_id FROM orders_v2.orders GROUP BY order_id HAVING COUNT(*) > 1) duplicates;
      mustBe: 0
  - name: order_timestamp
    physicalType: timestamptz
    logicalType: date
    quality:
    - type: text
      description: Must not be in the future
    - type: sql
      description: Ensure order_timestamp is not in the future
      query: SELECT COUNT(*) FROM orders_v2.orders WHERE order_timestamp > CURRENT_TIMESTAMP;
      mustBe: 0
  - name: order_total
    physicalType: bigint
    logicalType: integer
    quality:
    - type: text
      description: Order total in cents, must be non-negative
    - type: sql
      description: Ensure order_total is non-negative
      query: SELECT COUNT(*) FROM orders_v2.orders WHERE order_total < 0;
      mustBe: 0
  - name: customer_id
    deprecated: true
    physicalType: text
    logicalType: string
    quality:
    - type: text
      description: Must be a non-empty alphanumeric identifier
    - type: sql
      description: Ensure customer_id is not empty
      query: SELECT COUNT(*) FROM orders_v2.orders WHERE customer_id IS NULL OR customer_id = '';
      mustBe: 0
  - name: customer_email_address
    physicalType: text
    logicalType: string
    required: true
    classification: confidential
    examples: ['test394@example.org', 'test4757@example.org']
    quality:
    - type: text
      description: Must be a valid email address
    - type: sql
      description: Ensure email addresses contain @
      query: SELECT COUNT(*) FROM orders_v2.orders WHERE customer_email_address NOT LIKE '%@%';
      mustBe: 0
  quality:
  - type: text
    description: Orders table must not be empty
  - type: sql
    description: Ensure orders table has data
    query: SELECT COUNT(*) FROM orders_v2.orders;
    mustBeGreaterThan: 0
  - type: sql
    description: Ensure every order has at least one line item
    query: SELECT COUNT(*) FROM orders_v2.orders o LEFT JOIN orders_v2.line_items li ON o.order_id = li.order_id WHERE li.order_id IS NULL;
    mustBe: 0
- name: line_items
  synonyms:
  - synonym: order lines
  - synonym: Bestellpositionen
    locale: de
  context:
    instructions: One row per purchased SKU in an order.
  physicalType: table
  logicalType: object
  physicalName: line_items
  properties:
  - name: lines_item_id
    physicalType: text
    logicalType: string
    required: true
    primaryKey: true
    quality:
    - type: text
      description: Must be a valid UUID
    - type: sql
      description: Ensure lines_item_id is unique
      query: SELECT COUNT(*) FROM (SELECT lines_item_id FROM orders_v2.line_items GROUP BY lines_item_id HAVING COUNT(*) > 1) duplicates;
      mustBe: 0
  - name: order_id
    physicalType: text
    logicalType: string
    required: true
    relationships:
    - type: foreignKey
      to: orders.order_id
  - name: sku
    synonyms:
    - synonym: article number
    - synonym: Artikelnummer
      locale: de
    physicalType: text
    logicalType: string
    quality:
    - type: text
      description: Must be a non-empty product SKU
    - type: sql
      description: Ensure sku is not empty
      query: SELECT COUNT(*) FROM orders_v2.line_items WHERE sku IS NULL OR sku = '';
      mustBe: 0
  - name: quantity
    physicalType: bigint
    logicalType: integer
    quality:
    - type: text
      description: Must be a positive integer, defaults to 1
    - type: sql
      description: Ensure quantity is positive
      query: SELECT COUNT(*) FROM orders_v2.line_items WHERE quantity <= 0;
      mustBe: 0
  quality:
  - type: text
    description: Line items table must not be empty
  - type: sql
    description: Ensure line_items table has data
    query: SELECT COUNT(*) FROM orders_v2.line_items;
    mustBeGreaterThan: 0
  - type: sql
    description: Ensure every line_items.order_id exists in orders
    query: SELECT COUNT(*) FROM orders_v2.line_items li LEFT JOIN orders_v2.orders o ON li.order_id = o.order_id WHERE o.order_id IS NULL;
    mustBe: 0
team:
  name: order_data_team
  members:
  - username: owner@example.com
    role: Owner
support:
- channel: "#order-data-help"
  url: https://example.slack.com/archives/order-data-help
  tool: slack
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 daily
```

**Quick check:** Welche Änderung am aktiven Kontrakt orders_v2 ist für Consumer ein Breaking Change?

- Eine neue Spalte hinzufügen
- Den physischen Typ einer Spalte von BIGINT auf TEXT ändern (correct)
- Einer Spalte eine Beschreibung geben
- Eine Spalte mit deprecated: true markieren

Eine Typänderung bricht jeden Consumer, der sich auf den alten Typ verlässt, vom SQL-Vergleich bis zum nachgelagerten Schema. `datacontract breaking` meldet sie als ERROR, sie gehört also in eine neue Major-Version.

## 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.
