EMZETT.
Login

Multi-Tenant-Architektur

Kurz: Eine Software-Architektur, bei der eine einzige Anwendung mehrere unabhängige Kunden (“Tenants”) gleichzeitig bedient — jeder mit eigenen Daten, oft über eigene Subdomains getrennt.

Genauer: Typisches Muster: kunde1.meine-plattform.de, kunde2.meine-plattform.de usw. laufen alle über dieselbe Code-Basis, aber mit getrennten Daten pro Tenant. Braucht in der Regel einen Wildcard-DNS-Eintrag und eine Zahlungslösung, die Geld an mehrere Empfänger verteilen kann (z. B. Stripe Connect).

Kontext bei uns: Langfristiges Ziel für Emzett — die Shop-Plattform soll später an andere “vermietet” werden können, jeder Kunde über eine eigene Subdomain. Laut Roadmap der letzte, größte Ausbauschritt.

Im Detail

Drei Isolationsgrade

Bei Multi-Tenant-Systemen ist die zentrale Design-Entscheidung, WIE die Daten der verschiedenen Tenants voneinander getrennt werden — von “locker” bis “streng” gibt es grob drei gängige Ansätze:

  1. Geteilte Tabellen mit tenant_id-Spalte — alle Tenants liegen in denselben Tabellen, jede Zeile trägt eine Tenant-Kennung, jede Query filtert zusätzlich danach. Am einfachsten zu betreiben (eine Datenbank, ein Schema, ein Connection-Pool), aber ein vergessener WHERE tenant_id = ...-Filter kann Daten zwischen Kunden vermischen — ein ernstes Sicherheitsrisiko, das in der Praxis oft über Row-Level-Security-Policies auf Datenbankebene abgesichert wird (Postgres unterstützt das nativ: eine Policy sorgt dafür, dass selbst eine vergessene WHERE-Klausel im Anwendungscode keine fremden Zeilen zurückliefert, weil die Datenbank selbst filtert).
  2. Ein Schema pro Tenant — dieselbe Datenbank, aber jeder Tenant bekommt ein eigenes PostgreSQL-Schema (Namensraum, z. B. tenant_kunde1.orders statt public.orders). Bessere Isolation als Ansatz 1 (ein falsch geschriebener Query kann gar nicht versehentlich auf ein fremdes Schema zugreifen, ohne es explizit zu adressieren), aber Migrationen müssen für jedes Schema einzeln laufen — bei hunderten Tenants ein spürbarer betrieblicher Mehraufwand.
  3. Eine Datenbank pro Tenant — maximale Isolation (ein Datenbankfehler oder eine Performance-Spitze bei Tenant A betrifft Tenant B gar nicht), aber am aufwendigsten zu betreiben, besonders bei vielen kleinen Tenants: Jede Datenbank braucht eigene Backups, eigenes Monitoring, eigene Migrationen, und die Zahl der offenen Connections steigt linear mit der Tenant-Zahl.

Viele größere SaaS-Plattformen (z. B. Slack in seiner frühen Architektur) beginnen mit Ansatz 1 und wandern bei wachsender Kundengröße/Compliance-Anforderungen (manche Enterprise-Kunden verlangen vertraglich physische Datentrennung) Richtung Ansatz 2 oder 3, oft nur für die größten Kunden, während kleinere weiterhin Ansatz 1 teilen (“hybrides” Modell).

Subdomain-Routing

Die Subdomain-Erkennung selbst passiert meist im Middleware/Proxy-Layer: Der Host-Header des Requests wird geparst, der Tenant daraus abgeleitet, und dieser Kontext dann an alle nachgelagerten Datenbank-Queries weitergereicht (z. B. über einen Request-Kontext oder eine Middleware-gesetzte Variable, die dann z. B. per AsyncLocalStorage in Node.js durch die gesamte Request-Bearbeitung “durchgereicht” wird, ohne sie explizit an jede Funktion übergeben zu müssen).

// Vereinfachtes Muster im Middleware/Proxy-Layer
const host = request.headers.get("host"); // z.B. "kunde1.meine-plattform.de"
const tenant = host?.split(".")[0];

Für Wildcard-Subdomains braucht die DNS-Zone einen entsprechenden Eintrag (*.meine-plattform.de → dieselbe Server-IP), und beim TLS-Zertifikat muss ebenfalls ein Wildcard-Zertifikat (oder automatisches Zertifikat-Provisioning pro Subdomain, wie es Vercel für benutzerdefinierte Domains anbietet) hinterlegt sein, sonst bekommen Besucher eine Zertifikatswarnung.

Zahlungsverteilung

Bei einer Plattform, die selbst Geld von Endkunden entgegennimmt und an die jeweiligen Tenants weiterleiten muss (Marktplatz-Modell), reicht eine normale Stripe-Integration nicht aus — dafür ist Stripe Connect gedacht: Jeder Tenant bekommt ein eigenes (verknüpftes) Stripe-Konto, Zahlungen lassen sich direkt oder über die Plattform mit einer Provisions-Aufteilung (“Application Fee”) routen, ohne dass die Plattform selbst haftungsrechtlich als Zahlungsempfänger für alle Tenant-Umsätze auftreten muss.

Siehe auch: DNS-Records