implementation("io.micronaut.multitenancy:micronaut-multitenancy")
Table of Contents
Micronaut Multitenancy
Help with common tasks such as tenant resolution for multi-tenancy aware Micronaut applications.
Version: 5.5.1-SNAPSHOT
1 Release History
For this project, you can find a list of releases (with release notes) here:
2 Introduction
Multi-Tenancy, as it relates to software development, is when a single instance of an application is used to service multiple clients (tenants) in a way that each tenant’s data is isolated from the other.
2.1 Installation
To use the Micronaut’s multitenancy capabilities you must have the multitenancy
dependency on your classpath.
<dependency>
<groupId>io.micronaut.multitenancy</groupId>
<artifactId>micronaut-multitenancy</artifactId>
</dependency>
2.2 Built-In Tenant Resolvers
A common requirement for supporting Multi-tenancy is the ability to resolve the current tenant. Micronaut ships with the following built-in TenantResolvers:
name |
description |
Resolves the current tenant from an HTTP cookie. See CookieTenantResolver Configuration Properties. |
|
Resolves against a fixed tenant id. See FixTenantResolver Configuration Properties. |
|
Resolves the current tenant from the request HTTP Header. See FixTenantResolver Configuration Properties. |
|
Resolves the current tenant from the authenticated username. See PrincipalTenantResolver Configuration Properties. |
|
Resolves the current tenant from the HTTP session. See SessionTenantResolver Configuration Properties. |
|
Resolves the tenant id from the subdomain. See SubdomainTenantResolver Configuration Properties. |
|
Resolves the tenant id from a system property. See SystemPropertyTenantResolver Configuration Properties. |
2.3 Tenant Binding
To access the resolved tenant, you can bind Tenant as a controller method parameter.
package io.micronaut.multitenancy;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import java.io.Serializable;
@Controller("/tenant")
class TenantBindingController {
@Secured(SecurityRule.IS_ANONYMOUS)
@Produces(MediaType.TEXT_PLAIN)
@Get
Serializable echoTenant(Tenant tenant) {
return tenant.id();
}
}
package io.micronaut.multitenancy
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import io.micronaut.security.annotation.Secured
import io.micronaut.security.rules.SecurityRule
@Controller("/tenant")
class TenantBindingController {
@Secured(SecurityRule.IS_ANONYMOUS)
@Produces(MediaType.TEXT_PLAIN)
@Get
Serializable echoTenant(Tenant tenant) {
tenant.id()
}
}
package io.micronaut.multitenancy
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import io.micronaut.security.annotation.Secured
import io.micronaut.security.rules.SecurityRule
import java.io.Serializable
@Controller("/tenant")
internal class TenantBindingController {
@Secured(SecurityRule.IS_ANONYMOUS)
@Produces(MediaType.TEXT_PLAIN)
@Get
fun echoTenant(tenant: Tenant): Serializable = tenant.id()
}
2.4 Tenant Request Attribute
The module ships a filter which resolves the current tenant and sets it as a request attribute named tenantIdentifier
.
The following global configuration options are available for the TenantResolverFilter
:
Property | Type | Description |
---|---|---|
|
boolean |
Whether TenantResolverFilter should be enabled. Default value (true). |
|
java.lang.String |
Tenant Resolver filter processes only request paths matching this regular expression. Default Value: "^.*$" |
2.5 Tenant Expression Language
To allow using the current request tenant Id in the Micronaut Expression Language, you need to include the multitenancy-annotation as an annotation processor dependency.
annotationProcessor("io.micronaut.multitenancy:micronaut-multitenancy-annotation")
<annotationProcessorPaths>
<path>
<groupId>io.micronaut.multitenancy</groupId>
<artifactId>micronaut-multitenancy-annotation</artifactId>
</path>
</annotationProcessorPaths>
For Kotlin, add the micronaut-multitenancy-annotation dependency in kapt or ksp scope, and for Groovy add micronaut-multitenancy-annotation in compileOnly scope.
|
@Controller
public class TenantCheckingSecuredController {
@Get
@Produces(MediaType.TEXT_PLAIN)
@Secured("#{ tenantId == 'allowed' }") // (1)
public String index() {
return "Hello World";
}
}
@Controller
class TenantCheckingSecuredController {
@Get
@Produces(MediaType.TEXT_PLAIN)
@Secured("#{ tenantId == 'allowed' }") // (1)
String index() {
"Hello World"
}
}
@Controller
class TenantCheckingSecuredController {
@Get
@Produces(MediaType.TEXT_PLAIN)
@Secured("#{ tenantId == 'allowed' }") // (1)
fun index() = "Hello World"
}
1 | If tenantId (provided by the configured TenantResolver) is equal to 'allowed' then allow the request. |
2.6 Subdomain Tenant Resolver
The SubdomainTenantResolver does not support every domain suffix. For example, it does not support second-level domains. However, Micronaut Multi-tenancy ships with other implementations, PublicSuffixListSubdomainTenantResolver and InternetDomainNameSubdomainTenantResolver, which support every domain suffix but require extra dependencies. |
2.6.1 Public Suffix List Subdomain Tenant Resolver
The PublicSuffixListSubdomainTenantResolver uses the Public Suffix List API dependency.
To use it in addition to the micronaut-multitenancy
dependency, you need to the Public Suffix List API dependency to your classpath:
implementation("de.malkusch.whois-server-list:public-suffix-list")
<dependency>
<groupId>de.malkusch.whois-server-list</groupId>
<artifactId>public-suffix-list</artifactId>
</dependency>
2.6.2 Guava Subdomain Tenant Resolver
The InternetDomainNameSubdomainTenantResolver
uses Guava’s class InternetDomainName
to resolve the domain suffix.
To use it, in addition to the micronaut-multitenancy
dependency, you need to add the guava dependency to your classpath.
2.7 Tenant Propagation
Micronaut supports tenant propagation. As an example, take the following scenario:
You want incoming requests to the gateway
microservice to resolve the tenant id via subdomain. However, you want
your requests to other internal microservices to include the tenant Id
as an HTTP Header.
Your configuration in the gateway
microservice will look like:
micronaut.multitenancy.propagation.enabled=true
micronaut.multitenancy.propagation.service-id-regex=catalogue
micronaut.multitenancy.tenantresolver.subdomain.enabled=true
micronaut.multitenancy.tenantwriter.httpheader.enabled=true
micronaut:
multitenancy:
propagation:
enabled: true
service-id-regex: 'catalogue'
tenantresolver:
subdomain:
enabled: true
tenantwriter:
httpheader:
enabled: true
[micronaut]
[micronaut.multitenancy]
[micronaut.multitenancy.propagation]
enabled=true
service-id-regex="catalogue"
[micronaut.multitenancy.tenantresolver]
[micronaut.multitenancy.tenantresolver.subdomain]
enabled=true
[micronaut.multitenancy.tenantwriter]
[micronaut.multitenancy.tenantwriter.httpheader]
enabled=true
micronaut {
multitenancy {
propagation {
enabled = true
serviceIdRegex = "catalogue"
}
tenantresolver {
subdomain {
enabled = true
}
}
tenantwriter {
httpheader {
enabled = true
}
}
}
}
{
micronaut {
multitenancy {
propagation {
enabled = true
service-id-regex = "catalogue"
}
tenantresolver {
subdomain {
enabled = true
}
}
tenantwriter {
httpheader {
enabled = true
}
}
}
}
}
{
"micronaut": {
"multitenancy": {
"propagation": {
"enabled": true,
"service-id-regex": "catalogue"
},
"tenantresolver": {
"subdomain": {
"enabled": true
}
},
"tenantwriter": {
"httpheader": {
"enabled": true
}
}
}
}
}
In the catalogue
microservice the configuration will look like:
micronaut.multitenancy.tenantresolver.httpheader.enabled=true
micronaut:
multitenancy:
tenantresolver:
httpheader:
enabled: true
[micronaut]
[micronaut.multitenancy]
[micronaut.multitenancy.tenantresolver]
[micronaut.multitenancy.tenantresolver.httpheader]
enabled=true
micronaut {
multitenancy {
tenantresolver {
httpheader {
enabled = true
}
}
}
}
{
micronaut {
multitenancy {
tenantresolver {
httpheader {
enabled = true
}
}
}
}
}
{
"micronaut": {
"multitenancy": {
"tenantresolver": {
"httpheader": {
"enabled": true
}
}
}
}
}
To propagate the tenant you will need to write the resolved tenant ID to the outgoing requests.
Currently, Micronaut ships with two built-in implementations for TenantWriter:
name |
description |
Writes the current tenant to a Cookie in your outgoing requests. See CookieTenantWriter Configuration Properties. |
|
Writes the current tenant to a HTTP Header. See HttpHeaderTenantWriter Configuration Properties. |
3 Multi-Tenancy GORM
GORM supports Multi-tenancy and integrates with Micronaut Multi-tenancy support.
See the Micronaut Groovy module for more information.
4 Repository
You can find the source code of this project in this repository: