Businesses need to grow in order to be successful and handle an increasing number of clients and partners, and if a company is not ready to respond to this load then there is a big chance that opportunities will be missed. This brings the topic of scalability into the game, as one of the main requirements that a company should address. One of the possible ways to address this requirement is to build a multi-tenant solution. And as this topic gains more importance, lots of options are available to achieve this, for example, using Microsoft Elastic database (elastic tools). However, in particular cases, like the case I faced on my project, not all of the product requirements could be satisfied with the available options. This brought me to the idea of gathering my experience on this topic and presenting it below.
As we all are aware, there are two main approaches to tackling application scaling – horizontal and vertical. Horizontal scaling will bring you the benefit of scaling on the fly and will imply dealing with multiple databases, as each tenant has its own database/shard. The vertical approach to scaling presumes that you have one database that serves several tenants.
In my article, I will address the approach of horizontal scaling with a step-by-step guide on how to build a multi-tenant web API application.
If you would like to refresh on some aspects of multi-tenant architecture or what are the pros and cons it brings to the project, then I recommend visiting these resources:
- Why Cloud Architecture Matters: The Multi-Instance Advantage over Multi-Tenant
- Why Multi-Tenant Application Architecture Matters in 2017
- Design patterns for multi-tenant SaaS applications and Azure SQL Database
Architecture
Let’s briefly take a look at the architecture first. The example below is designed based on N-tire architecture and has the following layers:
- Presentation layer or Web API.
- Service layer that will accommodate all the business logic.
- Data access layer that is implemented using UnitOfWork and Repository patterns. As an ORM, in this example, I used Entity Framework Core.
The key component of tenant separation is ContextFactory that contains logic to get the tenant id from the HTTP header, retrieve a tenant database name using DataBaseManager, and replace a database name in the connection string. As a result, a database context (EF context) is created.
The diagram below demonstrates this architecture.
Implementation
As you can see, the architecture is not that complicated here, and, skimming through it, I’d suggest you focus on the steps to implement it.
1. Create ContextFactory
As I mentioned before, ContextFactory is a key component of the whole architecture. It constructs the Entity Framework context (in the current example, DeviceApiContext), specific to the tenant database.
The source code of ContextFactory is available here.
3. Add the Database Manager
The database manager orchestrates all tenants' metadata, such as tenant database name, the activation status of tenants (activated/deactivated), and a bunch of other properties. To demonstrate a base principle I used a dictionary in the current solution. Later on, the dictionary should be replaced with more appropriate solutions, like a SQL or NoSQL database that contains the tenant's metadata. This idea is similar to the shard map manager that is used in Microsoft Elastic Tools. Also, the tenant's metadata may include fields to store the database name, options to activate/deactivate tenants, even tenant styles for front-end applications based on CSS/SASS/LESS files, etc.
4.Add Unit of Work Class (Contains Commit to Specific Context Method)
UnitOfWork solves two tasks. It commits all changes made by the Entity Framework in entities and disposes of specific contexst.
The source code of UnitOfWork is available here.
5. Add a Generic Repository Class
The repository will make changes in EF entities and the Unit of Work will commit changes to the tenant's database. Be aware that EF is making changes in memory, using Tracking Mechanism.
The source code is available here.
5. Add Tenant Header Operation Filter
The TenantHeaderOperationFilter class will add a tenant id field to all API calls (as an HTTP header). In solutions which use OIDS services, e.g. IdentityServer or auth0.com, tenants can be injected to a JWT token.
The source code is available here.
This is how the API will look after the filter is applied:
6. Service Layer Example
The current example of a service class (DeviceService.cs) contains functions for retrieving a device by its id and adding a new device for specific tenants. The source code of the service layer is available here.
Conclusion
In this article, I explained how to build a "ready to go" multi-tenant solution and gave some suggestions on how it can be used in your product/business. As I mentioned before, this solution is ready to go so it can be used as a "Boilerplate" project or as part of a larger project.
Source Code
The project's source code available on my Git repository here.
No comments:
Post a Comment