- A multi-tenant web applications respond differently depending on how it is addressed by the tenant.
- It helps a single code base to serve many different tenants.
- A tenant has a specific identity, and an application that responds to a particular tenant behaves differently from another tenant.
- Specifically, one or more of these may change:
- When it comes to the user interface, we may want to do different things:
- Show some content conditionally, for a specific tenant or set of tenants
- Show a totally different view for a specific tenant.
- A tenant may have different interface design, logo, or images.
- Data (including tenant and user configuration parameters). It
- Data should be easy to understand and isolated.
- Tenant ABC data should not be visible XYZ and vice-versa.
- Behavior: Changes in behavior or functionality are also possible, when a particular tenant has a different feature set than others.
- In the following implementation, the
ITenantService
will serve as the entry point for the multi-tenant functionality:
public interface ITenantService
{
string GetCurrentTenant();
}
public sealed class TenantService : ITenantService
{
private readonly HttpContext _httpContext;
private readonly ITenantIdentificationService _tenantService;
public TenantService(IHttpContextAccessor accessor, ITenantIdentificationService tenantService)
{
_httpContext = accessor.HttpContext;
_tenantService = tenantService;
}
public string GetCurrentTenant() => _tenantService.GetCurrentTenant(_httpContext);
}
Tenant Identification Strategies
- Host Header: the tenant will be inferred by the host header sent by browser when accessing the application.
- This is the most robust strategy and probably the most widely used.
- Query String: a query string parameter will be used to distinguish between the different tenants.
- This is probably a bad practice as it can be intercepted by users.
- Source IP: you may want that requests originating from the same IPs get the same tenant all the time.
- Define
ITenantIdentificationService
an interface that can provide the required information.
Database Access Strategies
When it comes to retrieving different values from a relational database, we have essentially three options:
- Different Schemas: we use the same database for all the data, but we use different schemas (and tables) for each tenant.
- Using different schemas allows us to share the same database instance, but essentially we are duplicating all (or at least some) tables
- Different Databases: we use a different database for each tenant.
- Using different databases requires extra maintenance, like, backups, managing security, etc, but provides better encapsulation
- Filter Columns: we use the same database and tables for all tenants, but different records for each tenant, filtered by some column:
- Using a filtering column we only have one table for all tenants, but it may be possible, by sending custom SQL, to bypass the tenant restriction
Other Requirements
- Caching
- Logging
- Profiling