Join us September 22-23 for CamundaCon - a virtual conference dedicated to process automation

Icon Close REGISTER FOR FREE

The Micronaut Framework is known for its efficient use of resources. It’s getting more popular and is an alternative to Spring Boot. In this article we show how Camunda can be run as an embedded engine inside a Micronaut application with the use of our open source integration project. Additionally, we’ll show how to implement an external task worker running on Micronaut. Finally, we optimize the external worker using GraalVM to start and connect in milliseconds!

Motivation

Camunda’s vision is to “Automate Any Process, Anywhere” and there are good reasons to also consider Micronaut. Micronaut is a rather new JVM-based microframework (comparable to Quarkus) with which you can implement your microservices (and more — like lambda functions).

In Camunda projects we often see the Camunda Platform Spring Boot Starter being used to embed Camunda Platform in a Spring Boot Application. However, Spring Applications tend to have increasing startup times and high memory usage with every Starter you add. The reason is mainly that Spring Boot Starters do a lot of magic at runtime by scanning the classpath, using reflection, creating proxies, creating caches, and modifying byte code.

Micronaut has solved this problem by using ahead-of-time (AOT) compilation in the build phase. The development model is actually very close to Spring Boot, i.e. you have your container-managed beans and you can inject them where needed. However, you have the advantage that the magic is done at compile time and more errors are found during the build (which takes a little bit longer but Gradle takes good care of only building deltas).

Novatec is successfully deploying Micronaut applications to production in Kubernetes clusters. My colleagues are experiencing three times faster boot up times and 40% less memory than comparable containerized Spring Boot applications. Actually, running in resource limited environments like Kubernetes will make more of a difference than running the applications natively on a local environment. Of course this highly depends on the use case but faster startup and less memory is generally desirable – not only when scaling up and down in a clustered environment.

We started two open source projects on GitHub which support the integration of Micronaut and Camunda Platform. The first project is about running the Camunda process engine embedded in your Micronaut microservice. The second project simplifies the implementation of external task workers on Micronaut. Together with the GraalVM, you can build external workers which connect to any Camunda Server (i.e. the Server must not be based on Micronaut) in milliseconds!

I’ll now introduce you to the two projects which are being developed by Novatec with the support of the open source community.

Workflow Engine Integration

Embedding the Process Engine

Our vision from the beginning was that embedding the Camunda process engine into a Micronaut project would be as simple as adding a dependency.

This is all you need for Gradle:

implementation("info.novatec:micronaut-camunda-bpm-feature:1.0.0")
runtimeOnly("com.h2database:h2")

Note: You can alternatively use Maven. However, my impression is that the Gradle support is a bit better when developing Micronaut applications.

By including the dependency you already get quite a few nice features:

  • Using H2 as an in-memory database is as simple as adding a dependency. Other data sources can be configured via properties.
  • BPMN process models and DMN decision tables are automatically deployed for all configured locations.
  • The Camunda process engine with its job executor is started automatically – but the job executor is disabled for tests by default.
  • The process engine and related services, e.g. RuntimeService, RepositoryService, …, are provided as lazy initialized beans and can be injected.
  • Micronaut beans are resolved from the application context if they are referenced by expressions or Java class names within the process models.
  • The process engine integrates with Micronaut’s transaction manager. Optionally, micronaut-data-jdbc or micronaut-data-jpa are supported.
  • The process engine can be configured with generic properties.
  • The Camunda REST API and the Webapps are supported (currently only for Jetty).
  • The Camunda Enterprise Edition (EE) is supported.
  • Process Engine Plugins are automatically activated on start.
  • The job executor uses the Micronaut IO Executor’s thread pools.
  • The process engine configuration and the job executor configuration can be customized programmatically.
  • A Camunda admin user is created if configured by properties and not present yet (including admin group and authorizations).
  • Camunda’s telemetry feature is automatically deactivated during test execution.

We configure Camunda Platform with sensible defaults, so that you can get started with minimum configuration.

The following picture shows how the application’s components fit together. The Micronaut Camunda Integration is the bi-directional link between Micronaut Core and the embedded Camunda Process Engine. This is packaged together with your application code and your process models.

how the application’s components fit together

To create a new project which includes the Micronaut Camunda integration go to Micronaut Launch and select the “camunda” feature. Yes, our integration project is an official Micronaut feature! You can then download the minimal project which already includes the required dependencies.

micronaut launch

You can immediately start your minimal application from the command line or your IDE – I prefer IntelliJ IDEA.

To deploy a process model create an executable BPMN file and save it in the resources’ root. When starting the application you’ll see the logs saying:

Deploying model: helloworld.bpmn

Inject the process engine or any of the Camunda services using constructor injection:

@Singleton
public class MyComponent {

    private final ProcessEngine processEngine;
    private final RuntimeService runtimeService;
    
    public MyComponent(ProcessEngine processEngine, RuntimeService runtimeService) {
        this.processEngine = processEngine;
        this.runtimeService = runtimeService;
    }

    // ...
}

Alternatively to constructor injection, you can also use field injection, Java bean property injection, or method parameter injection. You can then for example use the runtimeService to start new processes instances or correlate existing process instances.

To invoke a Java Delegate first create a singleton bean:

@Singleton
public class LoggerDelegate implements JavaDelegate {
 
  private static final Logger log = LoggerFactory.getLogger(LoggerDelegate.class);
 
  @Override
  public void execute(DelegateExecution delegateExecution) {
    log.info("Hello World: {}", delegateExecution);
  }
}

and then reference it in the process model with the following expression:

${loggerDelegate}

Further Topics

There are a few more topics you can explore:

  • enable the Camunda Webapps (Cockpit, Tasklist, and Admin) and the REST interface via configuration.
  • Configure the Camunda Enterprise Edition
  • Custom process engine and job exectuor configuration
  • Transaction Management
  • Process Tests

For a more detailed “Getting Started” and the advanced topics see our project page on GitHub at https://github.com/NovatecConsulting/micronaut-camunda-bpm.

External Task Worker

Pattern

A very common element in process models are service tasks which implement code execution. Service tasks can have different implementations – one being the external task pattern. This pattern supports separation of concerns, decoupling from the process, horizontal scaling, free choice of programming language. The following picture shows the external task pattern:

external task pattern

With the external task pattern you externalize the service task logic to a separate application which fetches tasks, processes them, and returns the result. The tasks are processed asynchronously by the workers, so no threads are blocked in the process engine and no incidents occur if a worker is temporarily unavailable. The process will then handle reported errors, timeouts, and take care of the next steps. 

Actually, you could simply use Camunda’s External Client for Java in your Micronaut application. However, this would require some boilerplate code and configurability would also need to be implemented as needed. Therefore, we started an open source project which makes it as simple as pulling in a dependency, implementing an interface, and setting some configuration properties. This is similar to the Camunda Spring Boot Starter for the External Task Client .

Code Example

If you don’t have an existing Micronaut project, create a new project using Micronaut Launch.

Add the dependency, e.g. with Gradle (Maven also works):

implementation("info.novatec:micronaut-camunda-external-client-feature:1.0.0")

Then implement the interface “org.camunda.bpm.client.task.ExternalTaskHandler” and declare the class as a bean using the “@Singleton” annotation , e.g.

import info.novatec.micronaut.camunda.external.client.feature.ExternalTaskSubscription;
import org.camunda.bpm.client.task.ExternalTask;
import org.camunda.bpm.client.task.ExternalTaskHandler;
import org.camunda.bpm.client.task.ExternalTaskService;

import javax.inject.Singleton;

@Singleton
@ExternalTaskSubscription(topicName = "my-topic")
public class ExampleHandler implements ExternalTaskHandler {

  @Override
  public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) {
    // Put your business logic here
    
    externalTaskService.complete(externalTask);
  }
}

You also need to configure the base URL, e.g. in your application.yml

camunda.external-client:
  base-url: http://localhost:8080/engine-rest

This can then be built as a Fat/Uber/Shadow JAR and started:

./gradlew run

Start-up time is about 850ms which is quite good. However, this can be improved with GraalVM – read on!

GraalVM

Micronaut applications can be compiled as native applications using GraalVM. With GraalVM the Micronaut based external worker will start up and connect in about 35 milliseconds and use even less memory.

There are some drawbacks: the compilation takes a few minutes and the application will only run on a single platform (you could put this in a Docker image to run it anywhere).

A good use case for minimal start up times and memory usage is the cloud where you can scale your army of workers up and down as needed. The workers will be available almost instantly and use less memory – thereby reducing costs. So even if the startup time is not critical for you, the reduced memory consumption should convince anyone deploying to the cloud.

When creating native images, a static code analysis determines which code is reachable. However, there are cases in which usages cannot be predicted, e.g. reflection or dynamic class loading. If code paths are not detected this will result in runtime errors. Luckily, you can configure an agent which will intercept all calls of a regular JVM execution to generate a configuration file which can subsequently be used to generate the native image.

With this setup our native external task worker starts and connects within 35 milliseconds! 

You can find more details regarding setup, configuration, and build at https://github.com/camunda-community-hub/micronaut-camunda-external-client

Jump to the “Getting Started” section of https://github.com/camunda-community-hub/micronaut-camunda-external-client to implement your first worker! You will also find more examples there.

Feedback

Camunda’s telemetry data shows a growing number of installations running Camunda on Micronaut. Unfortunately, we’re probably not aware of most installations of our open source projects. However, we’re in contact with projects going live on production this year. That’s quite exciting!

You’re welcome to try these two projects and we’d love to hear your feedback and discuss your ideas! If you like our work please give us a star on GitHub and join the stargazers (micronaut-camunda-bpm and micronaut-camunda-external-client).

Resources