Transitioning from single-tenancy to  multi-tenancy requires changes accross various components  of your existing setup.

This guide is made to help you navigate these changes.

Assumptions

This guide assumes that all necessary changes have been made to the blueriq model to support multi-tenancy. Therefore, the focus here will be exclusively on configuring the application setup for multi-tenancy.

Overview of Changes

To enable multi-tenancy, you'll need to perform the following tasks for each tenant:

  1. Create SQL databases
  2. Create message queues and exchanges
  3. Update and add configuration to some application components

Technology Stack Assumptions

For this example, we assume your setup includes the following components:

  • Runtime
  • Case Engine
  • CDS
  • DCM Lists
  • DCM Maintenance App
  • Gateway (no changes needed)
  • Keycloak
  • RabbitMQ
  • PostgreSQL

We will illustrate the process using two example tenants: Google and Apple.

Creating SQL Databases

In a multi-tenant setup, each tenant should have its own isolated database. This separation is crucial to ensure data security and integrity, allowing each tenant to access only its own data.

The case-engine scheduler and process-engine basic scheduler are exceptions. The scheduler can only be initialised once and is tenant-aware of itself.

To create the necessary databases, connect to your PostgreSQL server and execute the following SQL statements:

psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "postgres" <<-EOSQL
	CREATE DATABASE google_audits;
	CREATE DATABASE google_cds;
	CREATE DATABASE google_comments;
	CREATE DATABASE google_process;
	CREATE DATABASE google_timeline;
	CREATE DATABASE google_trace;
	CREATE DATABASE google_reports;

	CREATE DATABASE apple_audits;
	CREATE DATABASE apple_cds;
	CREATE DATABASE apple_comments;
	CREATE DATABASE apple_process;
	CREATE DATABASE apple_timeline;
	CREATE DATABASE apple_trace;
	CREATE DATABASE apple_reports;
	
    CREATE DATABASE scheduler;
EOSQL

Next, execute the SQL scripts for each tenant's database to set up the necessary tables and data structures. NOTE: use the regular creation scripts and transfer them to your postgresql server.

psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_audits" -f /db_scripts/audit.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_cds" -f /db_scripts/cds.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_comments" -f /db_scripts/comments.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_process" -f /db_scripts/process.sql -f /db_scripts/process_outbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_timeline" -f /db_scripts/timeline.sql -f /db_scripts/timeline_inbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_trace" -f /db_scripts/trace.sql -f /db_scripts/trace_inbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_reports" -f /db_scripts/reports.sql

psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_audits" -f /db_scripts/audit.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_cds" -f /db_scripts/cds.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_comments" -f /db_scripts/comments.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_process" -f /db_scripts/process.sql -f /db_scripts/process_outbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_timeline" -f /db_scripts/timeline.sql -f /db_scripts/timeline_inbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_trace" -f /db_scripts/trace.sql -f /db_scripts/trace_inbox.sql
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_reports" -f /db_scripts/reports.sql

psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "scheduler" -f /db_scripts/scheduler.sql

Defining Queues & Exchanges

Next we change the definitions.json of RabbitMQ to add two vHosts: Google and Apple. Afterwards we change the current vHosts of all declarations and bindings to Google. Finally we will duplicate the bindings an declarations and change on those the vHost to Apple.

In the least complex situation, the RabbitMQ is shared between tenants. Each tenant uses the same queues, but in its own virtual host. This means that data is not mixed between tenants.

An example script for RabbitMQ initialisation can be found here, it must be adjusted to create all exchanges and queues for each tenant, with a different virtual host, like the description below.

Google Tenant

definitions.json
{
	"bindings": [
		{
			"arguments": {},
			"destination": "dcmEventsQueue",
			"destination_type": "queue",
			"routing_key": "",
			"source": "dcmEvents",
			"vhost": "Google"
		},
		...
	],
	"exchanges": [
		{
			"arguments": {},
			"auto_delete": false,
			"durable": true,
			"internal": false,
			"name": "dlx.dcmMaintenanceApp",
			"type": "fanout",
			"vhost": "Google"
		},
		...
	],
	"permissions": [
		{
			"configure": ".*",
			"read": ".*",
			"user": "guest",
			"vhost": "Google",
			"write": ".*"
		}
	],
	"policies": [],
	"queues": [
		{
			"arguments": {},
			"auto_delete": false,
			"durable": true,
			"name": "dcmMaintenanceAppDlq",
			"vhost": "Google"
		},
		...
	],
	"vhosts": [
		{
			"name": "Google"
		}
	]
}


Apple Tenant

definitions.json
{
	"bindings": [
		{
			"arguments": {},
			"destination": "dcmEventsQueue",
			"destination_type": "queue",
			"routing_key": "",
			"source": "dcmEvents",
			"vhost": "Apple"
		},
		...
	],
	"exchanges": [
		{
			"arguments": {},
			"auto_delete": false,
			"durable": true,
			"internal": false,
			"name": "dlx.dcmMaintenanceApp",
			"type": "fanout",
			"vhost": "Apple"
		},
		...
	],
	"permissions": [
		{
			"configure": ".*",
			"read": ".*",
			"user": "guest",
			"vhost": "Apple",
			"write": ".*"
		}
	],
	"policies": [],
	"queues": [
		{
			"arguments": {},
			"auto_delete": false,
			"durable": true,
			"name": "dcmMaintenanceAppDlq",
			"vhost": "Apple"
		},
		...
	],
	"vhosts": [
		{
			"name": "Apple"
		}
	]
}


OAuth2 - Keycloak

To ensure that Keycloak adds the correct tenant name in the JWT token during user authentication, and to map the tenant attribute appropriately, you'll need to configure Keycloak accordingly.

A way to achieve this is to add a user-attribute to the users in Keycloak. For example using the steps below:

1. Add a "TenantID" Attribute to Users

  • Navigate to the Keycloak Admin Console:
    1. Go to Users in the left-hand menu.
    2. Select the specific user or create a new user or select an existing user.
    3. Go to the Attributes tab.
    4. Add a new attribute with the name TenantID and set its value to the appropriate tenant name.
    5. Save the changes.

2. Create a Custom Mapper to Add the "tenant" Claim

  • Create a Client Mapper:
    1. In the Keycloak Admin Console, go to the Clients section and select the client for which you want to add the claim.
    2. Go to the Mappers tab and click Create.
    3. Configure the new mapper with the following settings:
      • Name: Tenant Mapper (or any suitable name)
      • Mapper Type: User Attribute
      • User Attribute: TenantID
      • Token Claim Name: tenant (or your preferred claim path name)
      • Claim JSON Type: String
      • Add to ID token: ON
      • Add to access token: ON
      • Add to userinfo: ON
    4. Save the mapper.

Alternative Claim Path Name

If you decide to use a different claim path name, ensure that your application is configured to recognize this custom claim. For example, if you use custom_tenant_claim_path instead of tenant, you need to set the following configuration in the Runtime and DCM maintenance app:

application.properties
blueriq:
  jwt:
    tenant-path: $.custom_tenant_claim_path

This configuration allows your application to correctly parse the tenant information from the JWT token. 

Configuring the components

Runtime

To transition your Blueriq Runtime environment from single tenancy to multi-tenancy, you need to configure the property files to handle connections on a per-tenant basis. Below is described how to achieve this for two tenants, Google and Apple.

1. Enable Multi-Tenancy

First, enable multi-tenancy by adding the following properties to your application.properties file:

application.properties
blueriq.multi-tenancy.enabled=true
blueriq.multi-tenancy.allowedTenants=google,apple

This setting tells the Blueriq Runtime that it should operate in multi-tenant mode and allows only the specified tenants (google and apple).

2. Configure JDBC Connections for Tenants

For each tenant, configure the JDBC connection settings. Use default properties for common configurations like the driver, username, and password, and then specify the JDBC URL for each tenant's specific databases.

Google Tenant

application.properties
blueriq.default.datasource.tenants.google.driverClassName=org.postgresql.Driver
blueriq.default.datasource.tenants.google.username=<your database username>
blueriq.default.datasource.tenants.google.password=<your database password>

blueriq.datasource.comments-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_comments
blueriq.datasource.process-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_process
blueriq.datasource.trace-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_trace
blueriq.datasource.timeline-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_timeline
blueriq.datasource.report-service.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_reports

Apple Tenant

application.properties
blueriq.default.datasource.tenants.apple.driverClassName=org.postgresql.Driver
blueriq.default.datasource.tenants.apple.username=<your database username>
blueriq.default.datasource.tenants.apple.password=<your database password>

blueriq.datasource.comments-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_comments
blueriq.datasource.process-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_process
blueriq.datasource.trace-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_trace
blueriq.datasource.timeline-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_timeline
blueriq.datasource.report-service.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_reports

3. Configure RabbitMQ Connections for Tenants

For each tenant, configure the RabbitMQ connection settings. Use default properties for common settings like host, port, username, and password. You dont need to set the queue and exchange names as they are picked up by default.

Defaults to the RabbitMQ instance

application.properties
blueriq.default.rabbitmq.host=<rabbitmq host>
blueriq.default.rabbitmq.port=<rabbitmq port>

Google Tenant

application.properties
blueriq.default.rabbitmq.tenants.google.username=<rabbitmq username>
blueriq.default.rabbitmq.tenants.google.password=<rabbitmq password>
blueriq.default.rabbitmq.tenants.google.virtualHost=Google

Apple Tenant

application.properties
blueriq.default.rabbitmq.tenants.apple.username=<rabbitmq username>
blueriq.default.rabbitmq.tenants.apple.password=<rabbitmq password>
blueriq.default.rabbitmq.tenants.apple.virtualHost=Apple

4. Additional Connections (SOAP, REST, etc.)

For other connections like SOAP services, REST calls, etc., follow the same pattern. Use the format:

<connection-prefix>.tenants.<tenantName>.<specific-property>=<value>
application.properties
blueriq.soap.service.tenants.google.endpoint=<google soap endpoint>
blueriq.soap.service.tenants.apple.endpoint=<apple soap endpoint>

Case Engine

To set up the Case Engine with multi-tenancy support, you will need to configure SQL connections (for the process sql stoe, trace sql store, timeline sql store, and scheduler-quartz) and RabbitMQ connections for each tenant.

1. Enable Multi-Tenancy

Add the following settings to enable multi-tenancy:

application.properties
blueriq.multi-tenancy.enabled=true
blueriq.multi-tenancy.allowedTenants=google,apple

2. Configure JDBC Connections for Tenants

Google Tenant

application.properties
# Google SQL Data Source
blueriq.default.datasource.tenants.google.driverClassName=org.postgresql.Driver
blueriq.default.datasource.tenants.google.username=<your database username>
blueriq.default.datasource.tenants.google.password=<your database password>


# Google JDBC URLs
blueriq.datasource.process-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_process
blueriq.datasource.trace-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_trace
blueriq.datasource.timeline-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_timeline
blueriq.datasource.scheduler-quartz.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_reports

Apple Tenant

application.properties
# Apple SQL Data Source
blueriq.default.datasource.tenants.apple.driverClassName=org.postgresql.Driver
blueriq.default.datasource.tenants.apple.username=<your database username>
blueriq.default.datasource.tenants.apple.password=<your database password>

# Apple JDBC URLs
blueriq.datasource.process-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_process
blueriq.datasource.trace-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_trace
blueriq.datasource.timeline-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_timeline
blueriq.datasource.scheduler-quartz.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_reports

3. Configure RabbitMQ Properties for Each Tenant

Defaults to the RabbitMQ instance

application.properties
blueriq.default.rabbitmq.host=<rabbitmq host>
blueriq.default.rabbitmq.port=<rabbitmq port>

Google Tenant

application.properties
blueriq.default.rabbitmq.tenants.google.username=<rabbitmq username>
blueriq.default.rabbitmq.tenants.google.password=<rabbitmq password>
blueriq.default.rabbitmq.tenants.google.virtualHost=Google

Apple Tenant

application.properties
blueriq.default.rabbitmq.tenants.apple.username=<rabbitmq username>
blueriq.default.rabbitmq.tenants.apple.password=<rabbitmq password>
blueriq.default.rabbitmq.tenants.apple.virtualHost=Apple

4. Add MongoDB Connections for Each Tenant

Lastly, add the MongoDB connection details for each tenant to the application.properties file:

Google Tenant

application.properties
# Google MongoDB Configuration
blueriq.default.mongodb.tenants.google.host=<mongodb host>
blueriq.default.mongodb.tenants.google.port=<mongodb port>
blueriq.default.mongodb.tenants.google.username=<mongodb username>
blueriq.default.mongodb.tenants.google.password=<mongodb password>

blueriq.case.engine.data.mongodb.tenants.google.database=google_caseEngine
blueriq.locking.mongodb.tenants.google.database=google_locks

Apple Tenant

application.properties
# Apple MongoDB Configuration
blueriq.default.mongodb.tenants.apple.host=<mongodb host>
blueriq.default.mongodb.tenants.apple.port=<mongodb port>
blueriq.default.mongodb.tenants.apple.username=<mongodb username>
blueriq.default.mongodb.tenants.apple.password=<mongodb password>

blueriq.case.engine.data.mongodb.tenants.apple.database=apple_caseEngine
blueriq.locking.mongodb.tenants.apple.database=apple_locks

CDS

To enable multi-tenancy for the CDS and adjust the customer data SQL store connection properties, enable multi-tenancy and adjust the customer data sql store datasource as follows

blueriq-customer-odata-service-v1.yml
blueriq:
  multi-tenancy:
    enabled: true
    allowed-tenants: google,apple
  customerdata:
    datasource:
      customer-data-sql-store:
        driver-class-name: org.postgresql.Driver
        tenants:
          google:
            url: jdbc:postgresql://<database host>:<database port>/google_cds
            username: <your database username>
            password: <your database password>
          apple:
            url: jdbc:postgresql://<database host>:<database port>/apple_cds
            username: <your database username>
            password: <your database password>

DCM Lists Service

To enable multi-tenancy for the DCM Lists Service and configure the MongoDB and RabbitMQ connections, update your configuration as follows:

blueriq-dcm-lists.yml
blueriq:
	multi-tenancy:
    	enabled: true
    		allowed-tenants:
      			- google
      			- apple
  	default:
		mongodb:
			tenants:
				google:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password>
 				apple:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
		rabbitmq:
			tenants:
 				google:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
 					virtualHost: Google
 				apple:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
 					virtualHost: Apple 
  dcm:
    lists:
      mongodb:
        tenants:
          google:
            database: google_tasks
          apple:
            database: apple_tasks


DCM Maintenance App

To enable multi-tenancy for the DCM Mainteance App and configure the MongoDB and RabbitMQ connections, update your configuration as follows:

blueriq-dcm-maintenance-app.yml
blueriq:
	multi-tenancy:
    	enabled: true
    		allowed-tenants:
      			- google
      			- apple
  	default:
		mongodb:
			tenants:
				google:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password>
 				apple:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
		rabbitmq:
			tenants:
 				google:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
					virtualHost: Google
 				apple:
					host: <database host>
					port: <database port>
					username: <database username>
					password: <database password> 
					virtualHost: Apple
	dcm:
    	maitnenance:
			app:
				mongodb:
			        tenants:
          				google:
				            database: google_messages
			          	apple:
				            database: apple_apple

Adding the X-Tenant-ID Header

The final step to ensure that your multi-tenant stack functions correctly is to include the X-Tenant-ID header in each request. This header should carry the appropriate tenant name as its value.

We recommend using NGINX as a proxy to automate this process. NGINX should be configured to add the X-Tenant-ID header to every incoming request.

One way of achieveing this is to add the tenant to your domain (google.mydomain.com or apple.mydomain.com). This can be rewritten in NGINX to add "apple" or "google" as a header to the request. Make sure the proxy always overwrites the header, otherwise someone might set it himself in the browser.