Multi-Tenant SaaS application Design Patterns, Cost-Effective Deployment Options in Azure, and Automate Deployment with ARM Templates

Ajith Ramawickrama
Ascentic Technology
13 min readJul 13, 2021
Photo by Lukas Blazek on Unsplash

The software as a Service (SaaS) model has become very famous today due to Its flexibility in cost and scalability. Today most individuals, SMEs, and even large enterprises are not interesting to invest capital expenditure to purchase software and install them on their own infrastructures. Instead, they all are looking for flexible solutions that they can pay as they use them. The customer wants to pay less while they are expecting more from the solution. On the other hand, the vendor wants to maximize their profit while providing a better service to the customer at a competitive price. If the vendor can minimize or keep their infrastructure and operational cost at a consistent level, both parties are in a win-win situation.

In this post, I am focusing on:

  1. Multi-Tenant SaaS design patterns.
  2. How Azure PaaS services such as SQL elastic pool and App Services help to deploy cost-effective SaaS solutions.
  3. Automate tenant deployments with ARM templates and .Net Core

Multi-Tenant SaaS design patterns

In the Software as a Service (SaaS) model, the vendor does not sell the license to the customer. Instead of that, the vendor rents the software to the customer along with its data. The customer pays the vendor for using the software based on the subscription plan they have agreed on. In this model, a single customer entity is called a tenant.

Assume that you are going to build a SaaS application for your customers. You have to decide the strategy that you are going to use when you provision compute resources for a tenant. You should consider the following factors before you choose the right model.

  1. Scalability: When your application becomes famous, more tenants will onboard with your application. And also, individual tenants will need more capacity when they grow with their businesses. This will continuously increase the workload on your compute resources and you should have a plan of allocating resources (CPU power, memory, storage …etc) for tenants to meet these scalability requirements.
  2. Tenant Isolation: Data of a tenant should not be mixed up with the data of other tenants. And also, the workload of a tenant should not affect the performance of other tenants. (Ex: When one tenant running a complex query in the database, it should not impact the performance of other tenants)
  3. Per tenant cost: This is very important to keep the health of your business. If you want to make a profit while providing a better service to your customer at a competitive price, you should keep this value as minimum as possible.
  4. Development & deployment complexity: The model you are using should support adding new features and deploy them easily. Giving continuous updates to the customer is very important to keep them stay signed with your application.
  5. Customizability: This depends on the nature of your applications. Some SaaS applications do not provide customized versions for specific customers. But some applications provide this option based on countries, regions, customer tier, and customer-specific customization…etc. Based on your application, you should think about this as well.
  6. Operational complexity: You should have a proper plan of monitoring, logging, disaster recovery, database schema management, and database point-in-time recovery of your SaaS application.

Let’s evaluate SaaS design patterns and how these patterns are supported for the above factors.

Model 1 — Standalone.

In this model, you deploy one database and one application for each and every tenant.

Standalone Model

This model provides greater tenant isolation. You can scale up and down both the application and the database based on the demand. And also, if you have an existing solution that you need to convert into a SaaS application without making big changes to the application code and the database, this is the easiest way to do so. Moreover, if you want to provide tenant-specific or tier-specific customizations, It is easy to do in this model since the change of one application does not affect the others. On the other hand, there is a maintenance hassle in this model. For example, If you have 100 tenants and you need to push an update to the application. In this case, you have to update 100 applications and 100 databases separately. It is a nightmare, isn't it? but there are several ways we can automate it.

Model 2-Multi-Tenant app with Database per tenant

I this model, a single application instance will be shared among multiple tenants. However, a separate database will be deployed for every tenant.

Multi-Tenant App with Database Per Tenant Model

In this model, you still have strong tenant isolation since every tenant has its own separate database. However, the application needs to be scaled up vertically or horizontally to meet the increasing demand. The catalog database keeps the mapping between the tenant and the database. When a request comes to the application (suppose you have a stateless backend), you need to identify the exact tenant that request is coming from, then find the correct database from the catalog, and build the connection string dynamically in order to connect with the tenant database. When comparing with the previous model, this model has less maintenance hassle since you do not need to deploy an application for every tenant separately. However, you should be careful when you do tenant-specific customization due to the fact that other tenants also sharing the same business logic in the application.

Model 3 — Multi-Tenant App with Multi-Tenant Databases

In this model, a single application instance serves many tenants and a single database holds data for many tenants. This is the most cost-effective way to design SaaS applications. And also, it is easy to deploy new features in this model. But on the other hand, you will lose tenant isolation, tenant-wise scalability, tenant-wise customizability, tenant-wise performance monitoring, and maintenance. Since all the tenants share the same compute resource for the database, you cannot limit the compute resources tenant-wise. Therefore, a huge workload on one tenant will be highly affecting the performance of other tenants. As well as, you should have a column in your relational tables to identify the tenant and you should filter all the data by tenant id when you query from the database.

Multi-tenant application with Multi-Tenant database

Database Sharding

In database sharding, tables of one database split horizontally and distribute among multiple databases in the same server or different servers. Sharding pattern also is a widely using SaaS design pattern. A catalog database is required to maintain the mapping between tenants and the database. Read this article to explore more about database sharding.

Summary of the SaaS Design Patterns

Let's summarize the above 3 patterns against the 6 factors which have mentioned above.

Cost-Effective SaaS application deployment options in Azure

Let's discuss what are the options available in Azure to deploy SaaS applications. Here I am going to discuss only the application and the database deployment. However, on top of that, you need to think about security, scalability, and high availability as well. The solution I am going to discuss supports these pillars as well. But it needs to be extended to achieve these pillars.

Containerization is one of the great and widely used options to deploy SaaS solutions. Azure provides managed Kubernetes service called Azure Kubernetes Services to orchestrate containers at any scale. This is one of the best fits for your application without any doubt. But on the other hand, deploying databases in containers is a controversial thing that you cannot find a black or white answer for that. However, deploying RDBMS databases such as MSSQL into containers is not recommended. Instead, Azure offers comprehensive PaaS services for SQL databases.

Azure SQL Elastic Pool

With Azure SQL Elastic pool you can create multiple databases on the same server at a fixed cost. Server resources are shared among multiple databases in the pool and the resources of the pool can scale up/down vertically to meet the demand. Elastic Pool can eliminate the following drawbacks.

  • If you are provisioning individual databases, you have to pay for each database separately.
  • Overprovisioning of resources — If you are using single databases, you have to provision them to be served for peak-time demand.
  • Cross-database reporting and schema update- you have to write a client application to get reports by combining multiple databases. And also schema update has to be done separately or using DevOps pipelines.

You can overcome all these drawbacks with Azure SQL Elastic Pools

Azure App Services

Azure App Services is a fully manages PaaS service that offers a greater set of features to deploy web applications. Features such as Automatic horizontal and vertical scaling options, deployment slots, custom domains & SSL, VNet integration, and containerization support make Azure App Service a very good choice for SaaS application deployment. Azure App Service Plan is a set of computer resources(VMs) that offer an environment to run your web application. App service plan offers both shared and dedicated infrastructures for web applications. On the other hand, Azure App Service Environment provides dedicated computer resources with more scalability and security options. If you can spend a little more money, App Service Environment is the best choice.

Deployment Architecture

This demonstration follows the database per tenant deployment pattern which has described above. It has only one application instance and separate databases for every tenant.

The following step-by-step guide describes how to deploy the application and the database.

  1. SaaS application developers push changes to the git repository (Azure Repo, Bit Bucket, Github…etc).
  2. Continuous deployment pipeline triggers the changes of the application repo and deploys the application to Azure App Service instance.
  3. Continuous deployment pipeline triggers the changes of the database project, creates a bacpac file, and saves it to Azure blob container.
  4. A new customer (tenant) comes to purchase a subscription to the SaaS application. Once he completed his profile creation, payments…etc, It sends a request to the deployment API to provision compute resources for the new tenant.
  5. Deployment API publish an ARM template to azure subscription via Azure Resource manager C# SDK.
  6. ARM template imports the bacpac file from the blob container and creates the tenant database in the elastic pool.
  7. ARM template creates a CNAME domain record for the application to identify the tenant uniquely. (Ex: tenant1.yourdomain.com)
  8. Create default admin accounts and email the details to the user.

Sample Application

The sample application I am going to discuss is written in .Net Core MVC and the database is MSSQL server. You can find the source code from this Github repo. This is a very simple application that has used the repository pattern. Please note that the separation of layers and design best practices have not been considered here. This repo consists of three projects.

Solution Structure

MultiTenatWebApp- A MVC application that can Add, Edit, Update, Delete Employees. Basically, this is our SaaS application.

MultiTenantDeployment — REST API that use to deploy manage tenants in Azure.

MultiTenantData - Common data access code that shares with the above two projects.

How to handle database connections in the application?

Since we are using the database per tenant pattern, Every tenant has a separate database. But all the tenants are connecting to the same application. Therefore, directing the users to the correct database is really important here.

Handling database connections in the app.

Every tenant has a unique subdomain and the relationship between the subdomain and the database is stored in CatalogDatabase. When the user makes a request to the application, the application identifies the subdomain that the request is coming from and finds the correct database to be connected from the catalog database. SaaS application uses a dynamic connection string in the ApplicationDbContext to connect to the database. Following is the magic code.

Dynamic connection string in ApplicationDbContext

If you have already downloaded the sample repo, please follow the below steps.

  1. Open the solution in Visual Studio.
  2. Open the appsettings.json located in MultiTenatWebApp and MultiTenantDeployment projects and change the connection string.
  3. Right-click on the MultiTenatWebApp project and set it as the startup project
  4. Open package manager console and select the MultiTenantData as the default project and fun the following commands one after another.
add-migration "initial migration" -Context ApplicationDbContext
update database

5. Right-click on the MultiTenantDeployment project and set it as the startup project.

6. Open package manager console and select the MultiTenantData as the default project and fun the following commands one after another.

add-migration "initial migration" -Context ApplicationDbContext
update database

7. This will create two SQL databases in your server.

8. Right-click on each database, then tasks, and select Export Data-Tier Application. This will create .backpac files in the given locations.

9. Create the publish artifact of the application. You can do this by using .Net Core CLI or Visual Studio

Now the application and the database are ready to deploy. Let's create Azure infrastructures for that. Please note that as mentioned on the architectural diagram, we are not using CI/CD tools to create artifacts automatically. Instead of that, we are following the manual process for demonstration purposes.

Create Azure Resources

Here I am using Azure CLI commands to create the required Azure resources. If CLI is not familiar with you, you can use the Azure Portal. To execute Azure CLI commands from your local computer, you have to have Azure CLI Tools installed. There is a cool VS Code extension to write, execute, and manage Azure CLI commands

  1. Log in to your Azure account. This will redirect you to the Azure portal to enter your credentials
az login

2. Set the default subscription for the demo.

az account set -s <Your Subscription name>

3. Create a service principle. When you access your azure subscription via Azure SDKs or Azure REST APIs, you need to have Azure Service Principle created and grant required permissions to that service principle. By default, it will grant the contributor role to your subscription. Once you run this command it will give you the appId, tenantId, and the password as the result. Save this information in a secure place. Please note that if these keys are compromised, the person who has these keys can do anything to your subscription. Therefore, delete this service principle as soon as you have completed your testing. For production applications, storing these keys in an Azure key vault is the recommended way.

az ad sp create-for-rbac --name <your service principle name>

4. Create a resource group

az group create-l <azure region ex:southeastasia>-n <your resource group name>

5. Create Azure App Service Plan. This will create a Linux app service plan in the Basic tier.

az appservice plan create-n <app service plan name>-g <resource group name>--is-linux--sku B1-l <azure region>

6. Create SaaS web Application

az webapp create--name <web app unique name>--plan <app service plan name>--resource-group <resource group name>--runtime "DOTNETCORE|3.1"

7. Allow application deployment via .zip file

az webapp config appsettings set-g <resource group name>--n <web app unique name>--settings WEBSITE_RUN_FROM_PACKAGE="1"

8. Deploy the application vis .Zip file

az webapp deployment source config-zip-n <web app unique name>-g <resource group name>--src <local path to the .zip file>

If this command does not work, please deploy the application using visual studio or SCM tools.

9. Create a SQL server.

az sql server create-g <resource group name>-l <azure region>-n <sql server name>-u <admin username>-p <admin password>

10. Create SQL elastic pool to hold tenant databases. This will be created in the Basic tier with 50 DTU capacity. Maximum DTU consumption of one database will be % DTUs

az sql elastic-pool create-n <elastic pool name>-g <resource group name>-s <sql server name>-e Basic-c 50--db-max-dtu 5

8. Create an Azure storage account

az storage account create-n <storage account name>-g <resource group name>-l <azure region>--sku Standard_LRS--https-only true--min-tls-version TLS1_0--access-tier Cool

9. Once it created run the following command to get storage access keys.

az storage account keys list-n <storage account name>

10. Create blob container to store the database .bappac file

az storage container create-n databases--account-name <storage account name>--account-key  <account key>

11. Upload catalog database bacpac to the blob container

az storage blob upload
--account-name <storage account name>
--account-key <storage account name>--container-name <container_name>--file <C:/local file path/catalog_db.bacpac>--name catalog_db.bacpac

12. Upload application database .bacpac to the blob container

az storage blob upload
--account-name <storage account name>
--account-key <storage account name>--container-name <container_name>--file <C:/local file path/saas_demo.bacpac>--name saas_demo.bacpac

13. Create an empty database for Catalog database in the elastic pool

az sql db create--name <catalog database name>--resource-group <resource group name>--server <sql server name>--elastic-pool <elastic pool name>--backup-storage-redundancy Local

14. Import catalog database .bacpac file to the empty database

az sql db import--admin-password <sql server password>--admin-user <sql server username>--storage-key <storage access key>--storage-key-type StorageAccessKey--storage-uri <Full url of the catalog.bacpac file>--auth-type SQL--name <database name>--resource-group <resource group name>--server <resource group name>

Automate Tenant Deployment with ARM templates.

There are several ways that you can provision Azure resources. Azure portal, Azure CLI, Azure resource manager REST APIs, SDKs, ARM templates…etc. ARM templates belong to the Infrastructure as a Code (IaaC) category which can use to automate deployments. ARM templates are just JSON files that consist of a collection of azure resources including their dependencies, configurations…etc. And also, you can define variables inside the template and you can pass parameters from the outside to the ARM template. Behind the scene ARM templates also using Azure REST APIs to manage resources.

Test Deployment Automation

  1. Open the appsettings.json of the MultiTenantDeployment project and edit the connection string and the CloudConfigurations sections.
  2. Set TenantDeployment project as the startup project and run it locally.
  3. Navigate to http://yourlocalurl/swagger/index.html and try the /api/Tenant endpoint

Once this runs successfully it should have created the tenant database and should have bound a subdomain to the application.

--

--