Skip to content

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 — stattdessen otherModuleService.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 VIEW mit periodischem REFRESH

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.findMany ohne where: { 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-canary mit 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.md mit 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.md mit 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.team mit 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 sbom oder syft
  • 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