Multi-Tenancy Support With Spring Boot, Liquibase, and PostgreSQL | by Wenqi Glantz | Mar, 2022

A step-by-step information on implement multi-tenancy

Picture by No-longer-here from Pixabay

There are a number of fashions to realize multi-tenancy in a microservice:

  1. Database per Tenant: Every tenant has its personal database and is remoted from different tenants.
  2. Shared database, Separate Schema: All tenants share a database, however have their very own database schemas and their very own tables.
  3. Shared Database, Shared Schema: All tenants share the identical database and schema. Shared tables have a column with the tenant identifier, which exhibits the proprietor of the row.

Every mannequin has its professionals and cons. For this story, we’re going to give attention to the third mannequin, shared database, shared schema, shared tables having a discriminator column with the tenant identifier. See diagram under.

diagram by writer

For information isolation, PostgreSQL RLS is most well-liked over Hibernate filters. Hibernate’s assist for discriminator-based multi-tenancy will not be mature, nonetheless questionable. There’s a recognized subject HHH-6054 nonetheless in OPEN standing and has had no progress since 2017. For that cause, we aren’t going to make use of Hibernate Filters. As a substitute, we glance to PostgreSQL RLS to realize information isolation.

RLS is a PostgreSQL safety function which permits database directors to outline insurance policies to manage how particular rows of knowledge show and function for a number of roles. RLS is, in essence, a further filter we are able to apply to a PostgreSQL database desk. When a person tries to carry out an motion on a desk, this filter is utilized earlier than the question standards or different filtering, and the info is narrowed or rejected in keeping with the safety coverage. We are able to create row degree safety insurance policies for particular instructions like SELECT, INSERT, UPDATE, and DELETE.

A discriminator column, say tenant_id, must be added in tables the place multi-tenancy assist is required. We’re going to use a demo Spring Boot microservice named customer-service as instance. It’s a easy microservice which handles buyer CRUD operations. Let’s assume we’d like multi-tenancy assist for Buyer entity. We have to add a brand new Liquibase changeSet so as to add the tenant_id column to buyer desk, non nullable, see under:

Row degree insurance policies must be outlined for the tables which have to have multi-tenancy assist. Two steps:

  • Enabling Row Degree Safety for the desk
  • Outline a Coverage for the desk, referencing the tenant_id discriminator column

Each steps might be wrapped in a single Liquibase changeSet, seems like the next:

With Row Degree Safety, along with the desk proprietor database person (in customer-service’s case, it’s database person postgres), we have to introduce an app degree database person who can entry the tenant particular operations. Why? Row Degree Safety insurance policies are by default not utilized for the desk proprietor, as desk proprietor should have the ability to entry all rows for administrative functions. So let’s add a app degree database person, say “customerservice” by including a brand new Liquibase changeSet. See under. The second changeSet within the pattern script under is to make sure the SELECT, INSERT, UPDATE, DELETE permissions are correctly granted to “customerservice” person for all present tables. The “ALTER DEFAULT PRIVILEGES...” within the first changeSet ensures all future new tables can have these permissions correctly granted to “customerservice”. Each changeSets are wanted.

See under the category diagram for multi-tenancy implementation in customer-service microservice. We’ll drill down class by class within the sections under.

class diagram by writer

We want two information sources:

  • grasp information supply: Liquibase seems for grasp information supply for database migration wants.
  • tenant information supply: this information supply is the default information supply for our microservice. See code under. Discover the @Main annotation on tenantDataSource bean definition.

DataSourceConfiguration class seems like this:

Accordingly, information sources definition in software.yml:

With the Row Degree Safety coverage in place, each time the app asks the info supply for a connection, we have to set the PostgreSQL session variable to the tenant_id to implement information isolation. When the connection is closed, clear tenant_id from PostgreSQL session. Since we don’t have separate database customers per tenant, we are able to make the most of a customized session parameter e.g. app.tenant_id to affiliate present tenant with a database connection. Setting a session parameter is completed utilizing a Postgres-specific SQL assertion.

"SET app.tenant_id TO '" + tenantId + "'"

See under TenantAwareDataSource class.

That is the interface the place multi-tenant entity courses have to implement.

Utilizing InheritableThreadLocal to retailer/retrieve/clear the present tenant id.

Now how can we set the tenant_id on every entity class which wants multi-tenancy assist? Look to JPA EntityListener! EntityListener permits a listener to be hooked up to the lifecycle of a JPA entity. It permits us to to populate the discriminator column with the present tenant id. See TenantListener class under. The tenantId is captured from TenantContext and set on the entity proper earlier than an replace, delete, and save.

  • @PreUpdate: earlier than the replace operation
  • @PreRemove: earlier than an entity is eliminated
  • @PrePersis: earlier than persist known as for a brand new entity

Including this class BaseEntity, which implements TenantAware interface, to be prolonged by entities which want multi-tenancy assist. Discover @EntityListeners(TenantListener.class), enabling TenantListener to connect to the life cycle of the actual entity courses which extends BaseEntity.

Entity courses which want multi-tenancy assist now want to increase BaseEntity. Pattern entity class Buyer as follows:

Now let’s take into consideration how greatest to extract the tenancy identifier from an incoming request. A preferred possibility is to have consumer app ship a HTTP Header with the title X-Tenant-ID to cross in present tenantId. Our microservice then reads the X-Tenant-ID header and shops its worth within the InheritableThreadLocal CURRENT_TENANT in TenantContext. We are able to add a TenantInterceptor like under. in preHandle technique, we set the tenantId primarily based on the HTTP Header handed in, if none is handed in, then tenantId holds default worth “0”.

Subsequent we add WebConfiguration to inject TenantInterceptor to it.

To validate the implementation of multi-tenancy function, the next take a look at eventualities have been developed. As we are able to see, tenant information is remoted to the actual tenant solely.

Pattern step definition as follows:

For the whole supply code for this story, try my GitHub repo.

Completely satisfied Coding! Completely satisfied Crafting!

More Posts