Vom CRUD zum Planungsflow
Vom CRUD zum Planungsflow
Abschnitt betitelt „Vom CRUD zum Planungsflow“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 anlegenSpieler auswählenGruppen bildenSpielplan erzeugenPlanung abschließenDamit 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.
Der fachliche Schnitt
Abschnitt betitelt „Der fachliche Schnitt“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/modellibs/tournament/tournament-setup/domainlibs/tournament/tournament-setup/presentationAuch API Gateway und Service wurden fachlich geschnitten:
apps/apis/tournament-api/src/app/tournament-setupapps/services/tournament-service/src/app/tournament-setupDas 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 Ablauf der Turnierplanung
Abschnitt betitelt „Der Ablauf der Turnierplanung“Der Use Case besteht aktuell aus drei Schritten:
1. Spieler auswählen2. Gruppen bilden3. Spielplan erzeugenDer 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:
ÜbersichtEin konkretes Turnier bedeutet:
/tournament-setup/:tournamentId/players/tournament-setup/:tournamentId/groups/tournament-setup/:tournamentId/scheduleErst durch diese Trennung wurde klar, wann die App eine Liste vorhandener Turniere zeigen soll und wann sie in einem aktiven Planungsprozess steckt.
Spieler auswählen
Abschnitt betitelt „Spieler auswählen“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-playersDie UI zeigt die Spieler in einem Grid. Ausgewählte Spieler werden deutlich markiert.
Die Validierung ist bewusst einfach:
mindestens 4 Spielergerade Anzahl SpielerNoch wird hier nicht entschieden, wer tatsächlich spielt und wer Reserve bleibt. Das passiert erst bei der Gruppenbildung.
Gruppen bilden
Abschnitt betitelt „Gruppen bilden“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 Spieler6 Spieler8 Spieler10 SpielerDas 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 GruppeUngültig: 2, 5, 12 oder andere GrößenSpielplan erzeugen
Abschnitt betitelt „Spielplan erzeugen“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.
Warum Runde 2 noch nicht geplant wird
Abschnitt betitelt „Warum Runde 2 noch nicht geplant wird“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 ATop 4 aus Gruppe Boder 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_READYDer Turniertag beginnt später mit:
ACTIVEDort 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 Spieler6 Spieler8 Spieler10 SpielerDie geplante Spielanzahl pro Spieler:
4 Spieler → 1 Spiel pro Spieler6 Spieler → 2 Spiele pro Spieler8 Spieler → 3 Spiele pro Spieler10 Spieler → 4 Spiele pro SpielerDa immer Doppel gespielt wird, besteht ein Match aus vier Spielern:
2 Spieler gegen 2 SpielerDaraus ergeben sich die Matchzahlen:
4 Spieler:1 Runde1 Matchkeine Pause
6 Spieler:3 Runden1 Match pro Runde2 Spieler Pause pro Runde
8 Spieler:3 Runden2 Matches pro Rundekeine Pause
10 Spieler:5 Runden2 Matches pro Runde2 Spieler Pause pro RundeFür eine 10er-Gruppe gilt also:
10 Spieler × 4 Spiele pro Spieler = 40 Spielereinsätze1 Doppel-Match = 4 Spielereinsätze40 / 4 = 10 MatchesDaraus entstehen:
5 Runden × 2 Matches pro Runde = 10 MatchesQualitä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.
Designfindung: Screenshot reicht nicht
Abschnitt betitelt „Designfindung: Screenshot reicht nicht“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 Cardsunruhige Meta-Flächenfalsche Hover-Effektespringende Borderweiße Default-Flächen in dunkler UIunpräzise Abständeinkonsistente AnordnungDie Erkenntnis daraus war deutlich:
Ein Screenshot ist für den Agenten ein Moodboard, aber kein Layout-Vertrag.
Besser funktioniert hat ein klassisches Grid-System.
Warum das 12-Grid geholfen hat
Abschnitt betitelt „Warum das 12-Grid geholfen hat“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-12Oder 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-12Das 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.
Kleine Layout-Verträge statt große Wünsche
Abschnitt betitelt „Kleine Layout-Verträge statt große Wünsche“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 schreibenDas 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:
ladensetzenpatchenmanuell synchronisierenModerne 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.
Kurzer Blick auf Resources
Abschnitt betitelt „Kurzer Blick auf Resources“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 TurnierOder:
Route Param ändert sich→ Schedule Resource lädt den aktuellen SpielplanSpannend 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→ ComponentAber genau diese Art Datenfluss ist noch nicht überall in den Agenten-Antworten angekommen.
Warum das zu Bugs geführt hat
Abschnitt betitelt „Warum das zu Bugs geführt hat“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_READYSobald 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_READYWenn 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.
Was der Agent gut gemacht hat
Abschnitt betitelt „Was der Agent gut gemacht hat“Trotz aller Korrekturen war der Fortschritt beeindruckend.
Die App hat heute einen vollständigen Planungsfluss:
Turnier anlegenSpieler auswählenGruppen bildenReserve berücksichtigenSpielplan für Runde 1 erzeugenTurnierstatus auf SCHEDULE_READY bringenAuch die Tests wurden stärker. Besonders wichtig waren Tests für:
GruppengrößenSpielanzahl pro SpielerMatch-AnzahlenSchedule-InvalidierungTenant-/Club-KontextBackend-ValidierungDas 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.
Was der Agent schwer fand
Abschnitt betitelt „Was der Agent schwer fand“Schwer waren vor allem Dinge, die nicht lokal in einer Datei lösbar sind.
Zum Beispiel:
Route-Konzeptaktiver Turnier-KontextCreate vs UpdateSchedule als AbleitungInvalidierungState über Tabs hinwegTheming über CDK/PrimeNG hinwegDas 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.