Clarity Platform Architecture chevron_right Tenancy Architecture

Tenancy Architecture

Multi-tenancy, single-tenancy, update strategies, isolation models, and the evolution path for scaling the Clarity platform to hundreds of customers.

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:

  1. The Client layer compiled at build time
  2. Database configuration and settings
  3. Kubernetes secrets for credentials

Deployment Unit per Customer

deployed_code

K8s Namespace: acme-prod

terminal Backend Container (.NET 8)
web Frontend Container (Remix)
memory Redis Sidecar
storage Dedicated Database (SQL Server)
folder NFS Storage Mount
lock SSL Certificate + Subdomain

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

settings

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.

conversion_path

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.

extension

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.

info

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

1

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

2

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
3

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

apartment

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

business

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

storefront

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

info

The same Docker image serves all tiers. The difference is deployment topology, not codebase.

policy Data Isolation & Compliance

PCI DSS Compliance by Model

STRONGEST Single-Tenant PCI — each customer is a fully isolated PCI environment
STRONG DB-per-Tenant PCI — payment tokens in separate databases, SAQ-A maintained
UNACCEPTABLE Shared DB PCI — SQL injection could expose all tenants' tokens

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
info

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

1

Connection String Isolation

Application physically cannot access another tenant's DB. Connection string resolved from tenant registry per authenticated request.

2

Tenant Context in Pipeline

Every IPipelineContext includes resolved TenantId. Cross-cutting operations require elevated permissions and are audited.

3

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.

4

JWT Tenant Claim

Authenticated user's JWT contains TenantId claim. Middleware validates token tenant matches resolved tenant. Mismatch → 403 Forbidden.

5

Integration Tests

Tests provision 2+ tenant databases and verify operations in Tenant A's context NEVER return Tenant B's data.

6

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)

check_circle 1. Create K8s namespace
check_circle 2. Provision database
check_circle 3. Create K8s secrets
check_circle 4. Generate deployment YAML
check_circle 5. Deploy via kubectl

Business Config (30-60 min)

check_circle 6. Site Setup Wizard
check_circle 7. Configure connector
check_circle 8. Configure payment provider

Multi-Tenant (DB-per-Tenant)

4 Steps

Infrastructure (~30 sec)

check_circle 1. Create database + run migrations + register tenant

Business Config (30-60 min)

check_circle 2. Site Setup Wizard
check_circle 3. Configure connector
check_circle 4. Configure payment provider

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.
info

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.

help FAQ