Multitenancy Flowable engine in Spring Reactive app

Hi, I’m trying to setup the Shared Engine / Multi-schema option for allowing multitenancy in our product.
This product is based on Spring Webflux: multitenancy is managed using a “tenant” claim into the Reactor Context for each request. Based on JWT, each request is forwarded to the correct MongoDB instance, or Redis instance (this for database and cache access).
Now I would like to obtain the same result for Flowable: the same engine (managed in our Spring mircoservice) will manage the requests from all the tenants, each with its own Postgres database instance: each tenant has its own deployments, process definitions, process instances and so on in a dedicated database.
But I’m not finding a way to make this work: I tryied a TenantInfoHolder implementation:

public String getCurrentTenantId() {
		return AuthenticationUtils.getTenantIdFromContext().block();
	}

I can’t figure out what to implement for setCurrentTenantId and clearCurrentTenantId methods because the tenant remains the same for each request.
I also implemented the MultiSchemaMultiTenantProcessEngineConfiguration, but I get a nullpointer exception at startup time (at startup time there is no logged user, so no tenant) if I don’t put a null tenant.

@Override
public ProcessEngine buildProcessEngine() {
	log.info("buildProcessEngine");
		
this.setDatabaseType(MultiSchemaMultiTenantProcessEngineConfiguration.DATABASE_TYPE_POSTGRES);
this.setDatabaseSchemaUpdate(MultiSchemaMultiTenantProcessEngineConfiguration.DB_SCHEMA_UPDATE_DROP_CREATE);
this.setAsyncExecutorActivate(true);
this.setDisableIdmEngine(true);
this.setDisableEventRegistry(true);
this.setAsyncExecutor(new SharedExecutorServiceAsyncExecutor(tenantInfoHolder));

registerTenant(null, createDataSource("jdbc:postgresql://localhost:5432/flowable", "flowable", "flowable"));
registerTenant("one-realm", createDataSource("jdbc:postgresql://localhost:5432/flowable", "flowable", "flowable"));
registerTenant("two-realm", createDataSource("jdbc:postgresql://localhost:5432/flowable-two", "flowable-two", "flowable-two"));

I get the application running, but all requests are to the one-realm tenant.
A request of a user logged into two-realm tenant will access to one-realm flowable data.

Could somehone help me, please?

If your setup looks like that, you’re using Flowable effectively in single-tenant mode. The code you’ve pasted is to work in a single database with multiple tenants and different schema’s.

In your use case, it looks like you want to use a single engine per tenant, but still set the tenantId in the data? To do that, you need to pass the tenantId to all relevant calls (e.g. start process instance, etc.). That’s not automatic.

Thanks for the reply.
I wrote that code following the example I found for “Shared Engine / Multi-schema” approach. So it’s totally wrong? What’s the correct way to manage distinct tenant with separate databases?
I this case I don’t need to pass the tenant id to all the calls, right?

The multi-schema approach means using one engine for multiple tenants, where the schema is distinct for each tenant. In your first post, you mention you have a dedicated database for each tenant. Maybe I misread, but if you’re already swapping to the correct redis/mongodb, couldn’t the same be done for the relational database?

Does this mean you have one Flowable engine running? Or do you have a separate setup for each engine? In this case, does separate database mean separate schema (in same database)?

See flowable-engine/DummyTenantInfoHolder.java at master · flowable/flowable-engine · GitHub, that’s basically up to you: if the tenant id is the same for the request it’s thread bound (as the model in REST typically is one request = one thread) which means you could use a threadlocal like in the example.

Thank you.
In the end, I made it work by instantiating a ProcessEngine for each tenant and storing them in a map.

@Configuration
@Slf4j
public class FlowableConfig {
	
	private Map<String, ProcessEngine> tenantProcessEngineMap = new HashMap<>();
	
	@Autowired
	private TenantFlowableProperties tenantFlowableProperties;
	
	@Autowired
	private PlatformTransactionManager transactionManager;

   @Autowired
	private ApplicationContext context;

    public FormEngineConfigurator formEngineConfigurator(Tenant tenant) {
		var fec = new FormEngineConfigurator();
		fec.setFormEngineConfiguration(formEngineConfigure(tenant));
		return fec;
	}
    public SpringFormEngineConfiguration formEngineConfigure(Tenant tenant) {
		var sfec = new SpringFormEngineConfiguration();
		sfec.setJdbcUrl(tenant.getFlowable().getUrl());
		sfec.setJdbcUsername(tenant.getFlowable().getUsername());
		...
		sfec.setDeploymentMode("single-resource");
		return sfec;
	}
    @PostConstruct
	public void buildProcessEngines() {
		log.info("buildProcessEngines - tenants: {}", tenantFlowableProperties.getTenants());
		
		tenantFlowableProperties.getTenants().forEach(tenant -> {
			
			var formEngineConfiguration = buildFormEngineConfiguration(tenant);
			
			var formConfigurator = new SpringFormEngineConfigurator();
			formConfigurator.setFormEngineConfiguration(formEngineConfiguration);
			
			var processEngineConfiguration = new SpringProcessEngineConfiguration();
			processEngineConfiguration.setJdbcUrl(tenant.getFlowable().getUrl());
			processEngineConfiguration.setJdbcPassword(tenant.getFlowable().getPassword());
			...
			processEngineConfiguration.setEngineName(tenant.getId() + "-processEngineConfiguration");
			processEngineConfiguration.setConfigurators(List.of(formConfigurator));
			processEngineConfiguration.setTransactionManager(transactionManager);
			// The AsyncExecutor is a component that manages a thread pool to fire timers and other asynchronous tasks. 
			processEngineConfiguration.setAsyncExecutorActivate(false);
			processEngineConfiguration.setDatabaseType(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES);
			
			processEngineConfiguration.setDataSource(buildDataSource(tenant));
			
			Map<Object, Object> beans = Map.ofEntries(
			    Map.entry("serviceTask", new ServiceTask()),		// this is working
			    Map.entry("fireEventServiceDelegate", new FireEventServiceDelegate()),	// this is working
			    Map.entry("rabbitTemplate", new RabbitTemplate())	//other beans are not injected into JavaDelegate
			);
			processEngineConfiguration.setBeans(beans);
			processEngineConfiguration.setApplicationContext(context);
			
			tenantProcessEngineMap.put(tenant.getId(), processEngineConfiguration.buildProcessEngine());
		});
	}
    private SpringFormEngineConfiguration buildFormEngineConfiguration(Tenant tenant) {
		var formEngineConfiguration = new SpringFormEngineConfiguration();
		formEngineConfiguration.setJdbcUrl(tenant.getFlowable().getUrl());
		...
		formEngineConfiguration.setDataSource(buildDataSource(tenant));
		formEngineConfiguration.setApplicationContext(context);
		return formEngineConfiguration;
	}
public Mono<RepositoryService> getRepositoryService() {
		return getProcessEngine().map(p->p.getRepositoryService());
	}
	
	public Mono<RuntimeService> getRuntimeService() {
		return getProcessEngine().map(p->p.getRuntimeService());
	}
	
	public Mono<TaskService> getTaskService() {
		log.info("getTaskService");
		return getProcessEngine().map(p->p.getTaskService());
	}
	
	public Mono<FormService> getFormRepositoryService() {
		return getProcessEngine().map(p->p.getFormService());
	}
	
	public Mono<ProcessEngine> getProcessEngine() {
		return Mono.subscriberContext().map(ctx -> {
            var tenant = ctx.getOrEmpty("tenant").orElseThrow(() -> new IllegalStateException("FlowableConfig: tenant must not be null"));
            log.info("using process engine for tenant {} ", tenant );
            return tenantProcessEngineMap.get(tenant);
        });
	}

Other services, will get the right bean at runtime.

@Service
public class WorkflowProcessService {

	@Autowired
	FlowableConfig flowableConfig;
	
	@Autowired
	ProcessDefinitionMapper processDefinitionMapper;

    public Flux<ProcessDefinitionDto> getAllProcessDefinitions() {
		return flowableConfig.getRepositoryService().map(
			s -> s.createProcessDefinitionQuery().latestVersion().list())
			.flatMapMany(Flux::fromIterable).map(
					p -> processDefinitionMapper.mapToDto(p));
	}
	

Now the problem is to get other spring beans injected into JavaDelegate executed during flowable service tasks. Spring context is always null.

@Service("fireEventServiceDelegate")
public class FireEventServiceDelegate implements JavaDelegate {
	
	@Autowired
	private RabbitTemplate rabbitTemplate;
	
	@Autowired
	private ApplicationContext context;

	@Override
	public void execute(DelegateExecution execution) {
		log.info("Process instance " + execution.getProcessInstanceId() + " completed"); // log is correct
		log.info("rabbitTemplate: {}", rabbitTemplate); // null
		log.info("context: {}", context); // null

I think the java delegate is correctly defined

<serviceTask id="sid-A56..." name="FireEventServiceDelegate" flowable:delegateExpression="${fireEventServiceDelegate}"></serviceTask>