Skip to content

Vom CRUD zum Planungsflow

This content is not available in your language yet.

Der erste fachliche Durchstich der Turnier-App war bewusst klein: Spieler verwalten.

Das war der richtige Einstieg. Ein CRUD-Feature eignet sich gut, um einen vertikalen Schnitt zu beweisen: Frontend, API Gateway, Service, Datenbank, Auth-Kontext und Vereins-/Tenant-Kontext.

Mit der Turnierplanung kam jetzt der nächste Schritt.

Diesmal ging es nicht mehr um ein einzelnes Formular und eine Liste. Es ging um einen mehrstufigen fachlichen Ablauf:

Turnier anlegen
Spieler auswählen
Gruppen bilden
Spielplan erzeugen
Planung abschließen

Damit wurde aus dem Projekt zum ersten Mal mehr als ein CRUD-Beispiel.

Der Players-Slice hatte gezeigt, dass die Architektur grundsätzlich trägt.

Der Tournament-Setup-Flow hat gezeigt, wo es schwieriger wird: bei abgeleitetem Zustand, Wizard-State, Routing-Kontext, Persistenzgrenzen und Reaktivität über mehrere Schritte hinweg.

Die Turnierplanung wurde bewusst nicht in die Spieler-Verwaltung eingebaut.

Spieler sind eine eigene Fachlichkeit. Tournament Setup ist eine andere.

Deshalb wurde ein neuer vertikaler Bereich geschaffen:

libs/tournament/tournament-setup/model
libs/tournament/tournament-setup/domain
libs/tournament/tournament-setup/presentation

Auch API Gateway und Service wurden fachlich geschnitten:

apps/apis/tournament-api/src/app/tournament-setup
apps/services/tournament-service/src/app/tournament-setup

Das Ziel war klar:

Das Setup darf Spieler verwenden, aber nicht vom Players-Frontend abhängig werden.

Die Turnierplanung lädt ihre eigene Sicht auf aktive Spieler. Sie greift nicht auf einen PlayersStore zu. Das ist bewusst etwas mehr Aufwand, schützt aber die fachliche Grenze.

Ein Spieler in der Spieler-Verwaltung ist nicht automatisch dasselbe ViewModel wie ein auswählbarer Turnierteilnehmer.

Der Use Case besteht aktuell aus drei Schritten:

1. Spieler auswählen
2. Gruppen bilden
3. Spielplan erzeugen

Der Einstieg liegt auf der Turnier-Setup-Übersicht.

Dort kann ein neues Turnier begonnen oder ein vorhandenes offenes Turnier weiterbearbeitet werden. Sobald ein konkretes Turnier geöffnet ist, befindet man sich im Turnier-Kontext.

Diese Trennung war wichtiger, als sie zuerst wirkte.

/tournament-setup bedeutet:

Übersicht

Ein konkretes Turnier bedeutet:

/tournament-setup/:tournamentId/players
/tournament-setup/:tournamentId/groups
/tournament-setup/:tournamentId/schedule

Erst durch diese Trennung wurde klar, wann die App eine Liste vorhandener Turniere zeigen soll und wann sie in einem aktiven Planungsprozess steckt.

Im ersten Schritt werden aktive Spieler angezeigt und ausgewählt.

Die Spieler-Auswahl ist bewusst kein Zugriff auf die bestehende Spieler-Verwaltung. Das Setup bekommt seine eigene API-Sicht:

GET /tournament-setup/available-players

Die UI zeigt die Spieler in einem Grid. Ausgewählte Spieler werden deutlich markiert.

Die Validierung ist bewusst einfach:

mindestens 4 Spieler
gerade Anzahl Spieler

Noch wird hier nicht entschieden, wer tatsächlich spielt und wer Reserve bleibt. Das passiert erst bei der Gruppenbildung.

Im zweiten Schritt werden die ausgewählten Spieler Gruppen zugeordnet.

Die Gruppenbildung erlaubt einen Spielerpool. Nicht alle ausgewählten Spieler müssen einer Gruppe zugeordnet werden. Spieler, die im Pool bleiben, werden später als Reserve persistiert.

Für den MVP wurden die planbaren Gruppengrößen bewusst begrenzt:

4 Spieler
6 Spieler
8 Spieler
10 Spieler

Das ist eine wichtige Vereinfachung.

Statt direkt ein beliebiges Regelwerk für alle denkbaren Gruppengrößen zu bauen, wird nur das unterstützt, was für das aktuelle Turnierformat gebraucht wird.

Spieler im Pool bleiben erlaubt. Gruppen selbst müssen aber eine unterstützte Größe haben.

Gültig: 4, 6, 8 oder 10 Spieler pro Gruppe
Ungültig: 2, 5, 12 oder andere Größen

Der dritte Schritt erzeugt den Spielplan der ersten Gruppenphase.

Sobald der Spielplan-Tab geöffnet wird, prüft das Backend:

Existiert bereits ein Spielplan?

Wenn ja, wird er geladen.

Wenn nicht, und das Turnier den Status GROUPS_READY hat, wird der Plan erzeugt und persistiert. Danach wird der Status auf SCHEDULE_READY gesetzt.

Damit ist die Planung abgeschlossen.

Wichtig ist: Es gibt im MVP keine Neu-Auslosung und keine manuelle Bearbeitung des Spielplans.

Das ist Absicht.

Ein Spielplan soll nicht beliebig oft neu gewürfelt werden, bis er jemandem besser gefällt. Später kann man Editierbarkeit oder neue Auslosungen als eigenen Use Case betrachten. Für den MVP bleibt der Plan stabil.

Runde 2 klingt auf den ersten Blick ähnlich.

Wieder Gruppen. Wieder Doppel. Wieder Paarungen.

Fachlich ist sie aber anders.

In Runde 1 sind die Spieler bekannt. Sie kommen aus der Gruppenbildung.

In Runde 2 ergeben sich die Spieler erst aus Ergebnissen.

Top 4 aus Gruppe A
Top 4 aus Gruppe B

oder ein vergleichbares Qualifikationsmodell.

Das heißt: Runde 2 kann nicht sauber geplant werden, bevor Ergebnisse und Tabellenstände existieren.

Deshalb endet die Turnierplanung aktuell bei:

SCHEDULE_READY

Der Turniertag beginnt später mit:

ACTIVE

Dort gehören dann Ergebniserfassung, Ranglisten, Qualifikation und Runde 2 hin.

Regelwerk der ersten Gruppenphase

Für den MVP unterstützt der Spielplan-Generator vier Gruppengrößen:

4 Spieler
6 Spieler
8 Spieler
10 Spieler

Die geplante Spielanzahl pro Spieler:

4 Spieler → 1 Spiel pro Spieler
6 Spieler → 2 Spiele pro Spieler
8 Spieler → 3 Spiele pro Spieler
10 Spieler → 4 Spiele pro Spieler

Da immer Doppel gespielt wird, besteht ein Match aus vier Spielern:

2 Spieler gegen 2 Spieler

Daraus ergeben sich die Matchzahlen:

4 Spieler:
1 Runde
1 Match
keine Pause
6 Spieler:
3 Runden
1 Match pro Runde
2 Spieler Pause pro Runde
8 Spieler:
3 Runden
2 Matches pro Runde
keine Pause
10 Spieler:
5 Runden
2 Matches pro Runde
2 Spieler Pause pro Runde

Für eine 10er-Gruppe gilt also:

10 Spieler × 4 Spiele pro Spieler = 40 Spielereinsätze
1 Doppel-Match = 4 Spielereinsätze
40 / 4 = 10 Matches

Daraus entstehen:

5 Runden × 2 Matches pro Runde = 10 Matches

Qualitätsziele:

Spielanzahl pro Spieler passt.
Pausen sind fair verteilt.
Partner-Paarungen wiederholen sich möglichst nicht.
Gegner-Paarungen wiederholen sich möglichst nicht.
Reserve-Spieler werden nicht eingeplant.

Im MVP arbeitet der Generator mit festen, testbaren Templates für die unterstützten Gruppengrößen. Später kann daraus ein konfigurierbares Regelwerk oder ein echter Generator entstehen.

Der visuelle Teil war überraschend aufwendig.

Am Anfang stand ein Designbild als Inspiration: dunkle Oberfläche, Vereinsfarben, schwarze und grüne Trikots, sportliches Admin-UI.

Für Menschen war die Richtung sofort klar.

Für den Coding-Agenten nicht.

Er konnte aus dem Bild zwar grob erkennen, dass es dunkel, grün und card-basiert sein sollte. Aber die eigentliche Struktur hat er mehrfach verfehlt.

Typische Probleme:

Cards in Cards
unruhige Meta-Flächen
falsche Hover-Effekte
springende Border
weiße Default-Flächen in dunkler UI
unpräzise Abstände
inkonsistente Anordnung

Die Erkenntnis daraus war deutlich:

Ein Screenshot ist für den Agenten ein Moodboard, aber kein Layout-Vertrag.

Besser funktioniert hat ein klassisches Grid-System.

Statt “mach es wie im Screenshot” funktionierten Prompts besser, wenn sie das Layout explizit beschrieben haben.

Zum Beispiel:

12-column grid
Desktop:
- Gruppenbereich: col-span-9
- Seitenpanel: col-span-3
Tablet/Mobile:
- Gruppenbereich: col-span-12
- Seitenpanel: col-span-12

Oder bei Spieler-Karten:

Desktop:
- 4 Karten pro Reihe
- jede Karte col-span-3
Tablet:
- 2 Karten pro Reihe
- jede Karte col-span-6
Mobile:
- 1 Karte pro Reihe
- jede Karte col-span-12

Das war viel greifbarer als ein rein visuelles Zielbild.

Flexbox ist stark, aber in Prompts oft zu offen. Ein Agent interpretiert dann viel selbst. Ein Grid mit Spalten, Bereichen und Breakpoints ist dagegen prüfbarer.

Die neue Regel für Layout-Prompts lautet daher:

Bilder liefern Stimmung. Das 12-Grid liefert den Vertrag.

Ein guter Layout-Prompt beschreibt nicht nur, wie etwas wirken soll, sondern wo es liegt.

Schlecht:

Mach die Liste professioneller.

Besser:

Player Row nutzt 12-column grid.
Avatar links.
Name und Ranking im Hauptbereich.
Status rechts.
Actions ganz rechts.
Keine nested cards.
Keine Hover-Effekte auf nicht klickbaren Rows.

Diese Art von Vorgabe ist weniger poetisch, aber deutlich wirksamer.

Gerade bei KI-Agenten ist das wichtig. Sie brauchen nicht nur eine Richtung, sondern harte Grenzen.

Drift: Wenn bekannte Muster gegen moderne Architektur arbeiten

Abschnitt betitelt „Drift: Wenn bekannte Muster gegen moderne Architektur arbeiten“

Der nächste große Lernpunkt war Architekturdrift.

Die App nutzt moderne Angular-Ansätze mit Signals, Stores, abgeleiteten Zuständen und zunehmend auch Resource-ähnlichen Datenflüssen.

Der Agent fiel aber mehrfach in ältere Muster zurück.

Besonders sichtbar war das beim Laden von Daten.

Statt einen reaktiven Datenfluss zu nutzen, wollte er mehrfach einen klassischen initialen Load einbauen:

Component startet
→ ngOnInit
→ load()
→ Daten in lokalen State schreiben

Das ist ein Muster, das man in sehr vielen Angular-Codebasen findet. Es ist nicht grundsätzlich falsch. Aber es passt nicht immer zu moderner signalbasierter Architektur.

Meine Hypothese: Ein großer Teil des Trainingsmaterials und der Community-Beispiele basiert weiterhin auf diesen älteren Set-Patterns.

Also:

laden
setzen
patchen
manuell synchronisieren

Moderne Angular APIs wie resource oder httpResource verschieben den Fokus. Daten werden stärker als reaktive Abhängigkeit modelliert. Ein httpResource erstellt eine Resource für HTTP-GET-Daten und aktualisiert sich, wenn sich die signalbasierte Request-Beschreibung ändert. Es nutzt dabei weiterhin den Angular HttpClient und dessen Infrastruktur, etwa Interceptors.

Das bedeutet: Nicht jeder Datenfluss braucht einen expliziten initialen Load im alten Stil.

Eine Resource kann man als asynchrone Abhängigkeit verstehen, deren Wert über Signals bereitgestellt wird.

httpResource ist die HTTP-nahe Variante davon. Sie ist für GET-basierte Datenflüsse gedacht und aktualisiert sich, wenn sich ihre Eingabe über Signals verändert.

Das passt gut zu Flows wie:

activeTournamentId ändert sich
→ Resource lädt das passende Turnier

Oder:

Route Param ändert sich
→ Schedule Resource lädt den aktuellen Spielplan

Spannend ist auch die parse-Option. Darüber kann man rohe HTTP-Daten transformieren oder mit einer Runtime-Schema-Bibliothek wie Zod validieren, bevor sie an die Resource ausgeliefert werden.

Das passt gut zu unserer Architekturidee:

DTO
→ parse / zod / ACL
→ ViewModel
→ Component

Aber genau diese Art Datenfluss ist noch nicht überall in den Agenten-Antworten angekommen.

Mehrfach entstanden Bugs durch unklare Ownership von State.

Zum Beispiel:

Turnier wurde geladen, aber nicht angezeigt.
Spieler-Auswahl wurde leer.
Gruppenänderung erzeugte ein neues Turnier.
Spielplan wurde nicht neu abgeleitet.
Alter Schedule blieb gültig, obwohl Gruppen geändert wurden.

Das waren keine reinen Tippfehler.

Es waren Modellierungsfehler.

Die Kernfrage war immer:

Was ist Source of Truth?
Was ist abgeleitet?
Wann wird etwas persistiert?
Wann wird etwas invalidiert?

Gerade beim Spielplan wurde das wichtig.

Der Spielplan ist keine unabhängige Fachlichkeit. Er ist eine Ableitung aus dem aktuellen Gruppen-Setup.

Wenn Gruppen geändert werden, ist der alte Spielplan ungültig.

Das muss sowohl im Backend als auch im Frontend klar abgebildet werden:

Gruppen ändern
→ alten Schedule invalidieren
→ Status zurück auf GROUPS_READY
→ Schedule-Tab öffnen
→ neuen Schedule erzeugen
→ Status SCHEDULE_READY

Sobald der Agent diesen Zusammenhang nicht sauber modelliert, entstehen neue Turniere, alte Pläne oder falsche Paarungen.

Der wichtigste Architekturpunkt: Ableitungen ernst nehmen

Abschnitt betitelt „Der wichtigste Architekturpunkt: Ableitungen ernst nehmen“

Der gesamte Turnier-Setup-Flow besteht aus Ableitungen.

aktive Spieler
→ auswählbare Spieler
ausgewählte Spieler
→ Gruppen-Setup
Gruppen-Setup
→ Spielplan
Spielplan
→ SCHEDULE_READY

Wenn ein früherer Zustand geändert wird, müssen spätere Ableitungen ungültig werden.

Das klingt trivial, war aber der schwierigste Teil.

Denn ein klassisches “set pattern” speichert gern jeden Zwischenstand als fertigen State und vergisst dann, ihn bei Änderungen zu invalidieren.

Der bessere Gedanke ist:

Persistierter Zustand ist Quelle. ViewModels und Pläne sind abgeleitet. Wenn die Quelle geändert wird, müssen abgeleitete Daten neu entstehen oder bewusst invalidiert werden.

Trotz aller Korrekturen war der Fortschritt beeindruckend.

Die App hat heute einen vollständigen Planungsfluss:

Turnier anlegen
Spieler auswählen
Gruppen bilden
Reserve berücksichtigen
Spielplan für Runde 1 erzeugen
Turnierstatus auf SCHEDULE_READY bringen

Auch die Tests wurden stärker. Besonders wichtig waren Tests für:

Gruppengrößen
Spielanzahl pro Spieler
Match-Anzahlen
Schedule-Invalidierung
Tenant-/Club-Kontext
Backend-Validierung

Das ist genau der Punkt, an dem KI-gestützte Entwicklung funktioniert: Der Agent erzeugt schnell Struktur, Code und Tests. Der Mensch muss aber die fachlichen Invarianten erkennen und verteidigen.

Schwer waren vor allem Dinge, die nicht lokal in einer Datei lösbar sind.

Zum Beispiel:

Route-Konzept
aktiver Turnier-Kontext
Create vs Update
Schedule als Ableitung
Invalidierung
State über Tabs hinweg
Theming über CDK/PrimeNG hinweg

Das sind Querschnittsthemen.

Ein Agent kann sie lösen, aber er braucht sehr klare Prompts. Sobald der Prompt zu offen ist, sucht er sich bekannte Muster. Und diese Muster passen nicht immer zur Architektur.

Der Schritt von CRUD zu einem echten Planungsflow war der bisher spannendste Teil der Reise.

Die Turnierplanung ist kein großes Produkt. Aber sie enthält bereits viele echte Architekturfragen:

Wie schneidet man Features?
Wie verhindert man Kopplung?
Wie modelliert man Wizard-State?
Wann persistiert man?
Wann invalidiert man Ableitungen?
Wie hält man UI-Layout agententauglich?
Wie bringt man moderne Angular-Reaktivität in eine Codebasis, obwohl viele Beispiele noch alte Muster zeigen?

Die wichtigste Erkenntnis beim Design:

Für KI-Agenten ist ein klassisches 12-Grid oft besser als ein frei beschriebenes Flex-Layout.

Die wichtigste Erkenntnis bei der Architektur:

Agenten können schnell Code erzeugen, aber sie brauchen klare Source-of-Truth-Regeln.

Und die wichtigste Erkenntnis bei der Fachlichkeit:

Runde 1 planen ist ein Setup-Problem. Runde 2 ist ein Ergebnisproblem.

Deshalb ist die Turnierplanung jetzt bewusst bei SCHEDULE_READY abgeschlossen.

Der nächste große Use Case ist nicht mehr Planung.

Der nächste große Use Case ist der Turniertag.