Tenancy Architecture
Multi-tenancy, single-tenancy, update strategies, isolation models, and the evolution path for scaling the Clarity platform to hundreds of customers.
On this page
cloud_done Current Architecture
Clarity operates a single-tenant, dedicated-instance model. Each customer receives their own Kubernetes namespace ({{acronym}}-{{env}}), database instance, Docker container set (backend, frontend, Redis sidecar), Client layer with customer-specific Startup.cs and overrides, plugin configuration, NFS storage mount, SSL certificate, and subdomain.
The codebase is shared via Docker image tags — all customers run the same backend and frontend images. Customer-specific behavior is driven by:
- The Client layer compiled at build time
- Database configuration and settings
- Kubernetes secrets for credentials
Deployment Unit per Customer
K8s Namespace: acme-prod
compare Tenancy Model Comparison
| Dimension | Single-Tenant Current | Multi-Tenant Shared DB | Multi-Tenant DB-per-Tenant Recommended |
|---|---|---|---|
| Deployment Unit | 1 K8s namespace + DB per customer | 1 shared app + 1 shared DB | 1 shared app + 1 DB per tenant |
| Data Isolation | COMPLETE — separate everything | ROW-LEVEL — TenantId filter required | DATABASE-LEVEL — strong isolation |
| PCI Compliance | STRONGEST — fully isolated PCI environment | WEAKEST — filter bug could leak tokens | STRONG — separate databases |
| Performance Isolation | COMPLETE — no noisy neighbor | NONE without throttling | PARTIAL — DB isolated, app shared |
| Update Speed | N operations (one per customer) | Single deployment, instant | Single deployment + per-tenant migrations |
| Customization | Full (Client layer + hooks + plugin) | Config + feature flags only | Config + feature flags only |
| Cost at Scale | Highest (dedicated compute per customer) | Lowest (shared everything) | Moderate (shared app, dedicated DBs) |
| Provisioning | ~5 minutes (infra + config) | ~30 seconds (DB + registry) | ~30 seconds (DB + registry) |
tune Customization Without Forking
Layer 1 — Configuration (80%+ of customizations)
The Settings system stores customer-specific preferences: payment provider selection, ERP connector, feature toggles, notification rules, surcharge configuration, and branding/theme. All managed through the admin UI without any code changes.
Layer 2 — Pipeline Hooks (code, non-invasive)
The Client layer registers pre-hooks and post-hooks on any pipeline. Example: a customer needs custom surcharge calculation based on their state's rules — they add a hook on CalculateSurchargePipeline that adjusts the result after default logic runs. Core is untouched. Other customers unaffected.
Layer 3 — Client Plugin (code, fully isolated)
For truly unique requirements, a ClientPlugin is registered last in Startup.cs. It can override any previously registered service, add new entities, create API endpoints, and inject frontend routes. Because it's registered last, it wins any DI conflicts. Because it's in the Client layer, it ships only with that customer's deployment.
The key insight: Client customizations are ADDITIVE. They hook into pipelines and override services — they never modify Core or Plugin source code. When Core or Plugins are updated via submodule pointers, customizations continue to work as long as pipeline interface contracts are maintained.
trending_up Path to Multi-Tenancy
Fleet Management
- check_circle Provisioning automation scripts
- check_circle Central health dashboard across all instances
- check_circle Fleet-wide image update pipeline (canary → staged → full)
- check_circle Centralized logging and monitoring
Immediate ROI, zero architectural risk
Shared Application Tier
- check_circle Tenant resolution middleware (hostname → TenantId → connection string)
- check_circle Scoped DbContext resolved per-request from tenant registry
- check_circle Database-per-tenant with central tenant registry (Redis-cached)
- check_circle Migration runner iterates all tenant databases on deploy
- check_circle Per-tenant rate limiting and resource quotas
Full Platform
- check_circle Tenant Admin Portal for self-service provisioning
- check_circle Tiered plans (Enterprise / Standard / Self-Service)
- check_circle Usage metering and billing integration
- check_circle Template-based onboarding (e.g., “NetSuite + Fortis + Standard Order-to-Cash”)
- check_circle Marketplace for connector and plugin distribution
layers Hybrid Model
Tier 1 — Enterprise
- check Single-tenant, dedicated instance
- check Full customization (Client layer + hooks + ClientPlugin)
- check Own K8s namespace, version pinning
- check Controls own update schedule
Example: Fortune 500 running D365 F&O with 20 custom pipeline hooks
Tier 2 — Standard
- check Multi-tenant, DB-per-tenant
- check Config + feature flags + standard hook patterns
- check Shared application tier, isolated database
- check Receives updates on Clarity's release schedule
Example: Mid-market distributor on NetSuite with standard order-to-cash
Tier 3 — Self-Service
- check Multi-tenant, shared/pooled infrastructure
- check Configuration-only customization
- check Fastest provisioning, lowest cost
Example: Small business using basic invoice payments
The same Docker image serves all tiers. The difference is deployment topology, not codebase.
policy Data Isolation & Compliance
PCI DSS Compliance by Model
SOC 2 Considerations
- check_circle Logical access controls enforced at connection string level
- check_circle Audit trails maintained per-tenant with no cross-contamination
- check_circle Change management tracked per deployment unit
- check_circle Encryption at rest and in transit for all tenant data
Data Residency
- check_circle Single-tenant: database can be provisioned in any cloud region
- check_circle DB-per-tenant: individual databases can be placed in required regions
- check_circle Supports GDPR, CCPA, and industry-specific residency requirements
Breach Blast Radius
- shield Single-tenant: breach affects only one customer's data and infrastructure
- shield DB-per-tenant: application compromise could affect multiple tenants, but database breach is limited to one tenant
- warning Shared DB: any database breach exposes ALL tenants' data
For payments platforms, shared-database multi-tenancy is not recommended. Database-per-tenant provides strong isolation while enabling the operational benefits of a shared application tier.
security Defense-in-Depth Isolation
Connection String Isolation
Application physically cannot access another tenant's DB. Connection string resolved from tenant registry per authenticated request.
Tenant Context in Pipeline
Every IPipelineContext includes resolved TenantId. Cross-cutting operations require elevated permissions and are audited.
EF Core Global Query Filter
Belt-and-suspenders: modelBuilder.Entity<BaseEntity>().HasQueryFilter(e => e.TenantId == _currentTenantId) prevents cross-tenant data even if wrong connection string resolved.
JWT Tenant Claim
Authenticated user's JWT contains TenantId claim. Middleware validates token tenant matches resolved tenant. Mismatch → 403 Forbidden.
Integration Tests
Tests provision 2+ tenant databases and verify operations in Tenant A's context NEVER return Tenant B's data.
Runtime Monitoring
Flags any database query returning data with TenantId different from request's tenant context.
person_add Customer Onboarding
Today (Single-Tenant)
8 Steps
Infrastructure (~5 min)
Business Config (30-60 min)
Multi-Tenant (DB-per-Tenant)
4 Steps
Infrastructure (~30 sec)
Business Config (30-60 min)
Self-Service Vision
Tenant Admin Portal: click “Create Customer” → DB provisioned → migrations run → tenant registered → customer receives login link. Pre-loaded templates for common configurations.
system_update Update & Rollback Strategy
| Scenario | Single-Tenant | Multi-Tenant (DB-per-Tenant) |
|---|---|---|
| Security patch | Fleet pipeline pushes new image to all namespaces. Staggered rollout possible. | Single deployment restart. ALL tenants patched immediately. |
| Feature release | New image + migration per customer. Can be staggered across customers. | Single restart + migration runner iterates tenant DBs. Feature flags for gradual enable. |
| Breaking connector change | Only affected customers updated independently. | Feature flag gates new behavior per tenant. |
| Rollback | Roll back individual customer's image tag. Others unaffected. | Roll back entire application. All tenants revert. Can roll back individual tenant DBs separately. |
| Customer requests delay | Pin their deployment to specific commit hash. | Feature flags disable new behavior for that tenant. |
| Emergency hotfix | Deploy patched image to ONLY their namespace. Zero risk to others. | Deploy to shared app — all tenants receive it. OR promote to single-tenant temporarily. |
This is why the hybrid model is recommended: customers with regulatory constraints or code freeze requirements can use single-tenant (Tier 1) for full version control, while standard customers benefit from instant multi-tenant updates.