Most multi-tenant SaaS is just a WHERE tenant_id = ? bolted onto a relational schema. It works until it doesn't — GDPR erasure requests, per-tenant audit trails, debugging "why did this break for Acme but not Beta Corp" at 2am.
We went a different direction with Kumiko: every write is an event in a per-tenant stream.
What that buys you:
Audit log for free. The stream is the audit log. No separate table, no "we should really add logging here someday."
GDPR erasure without surgical DELETEs. When a user requests erasure under Art. 17, we emit a UserErasureRequested event. The projection rebuild skips that user's data. Backup tapes? Handled separately, declaratively. No DELETE FROM ... WHERE user_id = ? scattered across 40 tables.
Debugging across tenants. You can replay Acme's event stream up to the moment before a bug appeared. No guessing what state the DB was in.
Feature tiers per tenant without schema branches. A tenant on the free tier just doesn't have certain event handlers registered. No if/else in business logic.
The downside is real: event sourcing adds complexity upfront. You have to think in terms of projections, not tables. It's not right for every app.
But for multi-tenant SaaS where GDPR, audit trails, and per-tenant customization are first-class concerns — it's the best foundation we've found.
We open-sourced the framework that makes this practical for TypeScript devs: kumiko.rocks
Happy to answer questions about the tradeoffs.