I’ve had this personal objective on my to-do list for a while now. I wanted to start blogging and contributing some parts of my work to the online developer community, but I haven’t really gotten around to start working on my first blog post. Until now. Finally. Here it is. My first blog post.
This post will be part of a four-part series. In this first part, we will look at how to design a multi-tenant SaaS solution and which decisions you need to take before starting to implement the solution.
If you have already read my short bio, then you know that I’m a Spring Framework enthusiast. As the creators of Spring describe the framework themselves: “Spring makes Java simple”. I’m a strong supporter of keeping things simple, and not just in software. I also try to follow this guideline generally because it helps me to lead an overall better and happier life.
Making things simple helps to save a lot of time - very valuable time of software engineers. I’ve liked the strongly opinionated way of Spring Boot since I first started working with it because the great default functionality provided via its built-in auto-configuration saves so much effort.
For many years now, if you want to start developing a new Java web application, there’s no way around Spring. It has become the de-facto standard way to go. That’s why it was an easy choice for us at Quantics to start building up our SaaS solution with Spring Boot. It met all of our requirements when it came to aspects such as maintainability, security, and scalability. It was the perfect fit for our goal of creating a SaaS solution from scratch.
But we didn’t just want to build another SaaS app that was developed once for a dedicated customer. We wanted to build a scalable SaaS solution that could generate recurring revenue from our target customers of a very big market: the market of potentially all retail and manufacturing companies. For this, we could not build the solution in a way to have a separate instance of it running for each customer. It just doesn’t scale. That’s why we were building a multi-tenant SaaS solution.
A multi-tenant web application has the goal of being deployed as a single product used by multiple customers, i.e. tenants, simultaneously. It shares the code among all tenants but it provides separated access for each individual tenant. An essential feature of a multi-tenant solution is that each tenant has access to its own data and can manage its own users independently and isolated from the other tenants.
There are mainly three approaches to separate the access of tenants in a multi-tenant solution:
- Discriminator column: Shared database - shared schema
- Schema per tenant: Shared database - separate schemas
- Database per tenant: Separate databases
Let’s have a look into the pros and cons of each of these three approaches.
The most basic approach to separating tenants in your database is to add a discriminator column (e.g.
tenant_id) to all tables. But, when it comes to scalability, this method may seriously prevent you from growing with your solution. An issue with this approach that might already occur if you only have a few tenants on the solution is that a single tenant with high data volume can negatively affect the performance of other tenants. This is also known as the noisy neighbor problem.
Additionally, having a
tenant_id discriminator column in all tables of your database opens up the risk of mixing up data from multiple tenants if a developer forgets to add the discriminator column in the
WHERE clause of a query. I have seen this problem happening with my own eyes and believe me, you don’t want to be the responsible person who needs to explain to customer A that customer B has access to their data.
To improve on the aforementioned issues, a more robust strategy is to use one database with a dedicated schema per tenant. With this approach, each tenant’s data is stored in its own schema and is thereby logically isolated from the rest of the tenants.
Going one step further, storing each tenant’s data in their own dedicated database means the highest possible level of isolation from other tenants since the data is physically separated. However, setting up and maintaining a separate database instance for each tenant can lead to a lot of effort and increasingly high operational costs for your IT infrastructure.
As always and with everything: it depends. I would argue that storing all data in a single database inside a shared schema is not the best option if you want to build a scalable SaaS solution that can handle large amounts of data from multiple customers. That leaves us with the other two options. Concept-wise, they are pretty similar. The only difference is that isolation happens either logically (a schema per tenant) or physically (a database per tenant). Unless you have very strict security requirements right from the beginning, I’d say that starting off with logical separation (a schema per tenant) is easier and saves you some cost and maintenance effort as well. And once implemented, moving each tenant’s data from its schema to its own database is a manageable task that can definitely make sense once the application starts growing and additional security requirements start popping up.
Now that we have decided on a strategy for isolating the tenants in our multi-tenant solution, it’s time to choose a tech stack that fits the requirements of our SaaS solution, because after all, the Spring application won’t be the only part of our system. To be well prepared for future growth and possible changes to our system, we need to think about the cornerstones of our architecture early in the design stage and make well-informed decisions about which components we choose.
Since proper security is an essential requirement for any cloud-based solution, we have decided to use the industry-standard frameworks OAuth2 and OpenID Connect for ensuring that only authenticated and authorized users can access the data that is provided and managed by our application. With the OAuth2 terminology, our application is considered the resource server (since it provides access to secured resources) as opposed to the authorization server (which is responsible for authorizing and providing access tokens to the resource owner, i.e. the user who wants to access some resources). In order to access the secured resources in a user-friendly way, the user doesn’t have to directly consume the APIs on its own, but it can use a client, i.e. a frontend application that communicates with the resource server on behalf of the user.
As we have seen, designing a multi-tenant SaaS solution requires a clear vision and understanding of the target architecture of your system. We at Quantics have selected some technologies that help us in the best possible way to build our solution. Clearly, the discussed technologies in this blog post are far from the only options out there to build such a solution (and it might be that they are also not the best choice for your specific requirements). But as always in the world of software, it all depends.
Choosing the best-fit technologies and tools depends on the requirements that you have for your system. It depends on the skills of your team as well as on many more factors that might influence your decisions. At Quantics, we have selected these technologies because we feel confident that they can help us make effective use of our skill-sets.
In part 2 of this series, we will see how to build up the core piece of our multi-tenant SaaS solution: our backend. We will be using Spring for the implementation of it and there will be lots of code samples. Keep an eye out for the release of part 2!
Update: You can find the second part here.