Multi-Tenancy
Axelor Open Platform has multi-tenancy support. The term multi-tenancy refers to a software architecture in which a single instance of software runs on a server and serves multiple tenants. A tenant is a group of users who share common access with specific privileges to the software instance.
So now, a single instance of Axelor app can serve multiple tenants (users, companies etc).
Configuration
The default multi-tenancy implementation uses multiple databases per tenant and requires
configuration from axelor-config.properties
.
The database configuration keys with format db.<tenant-id>.<key>
is used to configure
database connections.
# enable multi-tenancy
application.multi-tenancy = true (1)
db.default.visible = false (2)
db.default.driver = org.postgresql.Driver
db.default.ddl = update (3)
db.default.url = jdbc:postgresql://localhost:5432/open-platform-demo-db
db.default.user = axelor
db.default.password =
db.db1.name = DB1 (4)
db.db1.hosts = host1,host1:8080 (5)
db.db1.driver = org.postgresql.Driver
db.db1.url = jdbc:postgresql://localhost:5432/open-platform-demo-db1
db.db1.user = axelor
db.db1.password =
db.db2.name = DB2
db.db2.hosts = host2,host2:8080
db.db2.driver = org.postgresql.Driver
db.db2.url = jdbc:postgresql://localhost:5432/open-platform-demo-db2
db.db2.user = axelor
db.db2.password =
db.db3.name = DB3
db.db3.hosts = host3,host3:8080
db.db3.driver = org.postgresql.Driver
db.db3.url = jdbc:postgresql://localhost:5432/open-platform-demo-db3
db.db3.user = axelor
db.db3.password =
1 | required to enable multi-tenancy feature |
2 | the default database should be invisible as it’s a fallback |
3 | only default database supports DDL operations, schema for other tenants should be created manually |
4 | display name of the tenant |
5 | host name based filter, users can only access one tenant in case of hosts match |
The default tenant is required and used for all unauthenticated requests.
Customization
We can override this default implementation by providing custom implementation of these two interfaces:
-
com.axelor.db.tenants.TenantConfig
- an interface to provide tenant connection config values -
com.axelor.db.tenants.TenantConfigProvider
- an interface to obtain TenantConfig, may be from external resource
The TenantConfigProvider
defines following methods to implement:
-
TenantConfig find(String tenantId)
- find a TenantConfig instance for the given tenant id -
List<TenantConfig> findAll(String host)
- find all the TenantConfig for the given hostname
The custom implementation should be integrated from application module like this:
public class DemoModule extends AxelorModule {
@Override
protected void configure() {
bind(TenantConfigProvider.class).to(MyTenantConfigProvider.class);
}
}
It’s up to the MyTenantConfigProvider
to decide how to resolve tenants (may be using jdbc connection
to some external database).
Multi-threading
Any multi-threaded or multi-process task, should run with a special helper com.axelor.db.tenants.TenantAware
.
Suppose you have following code that executes a task using a separate background process:
final ExecutorService executor = Executors.newFixedThreadPool(numWorkers);
executor.submit(() -> {
// work with database
});
It will not work as expected as the current tenant context is not available outside the current thread.
It can be fixed with following changes:
final ExecutorService executor = Executors.newFixedThreadPool(numWorkers);
executor.submit(new TenantAware(() -> {
// work with database
}));
Now the task will have the current tenant context.
However, if the process is not started from a web request (directly or indirectly), you have to pass the desired tenant id manually. Otherwise, it will assume a default tenant.
final ExecutorService executor = Executors.newFixedThreadPool(numWorkers);
executor.submit(new TenantAware(() -> {
// work with database
})
.tenantId("some-tenant"));
Same way, you have to change and tasks running in different threads:
Thread task = new Thread(() -> {
// work with database
});
task.start();
should be changed to:
Thread task = new TenantAware(() -> {
// work with database
});
task.start();
The TenantAware
is a subclass of java.lang.Thread
.
if the thread or process is not started from a web request, we have to
set the tenant id manually somehow using TenantAware#tenantId(String) method.
|
Authentication
Clients without session support (e.g. direct basic auth) should provide X-Tenant-ID
header
with every request to select a tenant.
Clients with session support should send X-Tenant-ID
header with login request.