mirror of https://github.com/abpframework/abp.git
committed by
GitHub
9 changed files with 740 additions and 2 deletions
@ -0,0 +1,148 @@ |
|||
Multi-tenancy sounds simple until the first data leak, migration issue, or noisy-neighbor incident shows up. The hard part is not adding a `TenantId` column. The hard part is enforcing isolation consistently across authentication, data access, caching, logging, and operations. |
|||
|
|||
If you are building with ABP, the good news is that multi-tenancy is already a first-class concept. You still need to choose the right architecture and apply a few guardrails, but ABP removes much of the plumbing. |
|||
|
|||
## Start with the right tenancy model |
|||
|
|||
There are three common approaches: |
|||
|
|||
### 1. Shared database, shared schema |
|||
All tenants use the same tables, usually separated by a `TenantId` column. |
|||
|
|||
Use this when: |
|||
- You want the simplest setup |
|||
- Cost matters more than strict isolation |
|||
- You expect many small tenants |
|||
|
|||
Watch out for: |
|||
- Missing tenant filters |
|||
- Noisy-neighbor performance issues |
|||
- Harder compliance stories |
|||
|
|||
### 2. Shared database, separate schemas |
|||
Each tenant gets its own schema in the same database. |
|||
|
|||
Use this when: |
|||
- You need better isolation than shared tables |
|||
- Your database supports schema-based separation well |
|||
|
|||
Watch out for: |
|||
- More complex migrations |
|||
- Operational overhead as tenant count grows |
|||
|
|||
### 3. Database per tenant |
|||
Each tenant gets its own database. |
|||
|
|||
Use this when: |
|||
- You need strong isolation |
|||
- Compliance or data residency matters |
|||
- Some tenants pay for premium isolation |
|||
|
|||
Watch out for: |
|||
- Higher cost |
|||
- More deployment and migration work |
|||
- More monitoring complexity |
|||
|
|||
In practice, many SaaS products start with shared schema and move large tenants to dedicated databases later. |
|||
|
|||
## How ABP helps |
|||
|
|||
ABP has built-in multi-tenancy support based on the current tenant context. The usual flow is: |
|||
|
|||
- Resolve the current tenant from subdomain, domain, header, or user context |
|||
- Store tenant information in the current unit of work |
|||
- Automatically filter tenant-owned entities |
|||
- Optionally use separate connection strings per tenant |
|||
|
|||
For tenant-owned entities, implement ABP's multi-tenant contract so the framework can apply filtering automatically. |
|||
|
|||
```csharp |
|||
public class Product : FullAuditedAggregateRoot<Guid>, IMultiTenant |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string Name { get; set; } |
|||
|
|||
protected Product() {} |
|||
|
|||
public Product(Guid id, string name, Guid? tenantId) |
|||
: base(id) |
|||
{ |
|||
Name = name; |
|||
TenantId = tenantId; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
That gives you a strong default for shared-schema applications. |
|||
|
|||
## Tenant resolution and authentication |
|||
|
|||
A multi-tenant app must know who the tenant is before it touches business data. |
|||
|
|||
Common strategies: |
|||
- Subdomain: `tenant1.myapp.com` |
|||
- Custom domain: `app.customer.com` |
|||
- Header: useful for APIs |
|||
- Login-time selection: common in back-office apps |
|||
|
|||
In ABP, tenant resolution can be configured through contributors. Keep it predictable and avoid mixing too many strategies unless you really need them. |
|||
|
|||
Also make sure: |
|||
- Tenant ID is included securely in claims when appropriate |
|||
- Authorization is scoped per tenant |
|||
- Admin users cannot accidentally cross tenant boundaries |
|||
|
|||
## Data isolation rules that matter |
|||
|
|||
No matter which model you choose, these rules are worth treating as non-negotiable: |
|||
|
|||
- Always propagate tenant context through application and background jobs |
|||
- Include tenant ID in logs and traces |
|||
- Index `TenantId` in shared-schema tables |
|||
- Add rate limits or quotas for heavy tenants |
|||
- Encrypt data at rest and in transit |
|||
|
|||
If you use shared schema, database-level protections such as row-level security can add another safety net. Application filters are good. Defense in depth is better. |
|||
|
|||
## Separate database per tenant in ABP |
|||
|
|||
ABP also supports tenant-specific connection strings. That makes database-per-tenant possible without rewriting your application model. |
|||
|
|||
Typical setup: |
|||
- Keep host data in the main database |
|||
- Store tenant connection strings in tenant management |
|||
- Resolve the current tenant |
|||
- Let ABP switch to the tenant database automatically |
|||
|
|||
This is a good fit when you want one codebase but stronger tenant isolation. |
|||
|
|||
## When to use and when not to use |
|||
|
|||
### Use multi-tenancy when |
|||
- You are building a SaaS product for multiple customers |
|||
- Tenants need isolated users, roles, and data |
|||
- You want one platform with centralized deployment |
|||
|
|||
### Do not use it when |
|||
- You only have one customer environment |
|||
- Every customer requires fully custom code and infrastructure |
|||
- Your team is not ready to operate tenant-aware monitoring, migrations, and support |
|||
|
|||
## Common mistakes |
|||
|
|||
A few mistakes show up repeatedly: |
|||
|
|||
- Adding multi-tenancy too late |
|||
- Relying only on UI-level filtering |
|||
- Writing tenant-specific `if` statements everywhere instead of configuration or features |
|||
- Forgetting tenant context in background workers and integration events |
|||
- Having no migration path from shared to dedicated databases |
|||
|
|||
ABP helps with the framework side, but architecture decisions are still yours. |
|||
|
|||
## TL;DR |
|||
|
|||
- Start with shared schema unless compliance or isolation requirements push you further. |
|||
- In ABP, use `IMultiTenant`, tenant resolution, and built-in data filters. |
|||
- For stronger isolation, use tenant-specific connection strings and move to database per tenant. |
|||
- Always treat tenant context, logging, indexing, and authorization as core design concerns. |
|||
|
After Width: | Height: | Size: 1.6 MiB |
@ -0,0 +1,581 @@ |
|||
Multi-tenancy sounds simple at first: one application, many customers. In practice, it changes how you design data access, authentication, configuration, monitoring, migrations, and even support workflows. |
|||
|
|||
If you get it right, you can serve many organizations efficiently from one platform. If you get it wrong, you risk the worst kind of bug in SaaS: one tenant seeing another tenant's data. |
|||
|
|||
This article explains how to implement multi-tenancy in a practical way, starting from the core patterns and ending with an ABP-based implementation approach. The goal is not just to define multi-tenancy, but to help you choose the right model and avoid the mistakes that usually show up after launch. |
|||
|
|||
## What multi-tenancy actually means |
|||
|
|||
A tenant is usually a customer organization, company, school, or business unit using your application. Multi-tenancy means multiple tenants use the same application platform while remaining isolated from each other. |
|||
|
|||
That isolation can include: |
|||
|
|||
- Data isolation |
|||
- Authentication and authorization boundaries |
|||
- Feature differences per tenant |
|||
- Performance protection |
|||
- Audit and compliance boundaries |
|||
|
|||
It is useful to separate two ideas that are often mixed up: |
|||
|
|||
- **Multi-tenant application**: one application instance serves many tenants |
|||
- **Multi-instance deployment**: each customer gets a separate deployment |
|||
|
|||
Multi-instance is simpler from an isolation perspective, but more expensive to operate. Multi-tenancy is usually more efficient, but it requires stronger architectural discipline. |
|||
|
|||
## Choose your data isolation model first |
|||
|
|||
Most multi-tenancy decisions become easier once you choose the data isolation model. There are three common patterns. |
|||
|
|||
### 1. Shared database, shared schema |
|||
|
|||
This is the most common starting point. All tenants use the same tables, and each tenant-owned row includes a `TenantId`. |
|||
|
|||
Example: |
|||
|
|||
```sql |
|||
Orders |
|||
- Id |
|||
- TenantId |
|||
- CustomerId |
|||
- TotalAmount |
|||
- CreationTime |
|||
``` |
|||
|
|||
Every query must be tenant-aware. |
|||
|
|||
**Pros** |
|||
|
|||
- Lowest infrastructure cost |
|||
- Simplest provisioning |
|||
- One migration path |
|||
- Fastest way to launch |
|||
|
|||
**Cons** |
|||
|
|||
- Highest risk of accidental data leakage if tenant filtering is missed |
|||
- Noisy neighbor problems are more likely |
|||
- Harder to isolate performance-heavy tenants |
|||
- Compliance requirements may rule it out |
|||
|
|||
**Best for** |
|||
|
|||
- MVPs |
|||
- Early-stage SaaS products |
|||
- Products with many small tenants |
|||
- Teams that want operational simplicity first |
|||
|
|||
### 2. Shared database, separate schema per tenant |
|||
|
|||
In this model, tenants share the same database server, but each tenant has its own schema. |
|||
|
|||
For example: |
|||
|
|||
- `tenant_a.Orders` |
|||
- `tenant_b.Orders` |
|||
|
|||
**Pros** |
|||
|
|||
- Better separation than shared schema |
|||
- Easier tenant-specific backup and restore |
|||
- Lower leakage risk than row-level isolation alone |
|||
|
|||
**Cons** |
|||
|
|||
- Migrations become harder |
|||
- Schema drift becomes a real risk |
|||
- Managing hundreds of schemas gets messy |
|||
- Connection and routing logic becomes more complex |
|||
|
|||
**Best for** |
|||
|
|||
- Moderate compliance needs |
|||
- Tens to a few hundreds of tenants |
|||
- Teams that need more isolation without going full database-per-tenant |
|||
|
|||
### 3. Database per tenant |
|||
|
|||
Each tenant gets its own database, and sometimes even its own server. |
|||
|
|||
**Pros** |
|||
|
|||
- Strongest isolation |
|||
- Easier compliance story |
|||
- Easier tenant-specific backup, restore, and residency rules |
|||
- Better performance isolation |
|||
- Easier to support premium tenants with custom SLAs |
|||
|
|||
**Cons** |
|||
|
|||
- Higher cost |
|||
- More provisioning automation required |
|||
- More complex migrations and monitoring |
|||
- Operational overhead grows quickly |
|||
|
|||
**Best for** |
|||
|
|||
- Enterprise SaaS |
|||
- Regulated industries |
|||
- High-value tenants |
|||
- Cases where data residency or strict isolation matters |
|||
|
|||
## How to choose the right pattern |
|||
|
|||
There is no universally correct model. The right choice depends on trade-offs. |
|||
|
|||
Use these questions to decide: |
|||
|
|||
- How many tenants do you expect in 6 to 24 months? |
|||
- Do you have compliance requirements like HIPAA, GDPR residency constraints, or strict audit demands? |
|||
- How much tenant customization do you need? |
|||
- Can your team operate many databases reliably? |
|||
- What happens if one tenant runs expensive reports all day? |
|||
- Do you need tenant-specific backup and restore? |
|||
|
|||
A practical rule: |
|||
|
|||
- Start with **shared schema** if speed and cost matter most |
|||
- Use **database per tenant** if isolation and compliance matter most |
|||
- Use a **hybrid model** if your tenant base is mixed |
|||
|
|||
A hybrid model is common in real SaaS systems: |
|||
|
|||
- Small tenants live in shared infrastructure |
|||
- Large enterprise tenants get dedicated databases |
|||
|
|||
That gives you a better cost curve without forcing every customer into the most expensive setup. |
|||
|
|||
## The core building blocks of a multi-tenant system |
|||
|
|||
Regardless of the storage model, most implementations need the same building blocks. |
|||
|
|||
### Tenant resolution |
|||
|
|||
Before your application can isolate anything, it must know which tenant the request belongs to. |
|||
|
|||
Common tenant resolution strategies: |
|||
|
|||
- Subdomain: `acme.yourapp.com` |
|||
- Custom domain: `portal.acme.com` |
|||
- Header: `X-Tenant-Id` |
|||
- JWT claim or token metadata |
|||
- URL segment: `/t/acme/orders` |
|||
|
|||
Subdomain-based resolution is usually the cleanest for SaaS products. Header-based resolution is common for internal APIs but easier to misuse if not protected. |
|||
|
|||
The important part is consistency. Resolve the tenant early in the request pipeline and make it available everywhere else. |
|||
|
|||
### Tenant context |
|||
|
|||
Once resolved, store the active tenant in a tenant context that application services, repositories, caches, and logs can access. |
|||
|
|||
Typical tenant context data includes: |
|||
|
|||
- Tenant ID |
|||
- Tenant name or slug |
|||
- Connection string or database mapping |
|||
- Enabled features |
|||
- Region or residency info |
|||
|
|||
### Data filtering |
|||
|
|||
If you use shared tables, tenant filtering must be automatic. Do not rely on developers remembering to add `where TenantId == currentTenantId` in every query. |
|||
|
|||
This is where frameworks matter. ABP helps a lot here because it has built-in multi-tenancy support and data filters for tenant-aware entities. |
|||
|
|||
### Tenant-aware configuration |
|||
|
|||
Sooner or later, tenants will ask for differences: |
|||
|
|||
- Feature A enabled only for premium plans |
|||
- Different email templates |
|||
- Different password policies |
|||
- Different integrations |
|||
- Different branding |
|||
|
|||
Do not solve this with `if (tenant == ...)` scattered across the codebase. |
|||
|
|||
Use: |
|||
|
|||
- Feature flags |
|||
- Tenant settings |
|||
- Edition or plan-based configuration |
|||
- Modular integration points |
|||
|
|||
### Tenant-aware logging and auditing |
|||
|
|||
Every log entry and audit event should include tenant context where possible. |
|||
|
|||
That makes it much easier to answer questions like: |
|||
|
|||
- Which tenant triggered this error? |
|||
- Which tenant is causing high database load? |
|||
- Who accessed this record? |
|||
- Which tenant had failed background jobs? |
|||
|
|||
## Implementing multi-tenancy in ASP.NET Core |
|||
|
|||
If you build multi-tenancy manually in ASP.NET Core, the implementation usually follows this flow: |
|||
|
|||
1. Resolve tenant from the request |
|||
2. Load tenant metadata from a tenant store |
|||
3. Set current tenant context |
|||
4. Route database access based on tenant |
|||
5. Apply tenant filters automatically |
|||
6. Include tenant info in logs, cache keys, and background jobs |
|||
|
|||
A minimal tenant resolver middleware might look like this: |
|||
|
|||
```csharp |
|||
public class TenantResolutionMiddleware |
|||
{ |
|||
private readonly RequestDelegate _next; |
|||
|
|||
public TenantResolutionMiddleware(RequestDelegate next) |
|||
{ |
|||
_next = next; |
|||
} |
|||
|
|||
public async Task InvokeAsync(HttpContext context, ICurrentTenantAccessor tenantAccessor) |
|||
{ |
|||
var host = context.Request.Host.Host; |
|||
var subdomain = host.Split('.').FirstOrDefault(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(subdomain) && subdomain != "www") |
|||
{ |
|||
tenantAccessor.CurrentTenantId = await ResolveTenantIdAsync(subdomain); |
|||
} |
|||
|
|||
await _next(context); |
|||
} |
|||
|
|||
private Task<Guid?> ResolveTenantIdAsync(string subdomain) |
|||
{ |
|||
return Task.FromResult<Guid?>(null); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
That is only the beginning. The hard part is making the rest of the application consistently tenant-aware. |
|||
|
|||
For example, your repository layer must avoid this kind of bug: |
|||
|
|||
```csharp |
|||
var orders = await _dbContext.Orders |
|||
.Where(x => x.Status == OrderStatus.Pending) |
|||
.ToListAsync(); |
|||
``` |
|||
|
|||
In a shared-schema model, that query is dangerous because it ignores tenant isolation. |
|||
|
|||
Safer approaches include: |
|||
|
|||
- Global query filters |
|||
- Tenant-aware repositories |
|||
- Row-level security at the database level |
|||
- Framework-level multi-tenancy abstractions |
|||
|
|||
## Implementing multi-tenancy with ABP |
|||
|
|||
ABP is a strong fit for multi-tenant business applications because multi-tenancy is built into the framework rather than bolted on later. |
|||
|
|||
ABP gives you several useful pieces out of the box: |
|||
|
|||
- Tenant resolution pipeline |
|||
- `ICurrentTenant` abstraction |
|||
- Multi-tenant entity support via `IMultiTenant` |
|||
- Data filters for tenant isolation |
|||
- Tenant management module |
|||
- Per-tenant connection string support |
|||
- Feature and setting systems |
|||
|
|||
### Tenant-aware entities |
|||
|
|||
In ABP, tenant-owned entities typically implement `IMultiTenant`. |
|||
|
|||
```csharp |
|||
using System; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
public class Product : IMultiTenant |
|||
{ |
|||
public Guid Id { get; set; } |
|||
public Guid? TenantId { get; set; } |
|||
public string Name { get; set; } = string.Empty; |
|||
public decimal Price { get; set; } |
|||
} |
|||
``` |
|||
|
|||
This matters because ABP can automatically apply tenant filters for entities that belong to a tenant. |
|||
|
|||
### Accessing the current tenant |
|||
|
|||
ABP exposes the current tenant through `ICurrentTenant`. |
|||
|
|||
```csharp |
|||
public class ProductAppService : ApplicationService |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
private readonly ICurrentTenant _currentTenant; |
|||
|
|||
public ProductAppService( |
|||
IRepository<Product, Guid> productRepository, |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
_productRepository = productRepository; |
|||
_currentTenant = currentTenant; |
|||
} |
|||
|
|||
public async Task<List<ProductDto>> GetListAsync() |
|||
{ |
|||
var products = await _productRepository.GetListAsync(); |
|||
return ObjectMapper.Map<List<Product>, List<ProductDto>>(products); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
In a tenant context, ABP automatically scopes the repository query to the current tenant for multi-tenant entities. |
|||
|
|||
That is exactly the kind of default you want in a multi-tenant system: safe behavior unless you intentionally opt out. |
|||
|
|||
### Switching tenant context |
|||
|
|||
Background jobs, admin tools, and migration workflows sometimes need to operate in a specific tenant context. |
|||
|
|||
ABP supports this with `ICurrentTenant.Change(...)`. |
|||
|
|||
```csharp |
|||
using (_currentTenant.Change(tenantId)) |
|||
{ |
|||
var count = await _productRepository.GetCountAsync(); |
|||
} |
|||
``` |
|||
|
|||
This is useful, but it should be used carefully. Tenant context switching is powerful and easy to abuse if you do not keep boundaries clear. |
|||
|
|||
### Per-tenant connection strings |
|||
|
|||
If you choose database-per-tenant, ABP supports tenant-specific connection strings. That lets you keep the same application code while routing different tenants to different databases. |
|||
|
|||
This is one of the biggest practical advantages of using ABP for multi-tenancy: you can start simple and evolve toward stronger isolation without rewriting your entire application model. |
|||
|
|||
## Shared schema vs database per tenant in ABP |
|||
|
|||
ABP supports both host and tenant concepts, which makes it flexible enough for different SaaS stages. |
|||
|
|||
### Shared schema with ABP |
|||
|
|||
This is usually the easiest starting point. |
|||
|
|||
Use it when: |
|||
|
|||
- You want fast delivery |
|||
- Your tenants are relatively small |
|||
- Compliance requirements are moderate |
|||
- Your team wants simpler operations |
|||
|
|||
What to watch: |
|||
|
|||
- Ensure all tenant-owned entities implement `IMultiTenant` |
|||
- Be careful with raw SQL and custom queries |
|||
- Include `TenantId` in indexes where needed |
|||
- Test cross-tenant isolation aggressively |
|||
|
|||
### Database per tenant with ABP |
|||
|
|||
Use it when: |
|||
|
|||
- You need stronger isolation |
|||
- You have enterprise customers |
|||
- You need tenant-specific restore or residency |
|||
- You expect some tenants to be much larger than others |
|||
|
|||
What to watch: |
|||
|
|||
- Automate provisioning |
|||
- Automate migrations across tenant databases |
|||
- Monitor connection usage and migration failures |
|||
- Keep tenant metadata accurate and centralized |
|||
|
|||
## Migrations and schema management |
|||
|
|||
This is where many multi-tenant systems become painful. |
|||
|
|||
With shared schema, migrations are straightforward: apply once. |
|||
|
|||
With schema-per-tenant or database-per-tenant, you need orchestration. |
|||
|
|||
A practical migration workflow includes: |
|||
|
|||
- Track tenant inventory centrally |
|||
- Apply migrations in batches |
|||
- Record migration status per tenant |
|||
- Retry safely on failure |
|||
- Alert on drift |
|||
- Avoid manual one-off fixes unless documented and automated later |
|||
|
|||
If you have 5 tenants, manual migration might feel acceptable. If you have 500, it becomes an operational risk. |
|||
|
|||
For ABP-based systems, treat tenant database migration as a first-class operational workflow, not an afterthought. |
|||
|
|||
## Security pitfalls to avoid |
|||
|
|||
Multi-tenancy failures are usually not caused by the concept itself. They are caused by inconsistent enforcement. |
|||
|
|||
The most common mistakes are: |
|||
|
|||
### 1. Missing tenant filters |
|||
|
|||
This is the classic bug. One query forgets tenant scoping, and data leaks. |
|||
|
|||
Reduce the risk with: |
|||
|
|||
- Framework-level filters |
|||
- Repository abstractions |
|||
- Database row-level security where appropriate |
|||
- Integration tests that verify isolation |
|||
|
|||
### 2. Unsafe admin features |
|||
|
|||
Support tools, exports, reporting endpoints, and background jobs often bypass normal application flows. That makes them common leakage points. |
|||
|
|||
Treat admin code as high-risk code. |
|||
|
|||
### 3. Tenant context lost in async or background processing |
|||
|
|||
If a background job processes tenant data, it must explicitly carry tenant context. |
|||
|
|||
Do not assume the request context still exists. |
|||
|
|||
### 4. Shared cache keys |
|||
|
|||
If your cache key is just `product-list`, you already have a bug. |
|||
|
|||
Use tenant-aware cache keys such as: |
|||
|
|||
```text |
|||
tenant:{tenantId}:product-list |
|||
``` |
|||
|
|||
### 5. Weak audit trails |
|||
|
|||
When something goes wrong, you need to know: |
|||
|
|||
- Which tenant was affected |
|||
- Which user triggered the action |
|||
- Which service handled it |
|||
- Which data store was involved |
|||
|
|||
Without tenant-aware auditing, incident response becomes much harder. |
|||
|
|||
## Performance and noisy neighbor control |
|||
|
|||
Shared infrastructure saves money, but it also creates contention. |
|||
|
|||
A single tenant can hurt others through: |
|||
|
|||
- Expensive reports |
|||
- Large imports |
|||
- Chatty integrations |
|||
- Poorly indexed queries |
|||
- Background jobs running at the wrong time |
|||
|
|||
Mitigations include: |
|||
|
|||
- Indexing by `TenantId` |
|||
- Query timeouts |
|||
- Rate limiting per tenant |
|||
- Queue isolation for heavy jobs |
|||
- Read replicas for reporting |
|||
- Partitioning large tables |
|||
- Moving heavy tenants to dedicated databases |
|||
|
|||
This is why hybrid multi-tenancy is so common. It gives you an escape hatch when one tenant outgrows the shared model. |
|||
|
|||
## A practical implementation plan |
|||
|
|||
If you are building a new SaaS product, this is a sensible rollout path. |
|||
|
|||
### Phase 1: Start with shared schema |
|||
|
|||
- Resolve tenant from subdomain or domain |
|||
- Store tenant metadata centrally |
|||
- Use framework-level tenant filters |
|||
- Make all tenant-owned entities explicit |
|||
- Add tenant-aware logging, caching, and auditing |
|||
|
|||
For ABP, this is usually the fastest path because the framework already supports the core abstractions. |
|||
|
|||
### Phase 2: Add tenant configuration and feature management |
|||
|
|||
- Per-tenant settings |
|||
- Feature flags |
|||
- Plan-based capabilities |
|||
- Branding and integration settings |
|||
|
|||
This keeps customization manageable without branching the codebase. |
|||
|
|||
### Phase 3: Prepare for tenant mobility |
|||
|
|||
Even if you start with shared schema, design for future migration. |
|||
|
|||
That means: |
|||
|
|||
- Stable tenant IDs |
|||
- Export/import or replication strategy |
|||
- Clear ownership boundaries in data |
|||
- No hidden cross-tenant joins |
|||
|
|||
### Phase 4: Move selected tenants to dedicated databases |
|||
|
|||
Use an expand-backfill-contract approach: |
|||
|
|||
- **Expand**: create the target database |
|||
- **Backfill**: copy tenant data |
|||
- **Dual-write**: temporarily write to both stores if needed |
|||
- **Contract**: switch traffic and retire the old location |
|||
|
|||
This is much safer than a big-bang migration. |
|||
|
|||
## When to use multi-tenancy and when not to |
|||
|
|||
### When to use multi-tenancy |
|||
|
|||
- You are building a SaaS platform for many organizations |
|||
- Tenants share most application behavior |
|||
- Operational efficiency matters |
|||
- You want centralized upgrades and deployment |
|||
- You need a scalable commercial model |
|||
|
|||
### When NOT to use multi-tenancy |
|||
|
|||
- Every customer needs deep infrastructure-level customization |
|||
- Compliance requires strict physical isolation from day one |
|||
- Your team cannot support the operational complexity safely |
|||
- You only have a few large customers and each behaves like a separate product |
|||
|
|||
In those cases, multi-instance deployment may be the better choice. |
|||
|
|||
## Final recommendations |
|||
|
|||
If you are unsure where to start, start simpler than you think, but not sloppier than you can afford. |
|||
|
|||
That usually means: |
|||
|
|||
- Shared schema first |
|||
- Strong tenant resolution |
|||
- Automatic tenant filtering |
|||
- Tenant-aware logs, cache, and jobs |
|||
- A migration path to dedicated databases later |
|||
|
|||
ABP is especially useful here because it gives you the right primitives early: current tenant context, tenant-aware entities, filters, settings, and connection string support. That reduces the amount of custom plumbing you need to build and maintain. |
|||
|
|||
The biggest mistake is not choosing the wrong pattern. It is choosing a pattern without planning how tenant isolation will be enforced everywhere. |
|||
|
|||
## TL;DR |
|||
|
|||
- Multi-tenancy is mostly about safe isolation of data, behavior, and operations across customers. |
|||
- Shared schema is the easiest starting point; database-per-tenant gives the strongest isolation but adds operational cost. |
|||
- In ASP.NET Core, tenant resolution, tenant context, automatic filtering, and tenant-aware caching/logging are essential. |
|||
- ABP makes multi-tenancy easier with `ICurrentTenant`, `IMultiTenant`, data filters, tenant management, and per-tenant connection strings. |
|||
- Design for evolution early so large tenants can move to dedicated databases without a painful rewrite. |
|||
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 897 KiB |
|
After Width: | Height: | Size: 946 KiB |
Loading…
Reference in new issue