Tenancy & isolation
Multi-tenancy is the part of a SaaS backend that looks trivial in a demo and becomes a real security question in production: what actually stops one customer's data from leaking into another's response? The platform answers that at the data-access layer, not by convention.
Tenant context is never trusted from the caller
The tenant a request operates on is resolved from the gateway-signed X-Tenant-Id header — never from a request body field, a query parameter, or a path segment the client controls. A handler that read a tenant id out of the request itself would be a bug, not a feature.
// WRONG — trusting a caller-supplied tenant id
@GetMapping("/orders")
List<Order> list(@RequestParam UUID tenantId) {
return orderRepo.findByTenantId(tenantId); // caller picks the tenant!
}
// The tenant comes from the verified gateway header, never a parameter.
@GetMapping("/orders")
List<Order> list() {
UUID tenantId = TenantContext.getTenantId(); // from X-Tenant-Id, signature-verified
return orderRepo.findByTenantIdAndTenantId(tenantId); // enforced, every query
}Enforced at the data layer
Every service scopes its reads and writes to the caller's tenant at the repository layer — so even a handler that forgets to filter explicitly still can't cross tenant boundaries. That's defence in depth: the header can't be spoofed, and even if a query were built wrong, the enforcement layer underneath it still holds.
Data lifecycle
Tenant offboarding includes a scheduled data-purge path for GDPR-style deletion requests — not a manual, easy-to-forget cleanup script.
Need full infrastructure isolation — a dedicated database, VPC, or on-prem deployment for a single tenant, not just logical isolation in a shared stack? That's part of the Dedicated Enterprise engagement — see Security & Trust.