Enterprise Developer Gap Analysis
This page is a candid assessment of what the Multi-Tenancy module provides today versus what a developer building an enterprise-grade, multi-tenant application with existing authentication infrastructure would typically expect. Use it to make informed architecture decisions before committing to this module in production.
The validated core and shipped integration packages are stable for the documented pooled shared-table model. The items below are still real adoption boundaries, but they are platform and rollout concerns, not evidence that the documented core is still in preview.
What the module does well
| Capability | Status |
|---|---|
| Resolving tenant identity from JWT claims, headers, route values, subdomain, or membership | Fully implemented |
Membership-validated tenant selection (MembershipValidatedTenantResolutionStrategy) | Fully implemented |
| Enforcing tenant isolation on authenticated requests (403) | Fully implemented |
| Per-tenant rate limiting (in-process) | Fully implemented |
Pluggable tenant rate limiter abstraction (ITenantRateLimiter) | Fully implemented |
EF Core write enforcement — sets TenantId on new entities, blocks cross-tenant mutations | Fully implemented |
| EF Core read-isolation guard (throws when tenant-owned entities have no filter provider) | Fully implemented |
Super-admin bypass policy hook (ITenantBypassPolicy) | Fully implemented |
Membership-store caching decorator (AddPrimusMultiTenancyMembershipCaching) | Fully implemented |
Distributed membership caching (AddPrimusMultiTenancyDistributedMembershipCaching) | Fully implemented |
Distributed rate limiting (AddPrimusMultiTenancyDistributedRateLimiting) | Fully implemented |
Tenant lifecycle helpers (Suspend, Activate, SoftDelete) | Fully implemented |
Tenant hard-delete endpoint + store operation (DeleteTenantAsync) | Fully implemented |
Tenant offboarding data-retention contract (ITenantDataRetentionService) | Fully implemented |
| Tenant list/search/summary management endpoints | Fully implemented |
Structured audit sink (ITenantAuditSink) with 18 event types | Fully implemented |
Tenant provisioning hook (ITenantProvisioningHandler) | Fully implemented |
Background context propagation (TenantExecutionContext + AmbientTenantContextAccessor) | Fully implemented |
Tier-based rate-limiting (PerTierRateLimits) | Fully implemented |
Non-relational resource isolation policy contract (ITenantResourceIsolationPolicy) | Fully implemented |
| In-Memory store for local development and unit tests | Fully implemented |
RBAC scope composition helper (ResolveTenantIdForRbac()) | Fully implemented |
Known gaps
1. Distributed rate limiting still depends on distributed cache registration
Impact: High for multi-instance deployments.
AddPrimusMultiTenancyAspNetCore() auto-selects DistributedTenantRateLimiter only when IDistributedCache is registered. Without distributed cache registration, the fallback is still in-memory and process-local.
Resolution: Register IDistributedCache (Redis, SQL Server cache, etc.), and optionally call AddPrimusMultiTenancyDistributedRateLimiting(...) to enforce explicit distributed behavior.
builder.Services.AddPrimusMultiTenancyAspNetCore();
builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");
builder.Services.AddPrimusMultiTenancyDistributedRateLimiting();
For fully custom quota logic, implement ITenantRateLimiter and register it with AddSingleton<ITenantRateLimiter, YourLimiter>().
2. No admin UI or management dashboard
Impact: Low to medium, depending on team.
The module includes endpoints for list/search/summary/get/upsert/activate/suspend/soft-delete/hard-delete operations backed by ITenantStore. It still does not ship a first-party admin UI.
Workaround: Use built-in endpoints for baseline workflows, or build application-level endpoints for custom validation, workflows, and governance.
3. Membership caching is still in-process unless distributed cache is enabled
Impact: Medium for high-traffic APIs.
AddPrimusMultiTenancyMembershipCaching(...) provides an in-process cache wrapper.
For multi-instance deployments, use AddPrimusMultiTenancyDistributedMembershipCaching(...)
to back membership caching with IDistributedCache so cache entries can be shared across replicas.
Workaround: If you need custom serialization/eviction behavior, replace the decorator with your own ITenantMembershipStore implementation.
4. Subdomain resolution still requires consistent host configuration
Impact: Low, but easy to misconfigure.
SubdomainTenantResolutionStrategy reads the first segment of the Host header (e.g., acme from acme.example.com). It now supports development hosts like acme.localhost through SubdomainDevelopmentHosts, but production still requires consistent DNS.
Workaround: Leave EnableSubdomainResolution: false locally and use header- or claim-based resolution during development. Enable subdomain resolution only in environments with proper DNS wildcard records.
5. Isolation scope: pooled shared-table only
Impact: Architectural — depends on compliance and data-residency requirements.
All tenants share the same schema. The TenantId column is the only isolation boundary. This is appropriate for most SaaS workloads but does not satisfy regulations or contractual obligations that require physical data separation (e.g., separate database or schema per customer).
Resolution: This is by design and will not change within this package. For dedicated-DB or schema-per-tenant isolation, integrate an orchestration layer (e.g., Azure Elastic Pools + a custom ITenantStore that provisions per-tenant databases) alongside or instead of this package.
6. No built-in enforcement for file, blob, or search systems
Impact: Medium for applications with unstructured storage.
The module enforces isolation only on EF Core-tracked entities. It now provides ITenantResourceIsolationPolicy to centralize naming/prefix rules for non-relational resources, but platform-specific enforcement remains application-owned.
Workaround: Prefix storage container or index names with {tenantId}- at the application layer, and validate tenantId against the resolved TenantContext before all non-relational operations.
7. Hard-delete data purge orchestration remains application-owned
Impact: Low to medium, depending on data-residency requirements.
The module now supports hard-delete at tenant-record level (DELETE /primus/tenants/{tenantId} and DeleteTenantAsync). Full offboarding purge across related module data and external systems is still not automatic.
Workaround: Implement ITenantDataRetentionService and orchestrate cross-system purge jobs after retention windows.
8. Billing-plan enforcement remains application-owned
Impact: Medium for SaaS monetization.
The package now supports tier-aware rate limits (PerTierRateLimits, TenantTierMetadataKey, DefaultTenantTier) and endpoint feature gating (UsePrimusTenantEntitlements(), RequirePrimusFeature(...), PerTierEntitlements).
Billing integration (invoice state, grace windows, payment retries, and downgrade timing) is still application-owned.
Workaround: Use built-in entitlements for request-time access control, and integrate billing providers to update tier metadata and entitlement sets.
| Gap | Risk | Status |
|---|---|---|
Distributed rate limiting depends on IDistributedCache registration | High (multi-instance) | Register distributed cache, optionally call AddPrimusMultiTenancyDistributedRateLimiting(...) |
| Membership cache is in-process by default | Medium (multi-instance perf) | Use AddPrimusMultiTenancyDistributedMembershipCaching(...) |
| No admin UI | Low–Medium | Workaround: custom Portal integration |
| Subdomain resolution requires DNS discipline | Low | SubdomainDevelopmentHosts improves local workflows; production still needs DNS |
| Pooled shared-table isolation only | Architectural | By design — dedicated-DB and schema-per-tenant are out of scope |
| No built-in blob/search enforcement | Medium | Use ITenantResourceIsolationPolicy + storage-layer enforcement |
| Cross-system hard-delete orchestration | Low–Medium | Implement via ITenantDataRetentionService + purge job |
| Tiered feature entitlements | Low | Built-in via UsePrimusTenantEntitlements() + RequirePrimusFeature(...) + PerTierEntitlements |