Choose tenant boundaries deliberately
Tenant isolation can live in rows, schemas, databases or separate infrastructure. The right choice depends on risk, scale, compliance and operational cost.
Whatever model you choose, tenant identity must propagate through requests, jobs, caches, logs and background tasks. A missing tenant filter is a security bug, not just a data bug.
Model authorization independently from UI roles
“Admin” and “member” are often too coarse. Model permissions around capabilities and resources so enterprise customers can grow into more complex policies.
Authorization should be enforced at service boundaries and covered by tests. Frontend visibility is only a usability layer.
Treat billing as a ledger problem
Subscription status, usage, credits, invoices and retries create edge cases. Do not scatter billing decisions across product code.
Normalize provider webhooks, make handlers idempotent and store enough event history to reconcile state when a webhook arrives late or twice.
Move slow work to reliable async processing
Exports, imports, AI jobs, emails, media processing and integrations should not block request threads. Use queues with retry policy, deduplication, dead-letter handling and observability.
Every background job needs an idempotency strategy because retries are normal, not exceptional.
Make migrations backward compatible
SaaS cannot stop for every database or API migration. Use additive schema changes, dual-read/write where needed and staged cleanup.
The safest migration is one that old and new application versions can both survive during rollout.
Observe by tenant and workload
Global averages hide noisy-neighbor problems. Track latency, error rate, queue depth and cost by tenant or workload class.
Rate limits, quotas and workload isolation protect the platform while giving high-value customers predictable service.