Implementing multi-tenancy in ASP.Net
Hello fellow devs! I’ve recently embarked on a journey of converting an old project, originally crafted in Laravel with an ASP.Net backend, to adopt a multi-tenant architecture. This transition was motivated by two primary factors:
- My team at Entelect, led by our team lead Chim, embarked on a growth journey. I chose to look at things from the “Ground-up” and understand what make different things tick.
- The old project, NiftyPlanr, is a passion project which I would like to revive. I now need to accommodate different countries, each represented as a sub-domain, in conjunction with a Next.js front end.
I’m excited to share my experience and insights, hoping it will be a valuable resource for anyone looking to implement multi-tenancy in their ASP.Net applications.
This is the first part of the series. In each blog post, I will talk about something different so that this article does not become too long. No one wants to read very long blog posts.
Here are all the articles
- Implementing multi-tenancy in ASP.Net
- Implementing multi-tenancy in ASP.Net: Setting up Database Contexts
- Implementing multi-tenancy in ASP.Net: Resolving the tenant
- Implementing multi-tenancy in ASP.Net: Middleware
That said, let’s start by quickly discussing the different options we have for implementing multi-tenancy.
public class PageBreak {
// This code is inserted here as a motivator. Keep reading
}
Instance Replication Model: In this model, each tenant has its own dedicated instance of the application and database. This approach offers the highest level of data isolation and can be beneficial for customizing the application per tenant. It’s also straightforward to scale since you can add more instances as needed. However, it can become resource-intensive and might require more maintenance, as you need to manage and update each instance individually.
Single Database, Separate Schema Model: In the single database model, all tenants share the same database, but data is segregated using separate schemas. This model simplifies database maintenance and can reduce costs since there is only one database to manage. However, it requires careful design to ensure data security and tenant isolation. This model works well when the data structure is uniform across all tenants, and multi-tenancy logic can be efficiently handled within the application code.
Data Segregation with Separate Databases: This is a middle ground where all tenants share the same application instance, but each has its own separate database. It provides a good balance of isolation and maintainability, allowing for some level of customization in the database layer while keeping the application management centralized. This model is typically easier to maintain than instance replication, as the application codebase is unified, but it still maintains a strong data isolation for each tenant. This is the model we will be discussing in this blog post series.
public class PageBreak {
// this code is inserted here as a motivator. Keep reading
}
My Approach:
- Single Database for Platform, Separate Databases for Tenants: I decided to use one central database for the platform-related data and separate databases for each tenant (country). This approach allows me to maintain common platform data centrally while isolating tenant-specific data. Find the blog post here.
- Middleware for Tenant Resolution: In this blog post, I discuss a critical step was to implement middleware in ASP.Net to resolve the tenant. This middleware dynamically identifies the tenant based on the sub-domain of the request and retrieves the corresponding connection string from the platform database. It’s a nifty way to ensure that each request is routed to the correct tenant database.
- Database Factories: To manage databases effectively, I introduced two types of factories:
- Tenant Database Factory: This factory is responsible for creating instances of our
DbContext
tailored for the identified tenant. It uses the connection string resolved by our middleware to connect to the appropriate tenant database. - Design-Time DbContext Factory: Entity Framework Core requires a parameterless constructor for migrations. Hence, I created a separate factory for design-time operations like migrations. This factory helps in setting up the
DbContext
with a pre-defined connection string, typically from anappsettings.json
file, to facilitate migration tasks.
Key Learnings and Best Practices:
- Decoupling and Flexibility: Throughout this migration, maintaining a clean architecture was paramount. Decoupling the
DbContext
creation from the web context and other application layers was crucial for flexibility and testability. - Security and Configuration: We ensured sensitive data like connection strings were securely stored and managed, utilizing environment variables and configuration files.
- Testing and Validation: Rigorous testing was essential, especially when setting up the multi-tenant middleware and database factories. We validated our setup in different scenarios to ensure seamless tenant resolution and database connectivity.
Conclusion:
Transitioning to a multi-tenant architecture in ASP.Net, especially when integrating with a Next.js front end, can seem daunting. However, with a strategic approach focusing on clean architecture, security, and thorough testing, it’s a highly rewarding journey.
I hope my experience provides a helpful guide for your multi-tenant endeavors in ASP.Net. Feel free to reach out if you have questions or need further insights! Cheers!