Industrie-Standards, die wir noch nicht (oder noch nicht konsequent) nutzen
Praktiken, die in seriösen SaaS-Setups Standard sind. Mit Priorisierung nach Schmerz/Nutzen, damit klar ist, was als nächstes drankommt.
Legende:
- 🔴 Jetzt — Nicht-Tun ist heute schon Schmerzpunkt
- 🟡 Bald — Vor dem zweiten Kunden sinnvoll
- 🟢 Später — Ab ~10 Tenants oder beim ersten Incident relevant
🔴 1. E2E-Tests (Playwright)
Status: keine. Schmerz: Sprint 5/6 Zeugnis wurde „LIVE" mit grünen Mock-Tests, scheiterte sofort beim ersten echten Klick. Mock-Tests beweisen, dass die Funktion das tut, was wir denken — nicht, dass der Workflow funktioniert. Was tun:
- Playwright einrichten, ein Test-Tenant einrichten (
e2e-test.prilog.team) - Mindestens 1 Test pro neuem Feature: „Klassenleitung öffnet Zeugnis, schreibt Text, reicht zur Freigabe weiter, Schulleitung gibt frei"
- CI-Schritt: bei jedem Merge auf main werden die E2E gegen Test-Tenant gefahren. Wenn rot → kein Deploy.
🔴 2. Echte Datenbank-Migrationen mit Down-Pfad
Status: Migrations sind heute nur „up" (Add Column / Create Table). Bei Fehlern muss man manuell zurückdrehen (siehe L-008 Vorfall). Schmerz: Wenn eine Migration scheitert, ist die DB in einem Zwischen- zustand und keiner weiß, ob _prisma_migrations korrekt ist. Was tun:
- Pro Migration 2 Files:
up.sql+down.sql. Down-Pfad enthält das korrespondierende Rollback (DROP / ALTER TABLE ... DROP COLUMN). - Vor jedem Deploy auf einer Kopie der Live-DB durchspielen (
pg_dumplaufend → restore in Schatten-DB → migrate) - Memory
feedback_migration_dryrun.md(neu anlegen)
🔴 3. Feature Flags mit Kill-Switch
Status: Wir haben einzelne Feature-Toggles im enabledModules-Set, aber keinen sauberen Mechanismus für Halb-Releases / Notfall-Abschaltung. Schmerz: Wenn ein neues Feature in Production einen Bug zeigt, müssen wir entweder den Code reverten und re-deployen — oder schreien. Was tun:
- Tabelle
tenant_feature_flag(tenantId, flagKey, enabled, validFrom?, validUntil?, reason?)(Pattern vom existierendenComplianceGate) - Im Frontend:
useFeatureFlag('zeugnisse.bulk-import')hängt UI sichtbar an - Admin-UI: Schalter pro Tenant pro Flag
- Bei Notfall: SQL-Update setzt
enabled=false→ User sehen ab nächstem Page-Load keine UI-Pfade mehr für das Feature
🔴 4. Pre-Commit-Hooks (Husky + lint-staged)
Status: keine. Es passiert regelmäßig, dass TSC erst nach Push grün/rot ist. Schmerz: PRs müssen mehrmals gepusht werden, CI-Zeit wird verbrannt. Was tun:
husky+lint-stagedeinbauen- Hook
pre-commit: TSC für die geänderten Files + ESLint - Hook
pre-push: schnelle Unit-Tests - Niemals
--no-verify(steht schon im System-Prompt, aber explizit hier festhalten)
🟡 5. OpenAPI / Schema-First API
Status: wir schreiben Routen in Fastify direkt, Zod als Validator. Es gibt kein generiertes OpenAPI-Schema. Schmerz: Drittanbieter (n8n-Custom-Node, künftige Partner-Integrationen, Marketplace-Modules) müssen das API per Reverse-Engineering nachbauen. Was tun:
@fastify/swaggereinsetzen — Routen mit Schema-Annotation versehen@fastify/swagger-uifür/api/docs-Seite (intern, behind auth)- Generierte TypeScript-Client-Library für den Web-Client (statt handgepflegter Gateways)
🟡 6. Observability / Tracing (OpenTelemetry)
Status: Pino-Logs in Files. Kein verteiltes Tracing, keine Metriken. Schmerz: Wenn ein User „lädt nicht" meldet, durchsuchen wir Logs nach Pattern. Bei 100 Tenants funktioniert das nicht mehr. Was tun:
@opentelemetry/api+@opentelemetry/sdk-nodeeinbauen- Span pro HTTP-Request, Span pro DB-Query (Prisma-Middleware)
- Export an Tempo / Jaeger / Grafana Cloud (Stack zu klären)
- Latency-Histogramme pro Endpoint (für SLO-Tracking, siehe 12)
🟡 7. Strukturierte Error-Codes statt Strings
Status: wir haben prilogError('VALIDATION_ERROR', message) aber die Codes sind ad-hoc-Strings, kein Enum, kein i18n-key. Schmerz: Frontend kann nicht intelligent auf bestimmte Error-Codes reagieren (z. B. „token expired" → silent re-login vs. „permissions denied" → Re-Login-Hinweis). Was tun:
- Code-Enum (
ErrorCode.PASSWORD_TOO_SHORT, …) backend + frontend gemeinsam - Jeder Error-Code hat i18n-key für die User-facing Message
- Frontend hat zentralen
errorHandler.handle(error)der je nach Code unterschiedlich rendert
🟡 8. Idempotency Keys für POST-Endpoints
Status: keine. Wenn Browser einen POST doppelt sendet (z. B. wegen Network-Retry), wird die Aktion doppelt ausgeführt. Schmerz: Selten, aber bei Stripe-Payments oder Mail-Versand katastrophal (Doppel-Abbuchung, Doppel-Mail). Was tun:
- Header
Idempotency-Key: <client-generated-uuid>für alle POST-Endpoints - Server speichert pro Tenant 24h die Hash-zu-Result-Map. Bei Wiederholung: cached Result zurückliefern statt Aktion neu auszuführen
- Besonders wichtig für: Stripe-Webhook-Handler, Mail-Versand, Bulk-Operationen
🟡 9. Outbox Pattern für Mail / externe Aktionen
Status: Mail-Versand passiert synchron im Endpoint (sendMail(...).catch(...)). Wenn Mail fehlschlägt → silent loss, der User glaubt es ist gelaufen. Schmerz: Vorfälle wie verlorene Reset-Mails sind hart nachzuvollziehen. Was tun:
- Tabelle
outbox(id, tenantId, kind, payload, status, attempts, nextAttemptAt) - Schreibender Endpoint: Outbox-Eintrag in gleicher Transaktion wie Domain-Daten anlegen
- Separater Worker pickt Eintrag, führt Action aus, marked als delivered/failed. Bei
failedexponential backoff bis max-attempts. - Vorteil: Crash mitten in der Action verliert nichts, Retry läuft automatisch
🟡 10. Conventional Commits + automatischer Changelog
Status: Commit-Messages sind mal feat(), mal fix(), mal frei. Kein automatisch generiertes Changelog. Schmerz: Schule fragt „was hat sich seit letztem Monat geändert?" und wir müssen git log manuell zusammenfassen. Was tun:
- Convention erzwingen via Commitlint-Hook
release-pleaseo.ä. generiert Changelog pro Repo bei jedem Merge auf main- Auf
docs.prilog.chat/changelog/veröffentlichen, Schulen sehen es selbst
🟢 11. Visual Regression Testing (Chromatic / Percy)
Status: keine. Layout-Bugs (z. B. wenn jemand eine Spalte verbreitert) fallen erst auf, wenn ein User sich beschwert. Was tun: Sobald wir mehrere Tenants haben → Storybook + Chromatic. Vor jedem Merge: Screenshot-Diff. Wenn UI sich ungewollt ändert → blocked.
🟢 12. SLOs + Error Budgets
Status: keine. „Funktioniert" / „funktioniert nicht" ist binär bei uns. Was tun: Nach Observability (6):
- SLO pro kritischem Endpoint (z. B. „99 % der Login-Requests unter 800ms in einem 30-Tage-Fenster")
- Error Budget: 1 % darf scheitern. Wenn weniger → mehr Feature-Risiko ok. Wenn mehr → Sprint dreht auf Stabilität.
- Memory
reference_slo_budgets.md
🟢 13. Security Scanning (CodeQL, npm audit, Dependabot)
Status: Dependabot teilweise. Kein dediziertes SAST. Was tun:
- GitHub CodeQL für JS/TS aktivieren
npm audit fixals CI-Schritt (oder explizit dokumentiert)- Dependabot für alle Repos
- Bei Secrets-Leak: GitHub Secret Scanning aktivieren
🟢 14. Accessibility (WCAG 2.1 AA)
Status: ad-hoc. Buttons mit title= aber kein systematischer A11y-Check. Was tun:
eslint-plugin-jsx-a11yals Lint-Layer- Pro Page einmal mit Axe-DevTools / WAVE durchgehen
- Tastatur-Navigation muss überall funktionieren (Tab-Index, Enter, Esc)
- Schulen mit Inklusions-Schwerpunkt erwarten das
🟢 15. Performance Budget + Lighthouse CI
Status: keine Messung. Web-Client-Bundle ist ~19 MB precache (sw.js). Was tun:
- Lighthouse-Run als CI-Schritt, Threshold-Verstoß → blockt Merge
- Bundle-Size-Budget: z. B. „initial-bundle < 500 KB gzip"
- Critical-Path-CSS, Code-Splitting für seltene Routen
🟢 16. CODEOWNERS + PR-Templates
Status: keine. Aktuell ist es Ein-Owner-Setup. Was tun: Sobald ein zweiter Engineer dazukommt:
CODEOWNERS-File pro Repo (z. B. Stundenplan-Domain hat speziellen Reviewer).github/pull_request_template.mdmit Pflicht-Felder (Was/Warum/Tests/ Migration?/Rollback?).github/ISSUE_TEMPLATE/für Bugs + Features mit struktur
🟢 17. Architecture Decision Records (ADR)
Status: wir haben Decision-Docs in prilog_docs/umsetzung/<thema>/12-decision-log.md, aber kein einheitliches Pattern. Was tun:
- Format wie MADR (Markdown Architectural Decision Records)
- Pro architektonisch wichtige Entscheidung: Kontext, Optionen, Entscheidung, Konsequenzen, Status
- Ablage
prilog_docs/adr/0001-...md,0002-...md - Beispiele die wir RÜCKWIRKEND als ADR dokumentieren sollten:
- Modul-Architektur statt Monolith
- CRM-Foundation v2-Migration
- JWT-only Synapse-Login
- Stalwart als Mailserver
- Tenant-in-a-Box vs Shared-Hosting
🟢 18. Runbooks für Incident Response
Status: keine. Was tun:
- Pro typisches Incident-Szenario ein Runbook in
prilog_docs/runbooks/:- „Backend-API down" (was prüfen, wie restarten, wann eskalieren)
- „Postgres-Disk voll" (vacuum, wo finden, wer hat Zugriff)
- „Reset-Mail kommt nicht an" (Stalwart-Logs, MailPace-Status, …)
- „User berichtet 'kann mich nicht einloggen'" (welche Logs, welche Tabellen, welche Schritte)
- Jeder Runbook hat: Symptom, Diagnose, Recovery, Postmortem-Trigger
🟢 19. Postmortem-Kultur (blameless)
Status: wir dokumentieren in 02-lessons-aus-prilog.md, aber kein formales Postmortem-Format. Was tun:
- Bei jedem Vorfall mit User-Auswirkung MUSS Postmortem:
- Timeline (was passierte wann)
- Root Cause (echte Ursache, nicht erste Vermutung)
- Was geholfen hat (Detection, Mitigation)
- Was gefehlt hat (warum kam es so weit)
- Action Items (was wird konkret geändert, wer macht es bis wann)
- Format-Skeleton in
05-templates.md - Blameless: Fokus auf System-Schwächen, nicht Personen
🟢 20. Backup + Restore Drills
Status: Backups laufen (Postgres + S3). Restore wurde noch nie geübt. Schmerz: Beim ersten echten Daten-Loss erstmal rätseln, ob/wie Restore funktioniert. Was tun:
- Quartal-Drill: Backup von letzter Nacht in Schatten-Tenant restoren, funktioniert das? Wie lange dauert es?
- Dokumentiere RTO (Recovery Time Objective) und RPO (Recovery Point Objective) pro Daten-Klasse
- Bei Schulen typisch:
- Zeugnisse: RPO ≤ 1 Tag, RTO ≤ 4 Stunden (Schulalltag darf nicht stehen bleiben)
- Chat-Nachrichten: RPO ≤ 1 Stunde, RTO ≤ 1 Stunde
Wie diese Liste lebt
- Sobald wir Punkt X umsetzen → von 🔴/🟡/🟢 auf ✅ + Datum
- Neue Standards die uns einfallen → einfach unten ergänzen
- Bei jedem Sprint-Plan: erste Frage „können wir nicht einen 🔴-Punkt mit reinnehmen?"