Multi-tenant deployments with Camunda BPM and SpringBoot
Multi tenancy is a common feature in modern software systems. Usually we deal with multi tenancy when it comes to data separation. We need to ensure that users from one tenant will never see or modify data of another tenant. That’s known territory.
When we do process automation, tenant specific process models are regarded more as an anti pattern. You do not want to model a new process for each tenant! But sometimes processes have tenant specific parts, so what do we do? We make our processes dynamic and let them decide on different behavior using business rules. These business rules can easily be tenant specific, as maintaining them is usually easier and rules are more dynamic by their nature.
Multi-tenancy with Camunda BPM
Speaking of Camunda BPM, you will probably use DMN for business rules, as DMN is well integrated in BPMN and also fully supported by Camunda. So you need to deploy tenant specific decisions and you are lucky — Camunda has multi-tenancy built-in!
The Camunda way of deploying things for different tenants is to define different process archives in Camunda’s
processes.xml. You can even specify the resource path. Refer to Camunda’s official documentation for more details on how to do that:
Process Archive Configuration
my-engine bpmn/invoice.bpmn bpmn/order-resource.bpmn false true groovy,py true Container element for the configuration…
This is a small example of two process archives with different resource paths:
In this example the
resourceRootPath is specified with the
pa: prefix, which means that it is a path relative to the
processes.xml. Remember that, as it becomes important later! Here is a sample resource structure, which matches the
So far so good — this kind of multi tenancy setup works like a charm in most cases. But there is one scenario, where it does not work and that is when you are using SpringBoot! I was using SpringBoot in a project and after introducing multi tenancy that way, I could not launch my application with
java -jar anymore, while running it with Gradle’s
gradle bootRun worked fine. Well, I could launch the JAR but nothing was deployed to Camunda anymore.
The issue with SpringBoot
But why is that?? I debugged Camunda’s auto deployment mechanism and figured out that it did not handle the repackaged JAR from SpringBoot correctly. There is a class in Camunda, the
ClassPathProcessApplicationScanner that even discovers the BPMN files, but cannot not match their path (
BOOT-INF/classes/tenants/one after repackaging by SpringBoot) with the root path from the
tenants/one)! So it did not deploy anything, although I even specified the resource paths as relative to the
processes.xml using the
pa: prefix. I played around with the settings, but it either did not work running the JAR while it worked running with Gradle — or the other way round.
There was absolutely no way around it, so I contacted Camunda (luckily I had enterprise support) and they recommended to disable auto deployment and deploy processes manually. So I went and implemented my own auto deployment mechanism and here is how I did it:
Custom deployment with SpringBoot tooling
First of all I decided to get rid of
processes.xml completely and go for YAML to define my process archives. So this is what I came up with:
To be able to use that in my SpringBoot application, I also needed a small class to represent those properties, which is a no-brainer with SpringBoot, especially in Kotlin:
Now I could use these properties inside a Spring EventListener to perform a deployment during the startup phase of the application. Camunda provides its own lifecycle events, so I could use their
PostDeployEvent to make sure that the process engine is ready and to perform the deployment before the whole application is started. In fact, my custom deployment blocks the application startup, which is quite handy when you want to prevent that e.g. message correlations interfere with the deployment.
Don not forget to delete
processes.xml to not interfere with the custom deployment and launch the application. Et voilà:
2021-12-16 11:50:02.742 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Starting Camunda deployment
2021-12-16 11:50:02.742 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Deploying process archive: ProcessArchive(name=All, tenant=null, path=tenants/all)
2021-12-16 11:50:02.745 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Adding resource: tenants/all/message-based-travel.bpmn
2021-12-16 11:50:02.840 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Deployment of 1 resources took 0.090590917 seconds
2021-12-16 11:50:02.840 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Deploying process archive: ProcessArchive(name=One, tenant=1, path=tenants/one)
2021-12-16 11:50:02.841 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Adding resource: tenants/one/message-based-travel.bpmn
2021-12-16 11:50:02.859 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Deployment of 1 resources took 0.017873792 seconds
2021-12-16 11:50:02.859 INFO 15854 --- [ main] d.h.e.d.DeployOnApplicationStart : Camunda deployment finished
To sum the whole thing up: Camunda is not able to properly handle JARs repackaged by SpringBoot, when you want to define different process archives, each with its own
resourceRootPath and there is no way around this using Camunda’s auto deployment.
But deploying manually is pretty easy, using two key features of SpringBoot — ConfigurationProperties and EventListeners. Major benefits from this approach:
- Multiple process archives with own resource paths, able to run as a JAR and with Gradle/Maven or inside your IDE
- Configurable “the SpringBoot way” — in YAML
- Full control of your deployment process, custom logging, metrics and whatever else you want to add
The complete code for this example can be found on GitHub:
GitHub - stefanzilske/camunda-manual-deployment
Camunda auto deployment is deactivated (no processes.xml in resources), deployment of process definitions is done…
Cover photo: Jason Richard/Unsplash