1.5 ABP General Introduction-Multi-tenancy
1.5.1 What is multi-tenancy
Wikipedia: "Software multi-tenancy refers to a software architecture where the instance software runs on a single server, but there are multiple tenants." A tenant is a group of software instances that share a common user access to specific permissions. Multitenant architecture, software applications are designed to provide each tenant-specific instance including data, configuration, user management, tenant individual functionality, and non-functional attributes. Multi-tenancy and multi-instance architecture, stand-alone software instances represent different tenants "operations multi-tenancy is typically used to create SAAS (software as a service) applications (cloud computing), and there are some cases: 1.5. More than 2 deployments of multiple databases
This is not actually multi-tenancy, and if you are configuring a separate database and an instance of the application for each customer (tenant), which is deployed in a single server but is available to multiple customers (tenants), we need to ensure that multiple instances of the application do not conflict with the same configuration environment for the system.
The existing design approach is not really for multi-tenant service, its benefits are easier to create, but there are some installation, use and maintenance problems. 1.5.3 Deploying multiple databases individually
Using this approach, we can run an instance of the application on the server. We have a master database that stores data for tenants (for example, tenant names and subdomains) and a single database per tenant. Once we identify the current tenant (for example, from a subdomain or user login information), we can switch to the current tenant's database to perform the operation.
Applications designed in this way can be considered as multi-tenancy to some extent. However, most applications still rely on multi-tenancy.
We should create and maintain their own separate databases for each tenant, including data migrations. If we have a large number of customers and corresponding databases, it will take too much time to migrate the database schema when the application is updated. Of course, we have separated the databases for our tenants, and we can back up their own databases for each tenant. If the tenant needs it, we can migrate the tenant's database to a more powerful server. 1.5.4 A single database deployment
This is a true multitenant architecture, where we deploy only a single instance of the application and only one database on the server. Use Tenantid in each table to isolate information from other tenants.
This benefit is easy to install and maintain, but it is difficult to create such an application. Because you need to prevent tenants from reading and writing information about other tenants. When the user reads the data, the Tenantid filter can be added to filter the data, and the system detects the user's write operation. This is tedious and error prone. ABP can help us automate data filtering.
If we have a lot of tenants and a huge amount of data, this implementation will cause some performance problems. We can use table partitioning or other functions of the database to overcome these problems. 1.5.5 Single-deployment hybrid database
Usually we may want to store the tenant in a separate database, but we may also want to create a detached database for the tenant. For example, we can create separate databases for tenants with huge amounts of data, but other tenants still use the same database. 1.5. More than 6 department-single/Multi/Mixed database
Finally, in order to achieve better performance, high availability and scalability, we may want to deploy our application to multiple servers. These are all methods that depend on the database. Multi-tenancy in the 1.5.7 ABP
The ABP can work on all of the scenarios described above. 1. Turn on multi-tenancy
The default multi-tenancy is disabled, and we need to open it in the preinitialize method of the module as follows:
Configuration.MultiTenancy.IsEnabled = true;
2. Host VS Tenant
First, we define the terms in two multi-tenancy systems:
Tenant: The customer has its own users, roles, permissions, Settings ... and use the application to be completely isolated from other tenants. A multi-tenant application will have one or more tenants. If this is a CRM application, different tenants also have their own accounts, contacts, products, and orders. So when we say a tenant user, we mean that the user owns the tenant.
The Host:host is a singleton (only one host). Host is responsible for creating and managing tenants. Therefore, the host user is independent of the tenant and can control the tenant. 3. Session
The ABP defines the Iabpsession interface to obtain the current user and tenant ID. This interface uses the ID of the multitenant current tenant. Therefore, it can filter data based on the ID of the current tenant.
Here are some rules:
If two user IDs and Tenantid are null, the current user is not logged on to the system. So, we don't know if this is a host user or a tenant user. In this scenario, the user cannot access the authorized content.
User ID (if not empty, Tenantid is empty, then we can know that the current user is a host user.)
User ID (if not empty, Tenantid is not empty, we can know that the current user is a tenant user.)
For more session content, see: Session 4. Determination of the current tenant
Since all tenant users are using the same application, we should have a way to differentiate between the currently requested tenant. The default session (Claimsabpsession) is implemented in a given order using different ways to find the tenant associated with the current request:
1. If the user is already logged in, obtain the tenant ID from the current statement (claims), the only name declared is: http://www.aspnetboilerplate.com/identity/claims/tenantId And the declaration should contain an integer value. If it is not found in the declaration, then the user is assumed to be the host user.
2. If the user is not logged in, it attempts to resolve the tenant ID from tenant resolve contributors (temporarily translated as: Tenant resolution Participant) . There are 3 pre-defined tenant contributors and run in the order given (the first parser that resolves successfully wins):
1. Domaintenantresolvecontributer: Try to parse the tenant name from the URL, typically a domain name or subdomain. In the pre-initialization of the module (preinitialize), you can configure the domain name format (for example:Configuration.Modules.AbpWebCommon (). Multitenancy.domainformat = "{0}.mydomain.com";). If the format of the domain name is "{0}.mydomain.com"and the current requested domain name is:Acme. mydomain.com, then the tenant name is resolved to Acme. The next step is to find the tenant ID by Itenantstore with the given tenant name, which is the ID of the current tenant if the tenant is discovered.
2. Httpheadertenantresolvecontributer: If there is a abp.tenantid request Header ( This constant is defined in Abp.MultiTenancy.MultiTenancyConsts.TenantIdResolveKey), then try to parse the tenant ID from the request header.
3. Httpcookietenantresolvecontributer: If there is a abp.tenantid cookie Value ( This constant is defined in Abp.MultiTenancy.MultiTenancyConsts.TenantIdResolveKey), then the tenant ID is parsed from the cookie.
If none of the above methods resolves to the tenant ID, then the current request is considered as the host request. The tenant parser is extensible. You can add a parser to the collection:Configuration.MultiTenancy.Resolvers, or remove an existing parser.
The last thing about resolving tenant IDs is that for performance optimizations, the resolved tenant ID is cached in the same request. Therefore, the resolution is executed only once in the same request (when and only if the user is not logged on). 5. Tenant Store
domaintenantresolvecontributer uses itenantstore to find the tenant ID through the tenant name. Nulltenantstore implements the Itenantstore interface by default, but it does not contain any tenants and only returns a null value for the query. When you need to query from the data source, you can implement and replace it. The extension has been implemented in Module Zero 's Tenant Manager . So, if you use module zero, then you don't need to care about the tenant store. 6. Data filtering
When we retrieve entities from the database, we must add a tenantid filter to the current tenant entity. When you implement an interface: Imusthavetenant or Imayhavetenant, the ABP will automatically complete the data filtering. imusthavetenant Interface
This interface distinguishes between entities of different tenants through the Tenantid property. Example:
public class product:entity, imusthavetenant
{public
int TenantId {get; set;}
public string Name {get; set;}
//... Other Properties
}
As a result, the ABP can find this to be a tenant-related entity and automatically isolate entities from other tenants. Imayhavetenant Interface
We may need to share entity types between host and tenant. An entity may belong to a tenant or host,imayhavetenant interface that also defines Tenantid (similar to imusthavetenant), but can be empty in this case. Examples are as follows:
public class role:entity, imayhavetenant
{public
int? TenantId {get; set;}
public string RoleName {get; set;}
//... Other Properties
}
We can use the same role class to store the host role and the tenant's role. In this case, the Tenantid property tells us whether this is a host entity or a tenant entity. A null value means that this is a host entity , and a non-null value means that this is owned by a tenant and that the tenant's ID is TenantId . Notes
Imayhavetenant is not as common as imusthavetenant. For example, a product class may not implement the Imayhavetenant interface because product is related to the actual application functionality and is irrelevant to the management tenant. Therefore, be careful with the Imayhavetenant interface because it is more difficult to maintain the code for tenants and tenants sharing.
When you define an entity type that implements the Imusthavetenant or Imayhavetenant interface, you need to set the value of the TenantId when creating a new entity ( The ABP will attempt to set the value of the current abpsession Tenantid to it, but in some cases this is not possible, especially for entities that implement the Imayhavetenant interface. Most of the time, this is the only place where you need to deal with Tenanti, but when other tenant data is filtered, you don't need to explicitly point out tenantid when writing LINQ where conditional statements because it automatically implements filtering. switching between host and tenant
When working on a multi-tenant application database, we should know the current lease. The default way to get a tenant ID is to get it from iabpsession. We can change this behavior and switch to the other tenant's database. For example:
public class Productservice:itransientdependency
{
private readonly irepository<product> _ Productrepository;
Private ReadOnly Iunitofworkmanager _unitofworkmanager;
Public Productservice (irepository<product> productrepository, Iunitofworkmanager UnitOfWorkManager)
{
_productrepository = productrepository;
_unitofworkmanager = Unitofworkmanager;
}
[Unitofwork]
Public virtual list<product> getproducts (int tenantId)
{
using (_ UnitOfWorkManager.Current.SetTenantId (tenantId))
{
return _productrepository.getalllist ();}}
}
The Settenantid method ensures that the data we get is the data for the specified tenant, which relies on the database schema:
If a given tenant has a specific database, switch to the database and obtain product data from the database
If a given tenant does not have a specific database (for example, a single-database method), it automatically adds Tenantid criteria to the query statement to filter the data for the specified tenant's product data
If we do not use the Settenantid method, it will get the tenant ID from the session as described earlier.
Here are some tips on best practices:
Switch to host using Settenantid (null)
For no particular reason, you should use the Settenantid method in the using statement block, as shown in the example above. Because it will automatically restore Tenantid after the using statement block and before the GetProducts method is complete (that is, when the using statement block is finished running, Tenantid is fetched from the session and not the incoming parameter from getproducts )
If required you can nest using the Settenantid method
Because _unitofworkmanager.current is only valid in the unit of work, make sure your code is running in the unit of work