Blog

16.12.2020. | Author: Gerardo Manzano

How to deploy a Camunda Spring Boot application to an external application server

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

More Blog articles

04.04.2022.

Maler oder Macher sein

Author: Mirko Pawlak

Digitale Transformation ohne Grenzen. Ich bin Prozessmacher. Ich hocke tief drinnen im Maschinenraum und schau mir an, was das Unternehmen einzigartig macht. Ich tu das gern, denn jeder Prozess ist lebendig und einzigartig. Es gibt einen Anfang, dann passiert etwas und am Ende ist es vorbei. Wie geht man am besten mit seinen Prozessen um? […]

read more
02.12.2021.

How to deploy a Camunda Spring Boot application to a shared application server

Author: Maximilian Kamenicky

Context Do you still remember our Blog: How to deploy a Camunda Spring Boot application to an external application server? In that blog, my colleague Gerardo Manzano Garcia explained the steps necessary to run a spring boot application on a dedicated application server i.e. an external Tomcat. In other words, run the spring boot application […]

read more
15.11.2021.

Ways to integrate Kafka with Databases

Author: Denis Savenko

Kafka is a great instrument. It is widely used in event-driven architectures, and it does the job perfectly fine. However, this is not the only use-case for it. Kafka is also very well suitable for data replication and ingestion use-cases, as well as for building modern ETL or ELT pipelines. The most noteworthy reasons for […]

read more