Nach Block 3 kann das Ticket-System fachliche Abläufe mit Transaktionen schützen. Ein Ticket, ein Kommentar und ein Event werden gemeinsam gespeichert oder gemeinsam zurückgerollt. Das löst aber noch nicht die Frage, welche Datenzustände überhaupt gültig sein dürfen.
Genau darum geht es in diesem Block. Ein Datenbankschema ist nicht nur eine technische Ablageform. Es ist ein Teil des Softwaredesigns: Es entscheidet, welche Pflichtfelder existieren, welche Werte erlaubt sind, welche Beziehungen gültig bleiben und welche Regeln auch dann gelten, wenn Daten nicht über den normalen Service geschrieben werden.
Für Block 4 arbeitest du normalerweise auf dem Stand weiter, den du nach Block 3 erreicht hast. Falls du mitten im Modul einsteigst, nutzt du den Checkpoint block-4-start. Das Checkpoint-System und die Lösungskopien werden im separaten Abschnitt Checkpoints erklärt.
Warum Schema Design Anwendungscode entlastet¶
Stell dir vor, ein Ticket wird mit leerem Titel, unbekanntem Status oder doppelter externer Referenz gespeichert. Der Anwendungscode kann solche Fälle prüfen. Das reicht aber nicht immer:
Daten können über Tests, Skripte, Admin-Tools oder spätere Services geschrieben werden.
Validierung im Controller schützt nur den HTTP-Eingang.
Service-Logik schützt Abläufe, aber nicht automatisch jeden möglichen Schreibpfad.
Eine Datenbankregel bleibt auch dann aktiv, wenn sich der Anwendungscode verändert.
PostgreSQL kann viele einfache, dauerhafte Regeln direkt im Schema erzwingen, etwa Pflichtfelder, Eindeutigkeit, Wertelisten und Beziehungen PostgreSQL Global Development Group (2026). Der Anwendungscode wird dadurch nicht überflüssig. Er kann sich stärker auf Abläufe, Fehlermeldungen und fachliche Entscheidungen konzentrieren.
Die wichtigste Frage in einem Schema Review lautet deshalb:
Welche fehlerhaften Daten dürfen gar nie dauerhaft gespeichert werden?
Technische IDs und fachliche Schlüssel¶
Die Spalte id in tickets ist ein technischer Primärschlüssel. Sie ist stabil, kurz, gut für Foreign Keys geeignet und für JPA/Hibernate einfach zu verwenden. Für Menschen ist sie aber selten die wichtigste Referenz.
Eine fachliche Referenz hat eine andere Rolle. Sie kann aus einem Umsystem, einem Monitoring-Tool oder einer Supportnummer stammen. Im Block-4-Lösungsstand ergänzt die Migration eine optionale Spalte:
ALTER TABLE app_starter.tickets
ADD COLUMN external_reference TEXT;Diese Referenz ist fachlich bedeutsam, aber nicht zwingend für jedes Ticket vorhanden. Deshalb ist sie nullable. Wenn sie vorhanden ist, soll sie aber eindeutig sein:
ALTER TABLE app_starter.tickets
ADD CONSTRAINT tickets_external_reference_unique
UNIQUE (external_reference);Das ist ein typischer Unterschied:
| Frage | Technische ID | Fachliche Referenz |
|---|---|---|
| Wer nutzt sie primär? | Datenbank, ORM, Foreign Keys | Benutzerinnen, Umsysteme, Supportprozesse |
| Muss sie sprechend sein? | Nein | Oft ja |
| Darf sie sich ändern? | Möglichst nie | Je nach Fachprozess |
| Braucht sie Eindeutigkeit? | Ja, als Primärschlüssel | Häufig, aber fachlich begründet |
Ein guter Entwurf verwendet technische IDs für robuste Beziehungen und fachliche Schlüssel für fachliche Wiedererkennung. Beides ist nicht dasselbe.
Constraints als dauerhafte Schutzschicht¶
Constraints sind keine lästige Zusatzarbeit. Sie machen Regeln sichtbar und überprüfbar. Im Ticket-System gehören einige Regeln direkt in PostgreSQL:
ALTER TABLE app_starter.tickets
ADD CONSTRAINT tickets_priority_check
CHECK (priority IN ('low', 'normal', 'high', 'urgent'));Diese Regel ist einfach, lokal und unabhängig vom konkreten HTTP-Request. Genau deshalb passt sie gut in die Datenbank.
| Regel | Typischer Schutz |
|---|---|
| Ticket braucht einen Titel | NOT NULL plus Anwendungvalidierung |
| Status hat nur erlaubte Werte | CHECK, Enum oder Referenztabelle |
| externe Referenz ist eindeutig | UNIQUE |
| Kommentar gehört zu einem Ticket | Foreign Key |
| Statuswechsel erzeugt Event | Service-Transaktion |
| geschlossenes Ticket wird nicht wieder geöffnet | Service-Logik |
Das Schema und der Service arbeiten zusammen. PostgreSQL schützt den gültigen Datenzustand. Der Service schützt fachliche Abläufe, die mehrere Schritte, Bedingungen oder Fehlermeldungen brauchen.
Status und Werte modellieren¶
Für Status, Priorität oder Event-Typen gibt es mehrere Modellierungsvarianten. Keine ist immer richtig.
| Variante | Geeignet, wenn ... | Risiko |
|---|---|---|
CHECK Constraint | die Werteliste klein und stabil ist | Schemaänderung nötig bei neuen Werten |
| PostgreSQL-Enum | Werte sehr stabil und stark typisiert sind | spätere Änderungen können unbequemer werden |
| Referenztabelle | Werte Metadaten, Sortierung oder Übersetzungen brauchen | mehr Tabellen und Joins |
| Nur Java-Enum | Regel nur im Anwendungscode sichtbar sein soll | andere Schreibpfade können falsche Werte speichern |
Im Block-4-Lösungsstand ist priority bewusst als TEXT mit CHECK modelliert:
ALTER TABLE app_starter.tickets
ADD COLUMN priority TEXT;
UPDATE app_starter.tickets
SET priority = 'normal'
WHERE priority IS NULL;
ALTER TABLE app_starter.tickets
ALTER COLUMN priority SET NOT NULL;
ALTER TABLE app_starter.tickets
ADD CONSTRAINT tickets_priority_check
CHECK (priority IN ('low', 'normal', 'high', 'urgent'));Diese mehrstufige Migration ist absichtlich. Bestehende Zeilen erhalten zuerst einen gültigen Wert. Erst danach wird NOT NULL aktiviert. Das passt zum Muster aus Schema Evolution: Produktionsdaten werden nicht ignoriert, sondern mitgedacht.
Audit-Felder¶
Audit-Felder beantworten später einfache, aber wichtige Fragen:
Wann wurde ein Ticket erstellt?
Wann wurde es zuletzt geändert?
Welches System oder welche Person hat es verändert?
Warum ist ein Ticket nicht mehr in normalen Listen sichtbar?
Im Starter gab es bereits created_at. Block 4 ergänzt updated_at:
ALTER TABLE app_starter.tickets
ADD COLUMN updated_at TIMESTAMPTZ;
UPDATE app_starter.tickets
SET updated_at = created_at
WHERE updated_at IS NULL;
ALTER TABLE app_starter.tickets
ALTER COLUMN updated_at SET NOT NULL;In der Entity wird updatedAt bei Änderungen aktualisiert:
@PreUpdate
void markUpdated() {
updatedAt = OffsetDateTime.now();
}Das ist für den Kurs bewusst einfach gehalten. In grösseren Systemen können Audit-Informationen auch durch Datenbank-Trigger, zentrale JPA-Auditing-Funktionen oder separate Event-Tabellen gepflegt werden. Entscheidend ist die Designfrage: Welche Nachvollziehbarkeit braucht das System später wirklich?
Soft Deletes kritisch betrachten¶
Ein Soft Delete löscht eine Zeile nicht physisch. Stattdessen wird sie markiert, zum Beispiel mit deleted_at:
ALTER TABLE app_starter.tickets
ADD COLUMN deleted_at TIMESTAMPTZ;Der Block-4-Lösungsstand ergänzt einen einfachen API-Endpunkt:
DELETE /api/tickets/{id}Der Service setzt dabei deleted_at. Listenabfragen zeigen nur Tickets, bei denen deleted_at IS NULL gilt.
Das klingt praktisch, ist aber kein kostenloser Gewinn.
| Nutzen | Risiko |
|---|---|
| versehentliche Löschung kann eher nachvollzogen werden | jede Query muss gelöschte Zeilen korrekt ausfiltern |
| Daten bleiben für Support oder Audit sichtbar | UNIQUE Regeln werden schwieriger, wenn gelöschte Werte wiederverwendet werden sollen |
| Wiederherstellung ist möglich | Tabellen wachsen und werden fachlich schwerer zu lesen |
Soft Delete ist deshalb eine bewusste Entscheidung, nicht ein Standardreflex. Manchmal ist ein Statuswert besser. Manchmal braucht es Archivtabellen. Manchmal ist echte Löschung mit Audit-Event die klarere Lösung.
Schema, Entity, DTO und API-Modell¶
Ein häufiger Fehler in Backend-Projekten ist, Schema, Entity und API-Antwort als dasselbe Modell zu behandeln. Das wirkt am Anfang bequem, erzeugt aber schnell enge Kopplung.
Im Block-4-Lösungsstand sieht man vier Sichten:
| Sicht | Aufgabe |
|---|---|
| Datenbankschema | dauerhafte Struktur, Constraints, Indizes und Datenregeln |
| JPA-Entity | Mapping zwischen Java und Tabelle |
| Request-DTO | was ein Client beim Erstellen senden darf |
| Response-DTO | was die API nach aussen zurückgibt |
Die Entity kennt deletedAt, weil die Anwendung diese Spalte zum Ausblenden braucht. Die API-Antwort gibt deletedAt aber nicht aus. Das ist Absicht: Die API soll nicht jedes interne Implementierungsdetail nach aussen tragen.
Diese Trennung hilft bei späteren Änderungen. Das Schema darf technische Spalten enthalten. Die Entity darf ORM-Details kennen. Die API bleibt ein bewusster Vertrag.
Review-Fragen für Schema Design¶
Wenn du ein Schema in einem Softwareprojekt reviewst, helfen diese Fragen:
Welche Daten dürfen nie
NULLsein?Welche Wertebereiche sind klein genug für einen
CHECKConstraint?Welche fachlichen Referenzen müssen eindeutig sein?
Welche Beziehungen brauchen Foreign Keys?
Welche Regel gehört in den Service, weil sie einen Ablauf beschreibt?
Welche Spalte ist intern und gehört nicht automatisch in die API?
Welche Queries müssen wegen Soft Deletes besonders sorgfältig formuliert werden?
Welche Migration ist für bestehende Daten sicher?
Ausblick¶
Block 4 entscheidet, welche Datenzustände gültig sind. Block 5 fragt danach, wie diese Daten gezielt gelesen werden. Ein klares Schema macht Queries nicht automatisch perfekt, aber es macht sie verständlicher: Status, Priorität, fachliche Referenz und Soft-Delete-Markierung sind dann explizite Entscheidungen statt versteckte Annahmen im Code.
- PostgreSQL Global Development Group. (2026). PostgreSQL Documentation. https://www.postgresql.org/docs/