Fortgeschrittene Praktiken — was Prilog beim Wachstum brauchen wird
Themen aus dem Senior-Engineering-Bereich, die jetzt nicht akut sind, aber bei bestimmten Trigger-Ereignissen (zweiter Kunde, 10 Tenants, erster Incident, …) gestern schon hätten existieren sollen. Zweck dieses Dokuments: dass uns die Aha-Momente nicht überraschen.
1. Domain-Driven Design (Bounded Contexts) konsequent
Was es ist: Ein größeres System wird in mehrere fachliche Domänen zerlegt, jede mit eigener Sprache, eigenen Modellen, klaren Grenzen. Inter-Domain-Kommunikation läuft über definierte Schnittstellen, nicht direkten DB-Zugriff.
Wo wir stehen: Wir haben Module (Stundenplan, Zeugnisse, DMS, …), aber die teilen sich eine Prisma-DB. Direkter Cross-Module-Zugriff ist möglich und passiert (z. B. Zeugnis-Modul liest TimetableEntry direkt). Das ist pragmatisch, aber wenn ein Modul wackelt, kann es andere mitreißen.
Trigger: Sobald ein Modul (z. B. Krisenmanagement, Marketplace) als separater Service ausgegliedert werden soll, oder wenn ein zweites Team Backend-Code schreibt.
Was vorbereiten:
- Pro Modul ein klarer „Service-Layer" — alle DB-Calls dort, nie aus dem Router
- Kein Cross-Module direkter
prisma.x.findMany— stattdessenotherModuleService.getX(tenantId, id)aufrufen - Spätere Trennung: jeder Modul-Service kommuniziert nur über Events oder HTTP-Calls mit anderen Modulen
2. Event Sourcing für kritische Domains
Was es ist: Statt nur den aktuellen Zustand zu speichern (z. B. Report.status = 'final_freigegeben'), speichert man jede Zustandsänderung als unveränderliches Event (ReportApprovedEvent). Der aktuelle Zustand ist die Summe aller Events.
Wo wir stehen: Wir haben ReportAuditLog für Audit-Zwecke, aber der ist eine Beobachtung des Hauptmodells, nicht die Wahrheit selbst. Bei Daten-Korruption oder DSGVO-Anfragen können wir nicht „Zeitreise" machen.
Trigger: Bei jeder Domäne wo „Wer hat wann was geändert" rechtlich oder pädagogisch zählt (Zeugnisse ja, Stundenplan-Änderungen ja, Mitteilungsheft gemischt).
Was vorbereiten:
- Auch jetzt schon: jeden schreibenden Endpoint mit Audit-Eintrag
- Audit-Eintrag enthält
beforeJson+afterJson(Diff) - Bei kritischen Domains: zusätzlich ein Outbox-Event publishen, das externe Consumer (Reporting, Analytics) konsumieren können
3. CQRS (Command Query Responsibility Segregation)
Was es ist: Schreiben (Commands) und Lesen (Queries) sind unter- schiedlich optimiert. Schreiben geht ins Domain-Modell, Lesen läuft über denormalisierte Views (Materialized Views, Caches).
Wo wir stehen: Wir machen findMany mit include/select direkt für jede Seite. Beim Cockpit z. B. läuft die Aggregat-Berechnung bei jedem Aufruf neu.
Trigger: Sobald eine Page > 1 sec lädt (siehe SLO-Tracking).
Was vorbereiten:
- Hot-Path-Queries identifizieren (Cockpit, Stundenplan-Wochen-Ansicht, Audit-Tab)
- Pre-aggregierte Tabellen (
report_period_summary) per Worker oder Trigger pflegen - Lese-Endpoint nutzt nur diese Summary, nicht den Live-Domain-State
- Eventuell PostgreSQL
MATERIALIZED VIEWmit periodischemREFRESH
4. Multi-Tenancy-Isolation-Testing
Was es ist: Automatische Tests, die beweisen, dass Tenant A nie Daten von Tenant B sehen kann.
Wo wir stehen: Wir filtern in jedem Query auf tenantId. Aber wir haben keinen automatischen Beweis, dass das wirklich überall passiert.
Trigger: Vor dem zweiten kommerziellen Kunden auf gleichem Backend-Server MUSS das laufen.
Was vorbereiten:
- Test-Suite: pro Endpoint zwei Tenants anlegen, mit Tenant-A-JWT versuchen Tenant-B-Resource zu lesen — muss 404/403 sein
- Statischer Check (Custom ESLint-Rule?):
prisma.X.findManyohnewhere: { tenantId }ist Fehler
5. Schema Migrations als Reversible Transactions
Was es ist: Jede DB-Migration ist eine Transaktion mit klarem Rollback-Pfad. Bei Fehler in Schritt 5 von 10 wird automatisch alles zurückgedreht.
Wo wir stehen: Prisma-Migrate ist hier eingeschränkt — manche Operationen (z. B. CREATE INDEX CONCURRENTLY) gehen nicht in Transaktionen.
Trigger: Beim ersten kaputten Production-Deploy.
Was vorbereiten:
- Bei Migrations mit > 1 ALTER: explizit
BEGIN; ... COMMIT;(sofern die Operationen es zulassen) - Für nicht-transaktionale Operationen (Index-Build): Migration in kleinste-mögliche Einheiten splitten + jedem eine Idempotency (
CREATE INDEX IF NOT EXISTS) - Rollback-Skript pro Migration (siehe
03-industrie-standards.md#2)
6. Canary Deployments
Was es ist: Neuer Code geht zuerst auf 1 Tenant (z. B. test-2), dann nach 24h ohne Vorfall auf alle.
Wo wir stehen: Heute alles-oder-nichts via pm2 restart backend-api.
Trigger: Sobald > 5 Tenants live sind.
Was vorbereiten:
- Mehrere Backend-Instanzen mit Routing nach Tenant-Subdomain
- Reconcile-Sweep nutzt schon stable-Channel — analoges Pattern für Backend:
backend-api-canarymit Tenant-Whitelist - Auto-Rollback bei Spike der Error-Rate (Voraussetzung: Observability)
7. Definition of Ready / Definition of Done
Definition of Ready = Bedingungen, die erfüllt sein müssen, bevor ein Ticket in einen Sprint aufgenommen werden darf. Definition of Done = Bedingungen, die erfüllt sein müssen, bevor ein Ticket als „done" markiert wird.
Wo wir stehen: Implizit, nicht aufgeschrieben.
Was vorbereiten:
DoR (in der Spec):
- [ ] P0-Ziel klar in 1 Satz
- [ ] Mockup oder Klick-Pfad-Skizze existiert
- [ ] Akzeptanz-Kriterien als prüfbare Liste
- [ ] Externe Abhängigkeiten bekannt (z. B. „braucht Stundenplan veröffentlicht")
DoD (vor Sprint-Abschluss):
- siehe
01-pflicht-checkliste.md— das ist effektiv unsere DoD
8. On-Call-Rotation + Eskalationspfad
Was es ist: Eine Person ist zu definierten Zeiten verantwortlich, auf Incident-Pages zu reagieren. Klare Eskalationspfade wenn die Person nicht erreichbar ist.
Wo wir stehen: Single-Person-Setup. Wenn der Owner schläft, schläft das System auch.
Trigger: Erster externer Kunde, der einen 24/7-Erwartungshorizont hat.
Was vorbereiten:
- Mindestens 2 Personen mit Production-Zugang
- PagerDuty / Better Uptime / Grafana Alerts mit Telefon-Eskalation
- Memory
reference_on_call.mdmit Kontakt-Reihenfolge
9. Capacity Planning
Was es ist: Vorhersage: wann reicht der aktuelle Server nicht mehr, welche Komponente wird zuerst Engpass.
Wo wir stehen: Hetzner-API gibt uns Live-Metrik, aber keine Trend-Analyse.
Trigger: Sobald CPU oder Disk-Usage über 70 % läuft.
Was vorbereiten:
- Wöchentlicher Bericht: Disk-Usage, CPU, Tenant-Anzahl, aktive Users
- Lineare Extrapolation → in N Wochen voll
- Bei 80 %: Hardware-Plan (Server upgraden, neuen aufsetzen, Tenants verschieben)
10. Data Retention / Privacy by Design — End-to-End
Was es ist: Persönliche Daten haben von Anfang an definierte Aufbewahrungsfristen + Lösch-Pfade. Nicht erst beim ersten DSGVO-Auskunfts- Antrag improvisieren.
Wo wir stehen: Wir haben Retention für DMS und Zeugnisse, aber nicht für Audit-Log, Chat-Nachrichten, Activity-Stream.
Was vorbereiten:
- Pro Tabelle deklarierte Retention (in Schema-Kommentar oder ADR)
- Retention-Worker pro Domain (wir haben einen für Zeugnisse — Pattern übernehmen)
- DSGVO-Selbstauskunft (Art. 15) pro User per Endpoint, der alle personenbezogenen Daten zusammensucht
- Recht-auf-Löschung (Art. 17) per Endpoint: löscht Daten, Audit-Log bekommt Pseudonym-Hash statt Klartext
11. Pricing/Packaging Validation vor Bau
Was es ist: Bevor wir 4 Wochen ein Feature bauen, ist klar, ob es im Free-Tier oder Pro-Tier liegt, was es kostet, ob Kunden es zahlen.
Wo wir stehen: Wir bauen, Stripe ist da, aber Mapping „welches Feature ist in welchem Tier" ist mündlich.
Trigger: Vor dem zweiten zahlenden Kunden.
Was vorbereiten:
- Pricing-Matrix als Tabelle in
prilog_docs/pricing/(öffentlich, damit Schulen es sehen) - Pro Feature ein Eintrag: Tier, Limit (z. B. „bis 50 Schüler"), Preis
- Im Code: Feature-Gate prüft Tier (
tenant.tier === 'pro')
12. Customer Discovery vor Feature-Bau
Was es ist: Bevor wir Feature X bauen, sprechen wir mit mindestens 2-3 Schulen die das angeblich brauchen.
Wo wir stehen: Wir bauen aus eigener Beobachtung. Risiko: wir bauen für eine Schule, andere brauchen es nicht oder anders.
Trigger: Vor jedem nicht-trivialen Feature.
Was vorbereiten:
- Kurzes Telefonat / Slack-Nachricht an 2-3 Schul-Owner: „Würdet ihr X benutzen? Was würde es lösen? Was wäre euer Alternativ-Workaround?"
- Notiz in der Spec: „Validiert mit Schulen X, Y" + Quote
- Negativbeispiele: wenn 0 Schulen es brauchen → Spec parken
13. „Tech-Debt-Register"
Was es ist: Eine Liste von bekannten Hacks, Workarounds, Wenn-A-dann-B- Konstrukten die uns später beißen werden.
Wo wir stehen: Implizit in den feedback_*-Memories + project_open_items.md. Aber kein zentrales Backlog mit Priorisierung.
Was vorbereiten:
prilog_docs/tech-debt/REGISTER.mdmit Tabelle:- ID / Symptom / Hack / Sauberer Fix / Aufwand / Wann-bricht-es / Owner
- Pro Sprint: 1-2 Items abarbeiten
14. Internationalisierung (i18n) konsistent
Was es ist: Alle User-facing Strings haben einen i18n-Key. Es gibt keine hardcoded deutsche Strings im Code.
Wo wir stehen: Halb-konsistent. Manche neuen Sektionen (z. B. Klassenlehrer-Befähigung in TeachingSection) haben hardcoded deutsche Strings.
Was vorbereiten:
- ESLint-Rule (Custom oder Plugin): hardcoded
<string>in JSX-Text → Warnung - Translation-Coverage-Check als CI-Schritt
- Locale-Fallback-Strategie: fehlende Übersetzung → Deutsch (nicht englisches Default)
15. Compliance as Code
Was es ist: DSGVO-Pflichten sind im Code abgebildet und automatisch geprüft, nicht nur in Word-Dokumenten.
Wo wir stehen: Wir haben ComplianceGate + DSFA-Pflicht-Upload (Sprint 5). Aber: kein automatischer Check, ob alle Datenbank-Spalten mit personenbezogenen Daten auch wirklich pseudonymisiert/auditiert/ Retention haben.
Was vorbereiten:
- Annotation-System: Prisma-Schema-Felder mit
@privacy(...)-Kommentar (@privacy(personal=true, retention=10y, audit=write)) - CI-Check parst das + warnt wenn Endpoint die Felder zurückliefert ohne Sicht-Resolver
- Memory
project_compliance_as_code.md
16. Chaos-Engineering / Fault Injection
Was es ist: Bewusste Fehler einbauen (Netzwerk-Hang, DB langsam, S3 weg) um zu prüfen, ob das System graceful degradiert.
Wo wir stehen: keine. Wir vertrauen darauf, dass alles immer funktioniert.
Trigger: Ab dem dritten Production-Incident in einem Quartal.
Was vorbereiten:
- Test-Tenant mit Chaos-Mesh oder einfachem fault-injection Middleware (z. B. „bei 5 % der DB-Queries: 2 sec Delay")
- Manueller „Game Day" pro Quartal: 1h Stress-Test im Test-Tenant, Team beobachtet was bricht
17. Onboarding-Time-to-First-Success messen
Was es ist: Wie lange dauert es vom Aufruf der Schul-Subdomain bis zur ersten erfolgreichen Aktion (Zeugnis schreiben, Stundenplan eingeben).
Wo wir stehen: Nicht gemessen. Vermutung: für eine Schule ohne Vorkenntnisse 2-4 Wochen mit unserem Support.
Trigger: Sofort.
Was vorbereiten:
- Activity-Log-Events („tenant.provisioned", „first_class_created", „first_zeugnis_written", …)
- Dashboard: Funnel-Conversion pro Schritt
- A/B-Test: ein Onboarding-Wizard → Time-to-First-Success messbar besser?
18. Mehrere Umgebungen (dev / staging / prod)
Was es ist: Code wird zuerst in Staging gegen produktions-ähnliche Daten getestet, bevor er in Production geht.
Wo wir stehen: dev = lokal, prod = leander/test-2/rssw. Kein dediziertes Staging.
Trigger: Mit erstem zahlenden Kunden.
Was vorbereiten:
- Tenant
staging.prilog.teammit Kopie der Live-DB (anonymisiert für DSGVO) - Deploy-Reihenfolge: dev → staging → prod
- Staging hat den letzten 24h-Snapshot von prod (nightly)
19. Konsequente Type-Safety Backend ↔ Frontend
Was es ist: Beide Seiten teilen sich Type-Definitionen, sodass ein Backend-Refactor sofort Frontend-Compile-Fehler erzeugt.
Wo wir stehen: Wir handpflegen Gateway-Types parallel zum Backend. Drift ist möglich (siehe Bugs mit studentDisplayName, subjectLabel).
Was vorbereiten:
- Aus OpenAPI (siehe #5 in Industriestandards) TypeScript-Client generieren (
openapi-typescript) - Oder: Shared-Types-Package, beide Seiten importieren
20. Software-Lieferketten-Sicherheit (Supply Chain)
Was es ist: Wir vertrauen darauf, dass die ~3000 npm-Pakete keinen Malicious Code enthalten. Das ist statistisch fragil.
Wo wir stehen: Dependabot teilweise. Kein SBOM (Software Bill of Materials), kein Signing, kein lock-file-Hash-Pinning über Mirrors.
Trigger: Erster CVE in einer Direct-Dependency oder Schul-DSB fragt nach SBOM (kommt in EU-Regulierung).
Was vorbereiten:
- SBOM generieren:
npm sbomodersyft - CycloneDX-Format archivieren pro Release
- Sicherheits-Audit als Quartal-Job: alle Direct-Deps reviewen, ob benötigt + ob noch maintained
Wie diese Liste sich entwickelt
- Pro Quartal Review: was ist relevant geworden, wurde umgesetzt, ist obsolet
- Sobald ein Trigger gerissen ist (z. B. „erster Vorfall") → entsprechender Punkt wird hoch priorisiert
- Liste ist intentional zukunftsorientiert — Stand „heute brauchen wir's noch nicht" ist okay, solange wir uns nicht vom Wachstum überraschen lassen