saas
SaaS State-of-the-Art – Multi-Tenancy, nutzungsbasierte Abrechnung, PLG, KI-Feature-Integration, moderne Observability, Compliance und Sicherheit für Software-as-a-Service-Produkte im Produktionseinsatz.
Anweisungen
Multi-Tenancy
Wählen Sie das richtige Isolationsmodell für Ihren Anwendungsfall:
| Modell | Beschreibung | Wann verwenden |
|---|---|---|
| Gemeinsames Schema | Alle Mandanten teilen sich Tabellen; Zeilen werden mit tenant_id getaggt | Frühe Phase, kostenbewusst, unkomplizierte Compliance |
| Schema-pro-Mandant | Separates DB-Schema pro Mandant | Mittlere Isolation, einfache Migrationen pro Mandant |
| DB-pro-Mandant | Separate Datenbank pro Mandant | Enterprise, strenge Datenresidenz, hohe Compliance (SOC 2, HIPAA) |
| Hybrid | Kerndaten geteilt; sensible Daten isoliert | Regulierte Branchen mit gestaffelten Angeboten |
Regeln für die gemeinsame Schema-Tenancy:
- Jede Abfrage muss nach
tenant_idfiltern – erzwingen Sie dies über ein Basis-Repository oder ORM-Middleware, nicht über die Disziplin der Entwickler - Verwenden Sie PostgreSQL Row-Level Security (RLS) als zweite Verteidigungsebene:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.current_tenant')::uuid); - Indizieren Sie
tenant_idin jeder mandantenbezogenen Tabelle - Schreiben Sie Integrationstests, die die mandantenübergreifende Datenisolation prüfen
- Begrenzen Sie alle Objektspeicher-Pfade:
s3://bucket/{tenantId}/uploads/{fileId} - Geben Sie niemals vorhersagbare, aufsteigende IDs preis – verwenden Sie UUIDs (v7 für Sortierbarkeit)
Abonnement & Abrechnung
Flatrate-Abonnements
- Delegieren Sie die Abrechnung an einen Anbieter (Stripe, Paddle, LemonSqueezy) – implementieren Sie niemals die Zahlungsabwicklung selbst
- Speichern Sie nur
customerIdundsubscriptionIdin Ihrer Datenbank; niemals Kartendaten - Synchronisieren Sie den Status über Webhooks – behandeln Sie Ihre Datenbank als eine letztlich konsistente Lesereplik der Wahrheit des Abrechnungsanbieters:
customer.subscription.created → Plan aktivieren customer.subscription.updated → Upgrade / Downgrade / Sitzungsänderung customer.subscription.deleted → Kündigen + Gnadenfrist starten invoice.payment_failed → Mahnung: benachrichtigen → einschränken → kündigen - Verwenden Sie die Webhook-Ereignis-
idals Idempotenzschlüssel – speichern Sie sie vor der Verarbeitung; überspringen Sie sie, wenn bereits gesehen - Speichern Sie rohe Webhook-Payloads für Audits und Wiederholungen
Nutzungsbasierte / gemessene Abrechnung (moderner Standard)
- Erfassen Sie Nutzungsereignisse in Echtzeit (API-Aufrufe, Sitzplätze, verbrauchte Tokens, GB Speicher):
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, { quantity: tokensUsed, timestamp: Math.floor(Date.now() / 1000), action: 'increment', }); - Stellen Sie Kunden ein Live-Nutzungs-Dashboard zur Verfügung – Überraschungen am Rechnungstag verursachen Abwanderung
- Implementieren Sie weiche Limits (Warnung bei 80%) und harte Limits (Sperre bei 100%) pro Abrechnungszeitraum
- Erwägen Sie einen kostenlosen Tarif + nutzungsbasiertes Upselling als Product-Led Growth (PLG)-Einstieg
Product-Led Growth (PLG)
- Kostenloser Tarif / Freemium – lassen Sie Benutzer den Wert erfahren, bevor Sie eine Zahlung verlangen; setzen Sie Limits, die kostenpflichtige Pläne offensichtlich lohnenswert machen
- Time-to-Value (TTV) – messen und minimieren Sie die Zeit von der Anmeldung bis zur ersten sinnvollen Aktion ("Aha-Moment")
- In-App-Upgrade-Aufforderungen – platzieren Sie Upgrade-CTAs kontextbezogen in dem Moment, in dem ein Planlimit erreicht wird, nicht auf einer allgemeinen Einstellungsseite
- Product-Qualified Leads (PQL) – bewerten Sie Benutzer anhand von Produktnutzungssignalen (nicht nur Demo-Anfragen), um sie an den Vertrieb weiterzuleiten
- Virale / kollaborative Funktionen – Teamkollegen einladen, Berichte teilen, öffentliche Seiten – jede Aktion ist ein Verteilungskanal
KI-Feature-Integration
Moderne SaaS-Produkte sollen KI-native Funktionen bieten. Wenden Sie diese Muster an:
KI-Assistenten und Copiloten:
- Verwenden Sie Streaming-Antworten (SSE /
ReadableStream), damit Benutzer Tokens sehen, während sie eintreffen – lassen Sie sie niemals auf eine vollständige Antwort warten - Machen Sie KI-Ausgaben immer bearbeitbar – Benutzer müssen die Ausgabe korrigieren, verfeinern und besitzen können
- Zeigen Sie Konfidenzsignale an und zitieren Sie Quellen (RAG), um Vertrauen aufzubauen
Retrieval-Augmented Generation (RAG):
- Speichern Sie mandantenbezogene Vektoreinbettungen in einem isolierten Namespace (Pinecone, pgvector, Qdrant) – vermischen Sie niemals Mandantendaten
- Betten Sie Inhalte beim Schreiben ein; fragen Sie beim Lesen mit Kosinus-Ähnlichkeit ab
- Teilen Sie Dokumente intelligent auf (512–1024 Tokens mit Überlappung); betten Sie neu ein, wenn sich der Quellinhalt ändert
Nutzungs- und Kostenkontrollen:
- Verfolgen Sie den LLM-Tokenverbrauch pro Mandant – berechnen Sie entsprechend oder begrenzen Sie die Rate
- Cachen Sie deterministische Prompts (gleiche Eingabe → gleiche Ausgabe), um Kosten zu senken
- Verwenden Sie günstigere Modelle für Klassifizierung/Triage; reservieren Sie Frontier-Modelle für Generierungsaufgaben
Datenschutz:
- Bieten Sie ein Datenverarbeitungsabkommen (DPA) an, das explizit angibt, ob Mandantendaten für das Modelltraining verwendet werden
- Stellen Sie eine Opt-Out-Möglichkeit bereit; Unternehmenskunden werden dies verlangen
Feature Flags & Plan-Steuerung
- Steuern Sie Funktionen auf der Service-Ebene – clientseitiges Ausblenden ist UI-Kosmetik, keine Sicherheit
- Zentralisieren Sie die Berechtigungslogik in einem einzigen
can(tenant, feature)-Helfer:function can(tenant: Tenant, feature: Feature): boolean { return tenant.plan.features.includes(feature); } - Entkoppeln Sie die Bereitstellung von Funktionen von der Freigabe von Funktionen mithilfe eines Flag-Systems (LaunchDarkly, Unleash, GrowthBook oder eine einfache DB-gestützte Tabelle)
- Verwenden Sie Flags für: schrittweise Rollouts (% der Mandanten), Beta-Programme, A/B-Tests, Kill-Switches
Onboarding
- Minimieren Sie TTV: Führen Sie Benutzer in < 5 Minuten zur ersten sinnvollen Aktion
- Verwenden Sie einen checklistenbasierten Onboarding-Flow mit klaren Fortschrittsanzeigen
- Befüllen Sie das Produkt vorab mit Beispiel-/Demodaten, damit es sich bei der ersten Nutzung nicht leer anfühlt
- Erfassen Sie zu Beginn das absolute Minimum an Daten; verschieben Sie alles andere
- Lösen Sie Onboarding-E-Mails basierend auf Produktereignissen aus (nicht nur Zeit): Willkommen bei der Anmeldung, Erinnerung, wenn Schritt 2 nach 24h nicht abgeschlossen ist, feiern Sie den ersten Meilenstein
- Bieten Sie eine interaktive Produkttour (kein Video), damit Benutzer durch Ausprobieren lernen
Observability & Betrieb
Strukturiertes Logging – jede Logzeile muss Folgendes enthalten:
{ "level": "info", "msg": "...", "tenantId": "...", "userId": "...", "requestId": "...", "timestamp": "..." }
Verteiltes Tracing mit OpenTelemetry:
- Instrumentieren Sie auf Framework-Ebene (Next.js
instrumentation.ts, Express-Middleware) - Propagieren Sie Trace-Kontext über Servicegrenzen hinweg (
traceparent-Header) - Exportieren Sie in Ihr Backend Ihrer Wahl (Jaeger, Honeycomb, Datadog, Grafana Tempo)
Pro-Mandant zu verfolgende Metriken:
- Aktive Benutzer (DAU/MAU), API-Aufrufvolumen, Fehlerrate, p50/p95/p99-Latenz, Feature-Adoption, verbrauchter Speicher
Alerting:
- Alarmieren Sie bei mandantenbezogenen Anomalien – plötzlicher Fehleranstieg, Nutzungsabfall, Abrechnungsfehler
- Definieren Sie SLOs pro Stufe: kostenlos (Best-Effort), Pro (99,5%), Enterprise (99,9%)
Admin-Tools (intern):
- Übernehmen Sie einen Mandanten für den Support (auditieren Sie jede Übernahme)
- Audit-Log anzeigen, Abonnements verwalten, Feature-Flags überschreiben, erneutes Onboarding auslösen
- Bauen Sie ein Health-Dashboard: Status pro Mandant, aktuelle Fehler, Nutzungstrends
Daten, Analytics & Compliance
Event-Tracking:
- Senden Sie strukturierte Produktereignisse (
user.signed_up,feature.used,subscription.upgraded) an ein Data Warehouse (Snowflake, BigQuery) oder eine CDP (Segment, PostHog, RudderStack) - Verwenden Sie diese Ereignisse für die Abwanderungsvorhersage, Kohortenanalyse und PLG-Bewertung
DSGVO / Datenschutz:
- Stellen Sie einen Datenexport-Endpunkt und einen Kontolöschungs-Endpunkt bereit – von der DSGVO gefordert und von Unternehmenskäufern erwartet
- Dokumentieren Sie Ihre Datenaufbewahrungsrichtlinie; implementieren Sie automatisierte Bereinigungen
- Verwenden Sie ein Consent-Management für Marketing-Cookies und Analytics
SOC 2 / Compliancy-Hygiene (Enterprise-Reife):
- Führen Sie ein Audit-Log aller zustandsändernden Benutzeraktionen (wer, was, wann, von welcher IP)
- Implementieren Sie eine rollenbasierte Zugriffskontrolle (RBAC) mit Minimalrechte-Standardeinstellungen
- Erzwingen Sie MFA für alle Admin-Konten; bieten Sie SSO (SAML/OIDC) für Enterprise-Mandanten
- Führen Sie regelmäßige Schwachstellenscans von Abhängigkeiten und Penetrationstests durch
- Pflegen Sie eine Richtlinie zur Offenlegung von Sicherheitslücken
Datenisolation & Sicherheit
- Validieren Sie
tenant_idbei jedem API-Aufruf gegen die authentifizierte Sitzung – vertrauen Sie niemals dem Anforderungstext - Begrenzen Sie die Rate pro Mandant, um Nachbarschaftseffekte zu vermeiden
- Hashen Sie API-Schlüssel in der DB (bcrypt oder Argon2); speichern Sie niemals Klartext
- Erzwingen Sie HTTPS; setzen Sie
Strict-Transport-Securitymit einem langenmax-age - Verwenden Sie
HttpOnly; Secure; SameSite=Laxfür Sitzungs-Cookies
Beispiele
// Mandantenbezogenes Repository mit RLS-Kontext
class TenantRepository<T> {
constructor(
private readonly tenantId: string,
private readonly db: PrismaClient,
) {}
protected async withTenantContext<R>(fn: () => Promise<R>): Promise<R> {
return this.db.$transaction(async (tx) => {
await tx.$executeRaw`SELECT set_config('app.current_tenant', ${this.tenantId}, true)`;
return fn();
});
}
async findById(id: string): Promise<T | null> {
return this.withTenantContext(() =>
(this.db as any).findFirst({ where: { id, tenantId: this.tenantId } })
);
}
}
// KI-Streaming-Endpunkt mit mandantenbezogener Nutzungsmessung
export async function POST(req: Request) {
const session = await getSession(req);
const { prompt } = await req.json();
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
stream: true,
});
let tokensUsed = 0;
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content ?? '';
controller.enqueue(new TextEncoder().encode(text));
tokensUsed += chunk.usage?.completion_tokens ?? 0;
}
controller.close();
// Nutzung nach Abschluss des Streamings messen
await meteringService.record(session.tenantId, 'ai_tokens', tokensUsed);
},
});
return new Response(readable, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}
// Stripe Webhook mit Idempotenz
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, req.headers['stripe-signature']!, process.env.STRIPE_WEBHOOK_SECRET!
);
const seen = await db.webhookEvent.findUnique({ where: { stripeEventId: event.id } });
if (seen) return res.sendStatus(200);
await db.webhookEvent.create({ data: { stripeEventId: event.id, payload: event } });
switch (event.type) {
case 'customer.subscription.updated':
await subscriptionService.sync(event.data.object);
break;
case 'customer.subscription.deleted':
await subscriptionService.cancel(event.data.object.metadata.tenantId);
break;
}
res.sendStatus(200);
});