Context
For a Camunda project it is generally recommended as good practice, to do the development using a so-called “Camunda Spring Boot application”. This provides a very fast and out-of-the-box environment to code and test processes. One advantage is that these projects come with embedded infrastructure.
Normally, this is the embedded infrastructure of a Camunda Spring Boot project:
- An embedded application server, example, Tomcat
- An embedded process engine
- An embedded REST engine
- Embedded Camunda Webapps (Cockpit, Tasklist, etc)
- This application is built with Maven and creates an executable JAR by default, containing all the mentioned components
Therefore, that layer, setting up the infrastructure, is taken away from the developer to make development easier and faster. This is an actual Camunda recommendation, see https://camunda.com/best-practices/using-a-greenfield-stack/. One of the reasons why this became a recommendation can be found here: https://camunda.com/best-practices/deciding-about-your-stack/#_using_a_strong_container_managed_strong_engine
For a production-ready Camunda project, things change considerably. If we are part of a relatively big organization, it is most likely that there is already infrastructure in place. External servers that exist somewhere within the realm of our organization, and have everything installed. We are talking about the Shared Container Engine Architecture from Camunda: https://docs.camunda.org/manual/7.14/introduction/architecture/#shared-container-managed-process-engine
To put it in other words, imagine the following scenario: there is already a Camunda installation with a process engine defined and some wars already deployed in a tomcat container. Now, to this tomcat we want to add a war containing only processes and services i.e. beans. We do not want to embed the process engine in my war. This is possible with normal spring integration but there is no documentation about how to do this with spring boot as well.
In order to deploy an application like that onto a production environment, a number of changes are needed for a Camunda Spring Boot Application to work properly. In this article we show you how to do exactly that.
Situation
We need to deploy the same Camunda Spring Boot application to a dedicated application server i.e. an external Tomcat, in other words, Tomcat has been installed in another machine.
Problem
If we try to deploy our Camunda Spring boot application as it is in development, the following problems will come up:
- Application generates by default a jar file, but, a war file is needed
- It has an embedded Tomcat, but, it is not needed as the target is an external Tomcat, they conflict each other
- It has an embedded Camunda engine, but, it is not needed as there is one engine on the external Tomcat
- Application does not know how to load the configuration i.e. beans, DB connection, related files, etc.
- Scenario is not documented, see: https://docs.camunda.org/manual/7.14/user-guide/spring-boot-integration/#supported-deployment-scenarios
Solution
To overcome this situation, we need a mixed version of Spring Boot and Spring Framework.
Adjust project’s maven configuration in pom.xml
1 Add <packaging>war</packaging>, below the version tag, or change it from jar to war if it exists
<artifactId>camunda-spring-boot-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
2 Adjust scope of dependency camunda-bpm-spring-boot-starter-webapp-ee to provided, this removes the embedded Camunda applications (cockpit, takslist, etc)
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter-webapp-ee</artifactId>
<version>${camundaSpringBoot.version}</version>
<scope>provided</scope>
</dependency>
3 Adjust the scope of camunda-bpm-spring-boot-starter-rest to provided, this removes the embedded rest API engine
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
<version>${camundaSpringBoot.version}</version>
<scope>provided</scope>
</dependency>
4 Adjust the scope of all the spin dependencies to provided, this avoids conflict between the libraries that already exist in the project and the libraries that are added to the built artifact.
<dependency>
<groupId>org.camunda.spin</groupId>
<artifactId>camunda-spin-dataformat-all</artifactId>
<scope>provided</scope> <!-- this removes the embedded spin plugin -->
</dependency>
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-plugin-spin</artifactId>
<scope>provided</scope> <!-- this removes the embedded spin plugin -->
</dependency>
<dependency>
<groupId>org.camunda.spin</groupId>
<artifactId>camunda-spin-core</artifactId>
<scope>provided</scope> <!-- this removes the embedded spin plugin -->
</dependency>
5 Add the dependency camunda-engine, and set it to provided. This tells the application that the process engine is provided and does not need to be bundled with the application:
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine</artifactId>
<scope>provided</scope>
</dependency>
6 Add two dependencies for spring framework, one from Camunda and the other from Spring. This helps the application that the process engine has a Spring context and has to load Spring beans:
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-spring</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
7 In the section build, add the following plugin to create a war artifact. This does the war packaging.
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
8 In the section build, delete (if present) the following dependency, that does the packaging for Spring Boot, so that, there are no conflicts between this dependency and the one in step 7
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springBoot.version}</version>
<configuration>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
Change the application code configuration
1 Enable your application as a servlet by extending your main class with SpringBootServletInitializer and override the configuration method in the main class. This tells the application from where to pick the configured process and beans:
public class CamundaApplication extends SpringBootServletInitializer {
public static void main(String... args) {
SpringApplication.run(CamundaApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class <CamundaApplication> applicationClass = CamundaApplication.class;
}
2 Delete your property configuration file inside the folder resources, or tell maven to exclude it in the build phase. This is because that file tells where to find the database for the embedded tomcat but since the dedicated tomcat is completely configured that is not needed.
3 Create a class that is used to configure and bootstrap the application context. Put the class in the same folder as the class with the main method. The purpose of this class is, to tell the application that it is deployed on an external application server, or so-called, shared process engine, and to tell the application to load our beans that are evaluated as expressions in our services tasks.
@Configuration
@ComponentScan(basePackages = "org.example")
public class CamundaApplicationContext {
@Bean
public ProcessEngineService processEngineService() {
return BpmPlatform.getProcessEngineService();
}
@Bean(destroyMethod = "")
public ProcessEngine processEngine() {
return BpmPlatform.getDefaultProcessEngine();
}
@Bean
public SpringProcessApplication processApplication() {
return new SpringProcessApplication();
}
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine) {
return processEngine.getRepositoryService();
}
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine) {
return processEngine.getRuntimeService();
}
@Bean
public TaskService taskService(ProcessEngine processEngine) {
return processEngine.getTaskService();
}
@Bean
public HistoryService historyService(ProcessEngine processEngine) {
return processEngine.getHistoryService();
}
@Bean
public ManagementService managementService(ProcessEngine processEngine) {
return processEngine.getManagementService();
}
}
Create a process.xml file
1 Inside the folder, resources, create a folder META-INF, and inside, create a file called processes.xml. This file contains the deployment metadata for the application. The content of the file can be taken from here, https://docs.camunda.org/manual/7.14/user-guide/process-applications/the-processes-xml-deployment-descriptor/
<process-application xmlns="http://www.camunda.org/schema/1.0/ProcessApplication" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<process-archive name="CamundaApplication">
<process-engine>default</process-engine>
<properties>
<property name="isDeleteUponUndeploy">false</property>
<property name="isScanForProcessDefinitions">true</property>
</properties>
</process-archive>
</process-application>
Without this file, the configuration class created before, cannot bind everything together, both work together. Failing to do so, will give us the false impression that everything is ok. However, problems like: processes that do not start, or suddenly stop with no reason, and similar ones, are very likely to appear.
Build and deploy the application
1 Using maven, perform a “clean and package”. Go to the location where the war file has been built. In IntelliJ that location can be seen in the console:
[INFO] Building war: ...\target\CamundaApplication.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
2 Put the war file in the webapps folder of the application server, and check that Tomcat loads two things, spring and the bpmn processes.
3 Optionally, wrap all these steps inside a Maven profile, for building the project comfortably.
Summary
Camunda recommends to use spring boot for your project development, see https://camunda.com/best-practices/using-a-greenfield-stack/
However, there is little documentation for what to do when this application has to be deployed on a production environment.
The solution lies in two things to understand, how to tell the application that:
- the infrastructure is already in place and the embedded components are not needed
- Some configuration is already provided, and some other things like beans are still part of the application’s responsibility, but they have to be registered in the existing infrastructure
This solution might look laborious, but it is easier with the help of Maven profiles. One can automatically build the same project for a development and a production profile, meaning, this changes can be made once. Then, with the help of maven profiles, the build can pick up a different configuration.
Links to resources to understand more
https://docs.camunda.org/get-started/spring/shared-process-engine/
https://maven.apache.org/guides/introduction/introduction-to-profiles.html
https://docs.camunda.org/manual/7.14/user-guide/spring-boot-integration/process-applications/
Photo on Unsplash