Skip to content

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_dump laufend → 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 existierenden ComplianceGate)
  • 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-staged einbauen
  • 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/swagger einsetzen — Routen mit Schema-Annotation versehen
  • @fastify/swagger-ui fü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-node einbauen
  • 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 failed exponential 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-please o.ä. 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 fix als 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-a11y als 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.md mit 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?"