Single-user apps are simple.
But real products eventually need:
- multiple accounts
- team workspaces
- organization switching
- sandbox vs production data
- different permission scopes
Without proper architecture, this leads to:
- mixed user data
- cache corruption
- wrong account actions
- impossible bug reports
- “why is this showing someone else’s data?”
This post shows how to design a multi-tenant architecture in SwiftUI that:
- isolates user data
- supports account switching
- prevents cross-tenant bugs
- scales to teams and organizations
🧠 The Core Principle
Every piece of data must belong to a tenant.
If you don’t know which account owns a piece of state, your architecture is already unsafe.
🧱 1. Define the Tenant Model
A tenant represents a data boundary.
struct Tenant {
let id: UUID
let name: String
let role: Role
}
enum Role {
case personal
case member
case admin
}
This could represent:
- a user account
- a workspace
- an organization
- a project context
🧬 2. Tenant-Aware App State
Never store global user data.
Bad:
class AppState {
var user: User
var projects: [Project]
}
Correct:
class AppState {
var activeTenant: Tenant
var tenantStores: [UUID: TenantStore]
}
Each tenant has its own isolated state.
📦 3. Tenant Store
final class TenantStore: ObservableObject {
@Published var user: User
@Published var projects: [Project]
@Published var settings: TenantSettings
}
This ensures:
- no cross-tenant leaks
- clean switching
- independent lifecycles
🔁 4. Account Switching Architecture
Switching tenants should:
- Save current state
- Tear down feature stores
- Activate new tenant store
- Rebuild navigation
Example:
func switchTenant(to tenant: Tenant) {
activeTenant = tenant
currentStore = tenantStores[tenant.id]
}
Never reuse old state across tenants.
🧭 5. Tenant-Scoped Services
Services must be tenant-aware.
Bad:
apiClient.fetchProjects()
Correct:
apiClient.fetchProjects(for: tenantID)
Or inject a tenant-scoped client:
TenantAPIClient(tenantID: tenant.id)
Every request must carry tenant context.
🧠 6. Storage Isolation
Persist data per tenant:
/Storage
/tenant_1
projects.db
/tenant_2
projects.db
Or use namespaced keys:
"tenant_\(id)_settings"
Never share storage across tenants.
🧪 7. Testing Multi-Tenant Flows
You must test:
- switching accounts
- logging out and back in
- background sync per tenant
- cross-tenant isolation
- permission differences
Multi-tenant bugs are subtle and dangerous.
🔐 8. Permission & Role Awareness
Tenants may have different roles.
Example:
if tenant.role == .admin {
showAdminPanel()
}
Never assume capabilities across tenants.
⚠️ 9. Common Multi-Tenant Anti-Patterns
Avoid:
- global singletons for user data
- shared caches across tenants
- not clearing state on switch
- hardcoded user assumptions
- mixing personal and workspace data
These cause the worst production bugs.
🧠 Mental Model
Think:
Tenant
→ Tenant Store
→ Tenant Services
→ Tenant Storage
→ Tenant UI
Not:
“There’s just one user”
🚀 Final Thoughts
A proper multi-tenant architecture gives you:
- safe account switching
- clean workspace logic
- better permission handling
- fewer data leaks
- scalable product design
If your app ever needs teams or organizations,
multi-tenant architecture is not optional.
Top comments (0)