SaaS-Architektur-Checkliste: 50 Entscheidungen, die du vor dem Skalieren treffen musst
Die meisten SaaS-Produkte scheitern nicht an einem einzigen großen Architekturfehler. Sie werden langsam und teuer in der Weiterentwicklung, weil Dutzende kleiner Entscheidungen getroffen wurden, die niemand bewusst gefällt hat. Zwei Jahre später dauert eine Migration, die eine Woche dauern sollte, ein Quartal.
Diese SaaS-Architektur-Checkliste sammelt die 50 Entscheidungen, die in Code-Audits von Produkten zwischen Seed und Series B am häufigsten Schmerzen verursachen. Für jede gibt es eine Standardwahl für ein typisches B2B-SaaS und die Bedingungen, unter denen eine andere Wahl sinnvoll ist. Du musst nicht alle 50 beim Start richtig machen. Du musst nur wissen, welche du übersprungen hast. Der Beitrag zu SaaS-Architekturfehlern, die Startups bei der Series A das Genick brechen behandelt die Fehlerbilder; dieser hier ist die Referenzversion.
Datenmodell: 12 Entscheidungen
-
Multi-Tenancy-Isolation. Standard: gemeinsame Datenbank, gemeinsames Schema, eine
tenant_id-Spalte auf jeder mandantenbezogenen Tabelle, durchgesetzt auf Query-Ebene. Wechsle nur zu Schema-pro-Mandant oder Datenbank-pro-Mandant, wenn ein Kundenvertrag oder eine Aufsichtsbehörde physische Trennung verlangt. Siehe Multi-Tenant-SaaS-Architekturmuster für die Abwägungen. -
Wo die Mandantentrennung durchgesetzt wird. Standard: an einer Stelle (eine Repository-Basisklasse, eine Query-Builder-Middleware oder Postgres Row-Level-Security), nie handgeschrieben pro Query. Wenn ein Entwickler das
WHERE tenant_idvergessen kann, wird er es irgendwann tun. -
Typ des Primärschlüssels. Standard: UUIDv7 oder ULID, zeitlich geordnet, damit Indizes kompakt bleiben. Auto-Increment-Integer verraten Datensatzanzahlen in URLs, und zufällige UUIDv4-Fragmente zerstückeln B-Tree-Indizes bei großen Tabellen.
-
Soft-Delete-Strategie. Standard: ein nullbarer
deleted_at-Zeitstempel plus ein partieller Index aufdeleted_at IS NULL. Hartes Löschen nur, wo es das Gesetz verlangt (DSGVO-Löschung), und das über einen dedizierten Job, nicht per Ad-hoc-SQL. -
Speicherung von Zeitstempeln. Standard:
timestamptzin Postgres, alles in UTC gespeichert, an der Kante konvertiert. Lokale Zeiten ohne Offset verursachen doppelte Cron-Läufe und Berichte, die einen Tag daneben liegen. -
Zeitzone des Mandanten gegenüber Zeitzone des Nutzers. Standard: beide speichern. Berichte sind pro Mandant, Benachrichtigungen sind pro Nutzer, und wenn du das erst spät entscheidest, musst du überall nachträglich Spalten einbauen.
-
Geld. Standard: Integer in kleinsten Einheiten (Cent) plus ISO-Währungscode. Nie
float. -
Audit-Log-Architektur. Standard: eine append-only
audit_events-Tabelle, geschrieben aus dem Anwendungscode auf Service-Ebene, mit Akteur, Mandant, Entität, Aktion und einem JSON-Diff. Datenbank-Trigger verbergen die fachliche Bedeutung. Enterprise-Käufer werden danach fragen, bevor sie unterschreiben. -
Schema-Migrationen. Standard: versionierte Migrationsdateien im Repository, automatisch in der Deploy-Pipeline angewendet, immer abwärtskompatibel zur vorherigen App-Version. Expand-then-contract bei jeder Umbenennung.
-
JSON-Spalten. Standard: erlaubt für mandantenspezifische Einstellungen und unstrukturierte Metadaten, verboten für alles, wonach du filterst oder joinst. In dem Moment, in dem du
WHERE data->>'status' = 'active'in einem Hot Path schreibst, will dieses Feld eine echte Spalte. -
Read-Modelle. Standard: keine, bis ein Screen messbar langsam ist. Dann eine materialisierte View, aktualisiert durch einen Job, keine zweite Datenbank.
-
Volltextsuche. Standard: Postgres
tsvector, bis du Facettierung, Tippfehlertoleranz oder Ranking über Millionen von Dokumenten brauchst. Danach Elasticsearch oder Meilisearch, gespeist vom selben Event-Stream wie alles andere. Der Beitrag PostgreSQL-Schema-Design für SaaS geht tiefer auf die Entscheidungen 3 bis 12 ein.
Authentifizierung und Autorisierung: 8 Entscheidungen
-
Identität bauen oder kaufen. Standard: die Identitätsschicht kaufen (Auth0, Clerk, Keycloak, WorkOS), wenn Enterprise-SSO innerhalb von 18 Monaten auf der Roadmap steht. Nur bauen, wenn es einen Compliance-Grund gibt, Zugangsdaten intern zu behalten, und jemand da ist, der das schon einmal gemacht hat.
-
Session-Speicherung. Standard: serverseitige Sessions in Redis mit einem opaken Cookie für Browser-Clients. JWTs für API-Clients und Service-zu-Service-Aufrufe. Ein JWT in einem Browser-Cookie gibt dir das Revocation-Problem von Tokens mit der CSRF-Angriffsfläche von Cookies.
-
Token-Lebensdauer und Rotation. Standard: 15-Minuten-Access-Tokens, Refresh-Tokens, die bei jeder Nutzung rotieren, und ein Family-Level-Revoke, wenn ein wiederverwendetes Refresh-Token erkannt wird.
-
Wo Berechtigungen geprüft werden. Standard: an der Service- oder Use-Case-Grenze, einmal, mit dem Ergebnis für den Request gecacht. Prüfungen auf Controller-Ebene werden undicht, sobald eine CLI oder ein Queue-Consumer zu einem zweiten Einstiegspunkt wird.
-
Berechtigungsmodell. Standard: Rollen pro Mandant (Owner, Admin, Member, Read-only) mit einer kleinen Menge expliziter Berechtigungen dahinter. Ressourcenbezogene ACLs nur hinzufügen, wenn ein Kunde nach "dieser Nutzer soll Projekt A sehen können, aber nicht Projekt B" fragt.
-
API-Keys. Standard: gehasht gespeichert wie Passwörter, mit Präfix, damit sie in Logs und Secret-Scannern erkennbar sind, auf einen Mandanten und eine Berechtigungsmenge beschränkt, mit einer
last_used_at-Spalte, damit du tote Keys findest. -
Passwort-Hashing. Standard: argon2id, oder bcrypt mit Cost 12, falls die Laufzeitumgebung kein argon2 hat. Nicht pro Mandant konfigurierbar.
-
Impersonation. Standard: baue einen Support-Impersonation-Flow früh, protokolliere jede Nutzung in der Audit-Tabelle, zeige dem impersonierenden Mitarbeiter ein Banner und begrenze die Zeit.
API-Design: 9 Entscheidungen
-
Versionierung. Standard: keine Version in der URL. Felder hinzufügen, nie entfernen oder umbenennen, und nur bei einem tatsächlichen Breaking Change eine datumsbasierte Header-Version nutzen.
-
Pagination. Standard: cursor-basiert mit einem opaken Cursor und einem
next_cursorin der Antwort. Offset-Pagination ist für Admin-Screens in Ordnung und bricht bei großen Tabellen zusammen, bei denen sich Zeilen zwischen den Seiten ändern. -
Fehlerantwort-Schema. Standard: eine Form für jeden Fehler, überall: ein maschinenlesbarer
code, eine menschenlesbaremessage, ein optionalesfieldfür Validierungsfehler und einerequest_id. Nimm RFC 9457 Problem Details, wenn du einen Standard zum Verweisen willst. -
Idempotenzschlüssel. Standard: erforderlich bei jedem Nicht-GET-Endpunkt, der etwas erstellt oder belastet. Speichere den Schlüssel mit Mandant, Request-Hash und Antwort für 24 Stunden. Sonst ist jeder Retry bei einem instabilen Netzwerk eine Dopplung.
-
Rate-Limiting. Standard: pro Mandant und pro API-Key, Token-Bucket in Redis, mit
RateLimit-*-Headern in der Antwort. Zusätzliche Limits pro Endpunkt für teure Operationen wie Exporte. -
Feldauswahl und Expansion. Standard: keine. Liefere eine stabile Repräsentation und mach sie schnell. Sparse Fieldsets erst hinzufügen, wenn ein Kunde den Unterschied misst.
-
Ausgehende Webhooks. Standard: signierte Payloads (HMAC mit einem Secret pro Endpunkt), At-least-once-Zustellung mit exponentiellem Backoff, ein sichtbares Zustell-Log in der UI und ein Event-Typ in jeder Payload.
-
Bulk-Operationen. Standard: asynchron. Ein Bulk-Endpunkt nimmt den Job entgegen, liefert eine Job-ID zurück, und der Client pollt oder erhält einen Webhook. Synchrone Bulk-Endpunkte produzieren Timeouts und halb angewendete Änderungen.
-
Öffentliche API vs. interne API. Standard: dieselbe API, mit dem internen Frontend als erstem Kunden. Zwei APIs driften innerhalb von Monaten auseinander.
Background-Jobs: 7 Entscheidungen
-
Transport. Standard: eine richtige Queue (SQS, RabbitMQ, Redis Streams oder Symfony Messenger und BullMQ obendrauf), nie eine Datenbanktabelle, die jede Sekunde abgefragt wird. Das Datenbank-als-Queue-Muster funktioniert, bis der erste Lock-Contention-Vorfall passiert.
-
Retry-Strategie. Standard: exponentielles Backoff mit Jitter, gedeckelt bei fünf Versuchen. Feste Ein-Sekunden-Retries machen aus einem Provider-Ausfall einen selbstverschuldeten.
-
Dead-Letter-Handling. Standard: jede Queue hat eine Dead-Letter-Queue, und die Dead-Letter-Queue hat einen Alert. Ein Job, der fünfmal fehlschlägt, ist ein Bugreport, keine Statistik.
-
Job-Idempotenz. Standard: jeder Handler ist sicher zweimal ausführbar. At-least-once-Zustellung bedeutet, dass die Queue ihn irgendwann zweimal ausführt.
-
Payload-Inhalt. Standard: nur IDs, nie serialisierte Entitäten. Der Handler lädt frischen Zustand neu. Serialisierte Entitäten sind veraltet, bis der Job läuft.
-
Scheduling. Standard: ein Scheduler-Prozess, der Jobs einreiht, und Worker, die sie ausführen. Cron auf dem App-Server läuft doppelt, sobald eine zweite Instanz dazukommt. Der Beitrag zu geplanten Jobs und Cron im großen Maßstab hat die Details.
-
Langlaufende Arbeit. Standard: alles, was länger als ein paar Minuten läuft, in eine Kette kleinerer Jobs aufteilen, mit Fortschritt zwischen den Schritten gespeichert. Ein einzelner Drei-Stunden-Job lässt sich nicht überdeployen oder ohne Neustart wiederholen.
Observability: 7 Entscheidungen
-
Log-Format. Standard: strukturiertes JSON mit einem festen Satz Top-Level-Felder (
timestamp,level,message,request_id,tenant_id,user_id,service) ab dem ersten Tag. -
Trace-Context-Propagation. Standard: OpenTelemetry, mit der Trace-ID injiziert in jede Logzeile, jeden ausgehenden HTTP-Aufruf und jede Queue-Job-Payload. Wohin du die Traces sendest, ist weniger wichtig, als die Verkabelung nie nachträglich bauen zu müssen.
-
Metriknamensgebung. Standard: eine Konvention, dokumentiert, orientiert an den OpenTelemetry Semantic Conventions, wo sie existieren.
-
Error-Tracking. Standard: Sentry oder gleichwertig, mit getaggten Releases und dem Mandanten an jedem Event.
-
Health-Checks. Standard: ein flacher
/healthfür den Load Balancer und ein tiefer/ready, der Datenbank, Queue und Cache prüft. Verdrahte den tiefen Check nie mit dem Load Balancer, sonst nimmt ein einzelner langsamer Redis-Aufruf die ganze Flotte aus der Rotation. -
Nutzungsmetriken pro Mandant. Standard: zähle Requests, Speicher und Job-Ausführungen pro Mandant ab Tag eins, selbst wenn du nie danach abrechnest. Du wirst es für die Preisgestaltung brauchen und um den Mandanten zu finden, der dich heimlich Geld kostet.
-
Alerting-Philosophie. Standard: auf Symptome alarmieren (Fehlerrate, Latenz, Queue-Tiefe), nicht auf Ursachen (CPU, Speicher). Alerts, die feuern, ohne dass jemand handelt, werden entfernt, nicht stummgeschaltet.
Deployment: 7 Entscheidungen
-
Zero-Downtime-Deploys. Standard: rollierende Deploys hinter einem Load Balancer mit Connection Draining, oder Blue-Green, wenn die Plattform es günstig macht. Beides erfordert, dass alte und neue Version gleichzeitig gegen dieselbe Datenbank laufen, was wieder Entscheidung 9 ist.
-
Timing der Migrationen. Standard: Migrationen laufen als separater Schritt, bevor die neue App-Version startet, und sind so geschrieben, dass die alte Version weiterläuft, während sie laufen. Migrationen beim Container-Start laufen sich bei zwei Replicas gegenseitig ins Gehege.
-
Rollback-Mechanismus. Standard: das vorherige Image neu deployen, was ein Ein-Befehl-Vorgang sein muss, den jemand tatsächlich geübt hat. Down-Migrationen sind keine Rollback-Strategie: bei Daten vorwärts rollen, bei Code zurück.
-
Konfiguration und Secrets. Standard: Umgebungsvariablen, von der Plattform für Konfiguration injiziert, ein Secret-Manager (Vault, AWS Secrets Manager, Doppler) für Secrets und ein Startup-Check, der schnell fehlschlägt, wenn eine benötigte Variable fehlt.
-
Umgebungsparität. Standard: ein Container-Image, das über Staging nach Produktion befördert wird, wobei sich nur die Konfiguration ändert.
-
Feature-Flags. Standard: ein Flag-System, bevor du das erste Flag brauchst, damit Deployen und Freischalten getrennte Ereignisse sind. Eine selbstgebaute Tabelle mit Cache reicht zum Start. Flags löschen, die älter als drei Monate sind.
-
Datenresidenz. Standard: wisse, in welcher Region jedes Byte liegt, einschließlich Backups, Logs und Drittanbieter-Prozessoren, und dokumentiere es. Europäische Kunden werden danach fragen, und "wir sind uns nicht sicher" verlangsamt den Deal. Wenn du wirklich regionsbezogene Isolation brauchst, ist das ein eigenes Architekturprojekt, beschrieben in Multi-Region-SaaS-Architektur für Datenresidenz.
Wie du diese SaaS-Architektur-Checkliste nutzt
Behandle die 50 Standardwerte nicht als Regeln. Behandle sie als die Antworten, gegen die du argumentieren musst. Geh die Liste mit deinem Tech Lead durch und markiere jeden Punkt als erledigt, bewusst anders oder unentschieden. Die Spalte "unentschieden" ist dein echtes technisches Schuldenregister. Sie ist meist länger, als das Team erwartet, und das meiste davon ist bei 100 Kunden billig zu beheben und bei 1.000 teuer.
Ein paar dieser Entscheidungen sind fast irreversibel, sobald Daten existieren: das Mandantenmodell, Primärschlüsseltypen, Zeitstempelspeicherung und das Audit-Log. Die verdienen eine Stunde Diskussion vor der ersten Migration. Der Rest lässt sich meist später zu ungefähr konstanten Kosten ändern. Gefährlich sind die, bei denen die Kosten mit der Codebasis wachsen, wie wo Berechtigungen geprüft werden und wie Logs strukturiert sind.
Wenn du ein neues Produkt baust, hilft Wolf-Tech Teams, diese Entscheidungen früh zu treffen, mit Tech-Stack-Strategie und Custom-Software-Entwicklung. Wenn du bereits ein Produkt hast und wissen willst, wie viele der 50 du falsch gemacht hast, liefert ein Code- und Architektur-Audit genau diese Liste mit deinen Antworten ausgefüllt. Schreib an hello@wolf-tech.io oder schau bei wolf-tech.io vorbei.

