This open source project allows you to implement a Zeebe client with the Micronaut Framework. You can connect to Camunda Platform 8 (previously Camunda Cloud) or your self-managed Zeebe Cluster.
With this integration you can implement a Zeebe job worker with minimal boilerplate code to process tasks. Additionally, you can use the client to deploy process models, and start and cancel process instances.
The Micronaut Framework is known for its efficient use of resources. If you use GraalVM you have startup times of about 35ms!
The integration is preconfigured with sensible defaults, so that you can get started with minimal configuration: simply add a dependency and your Camunda Platform 8 credentials in your Micronaut project!
If you are interested in using Camunda Platform on a Micronaut application instead, have a look at our open source project micronaut-camunda-bpm.
We're not aware of all installations of our Open Source project. However, we love to
- listen to your feedback,
- discuss possible use cases with you,
- align our roadmap to your needs!
📨 Please contact us!
Do you want to try it out? Please jump to the Getting Started section.
Do you want to contribute to our open source project? Please read the Contribution Guidelines and contact us.
Micronaut Framework + Camunda Platform 8 = ❤️
- ✨ Features
- 🚀 Getting Started
- 🏆 Advanced Topics
- 📚 Releases
- 📆 Publications
- 📨 Contact
- Camunda external client can be integrated by simply adding a dependency to your project.
- A worker can subscribe to multiple topics.
- The worker's external task client can be configured with properties.
This section describes what needs to be done to use micronaut-zeebe-client-feature
in a Micronaut project.
The Zeebe integration works with both Gradle and Maven, but we recommend using Gradle because it has better Micronaut Support.
You have the following options to integrate the Zeebe integration:
-
Create a new Micronaut project using Micronaut Launch and check that the "camunda-zeebe" feature is selected.
-
Manually add the dependency to a Micronaut project:
Click to show Gradle configuration
Add the dependency to the build.gradle file:
implementation("info.novatec:micronaut-zeebe-client-feature:1.19.0")
Click to show Maven configuration
Add the dependency to the pom.xml file:
<dependency> <groupId>info.novatec</groupId> <artifactId>micronaut-zeebe-client-feature</artifactId> <version>1.19.0</version> </dependency> <!-- workaround for https://github.com/camunda-community-hub/micronaut-zeebe-client/issues/88 --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.19.3</version> </dependency>
Note: The module micronaut-zeebe-client-feature
includes the dependency io.camunda:zeebe-client-java
which will be resolved transitively.
The minimal configuration requires you to provide a handler for a specific topic. You can register multiple handlers in this way for different topics.
On start of the application the external task client will automatically connect to Zeebe and start fetching tasks.
To register a handler you can annotate a method with ZeebeWorker
.
Example handler:
import info.novatec.micronaut.zeebe.client.feature.ZeebeWorker;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import jakarta.inject.Singleton;
@Singleton
public class ExampleHandler {
@ZeebeWorker(type = "my-type")
public void doSomething(JobClient client, ActivatedJob job) {
// Put your business logic here
client.newCompleteCommand(job.getKey()).send()
.exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
}
}
To register a handler you can annotate a class implementing the JobHandler
interface with ZeebeWorker
.
Example handler:
import info.novatec.micronaut.zeebe.client.feature.ZeebeWorker;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.client.api.worker.JobHandler;
import jakarta.inject.Singleton;
@Singleton
@ZeebeWorker(type = "my-type")
public class ExampleHandler implements JobHandler {
@Override
public void handle(JobClient client, ActivatedJob job) {
// Put your business logic here
client.newCompleteCommand(job.getKey()).send()
.exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
}
}
The annotation accepts the following properties, more will be added later:
Property | Description |
---|---|
type | The mandatory the type of jobs to work on. |
timeout | The optional time for how long a job is exclusively assigned for this worker, e.g "PT15M" |
maxJobsActive | The optional maximum number of jobs which will be exclusively activated for this worker at the same time. |
requestTimeout | The optional request timeout for activate job request used to poll for new job, e.g. PT20S. |
pollInterval | The optional maximal interval between polling for new jobs, e.g. PT0.1S for 100ms. |
Note: If no value is provided for an optional property then the default will be taken from the configuration as documented below.
You may use the following properties (typically in application.yml) to configure the Zeebe client.
Prefix | Property | Default | Description |
---|---|---|---|
zeebe.client.cloud | .cluster-id | The cluster ID when connecting to Camunda Platform 8. Don't set this for a local Zeebe Broker. | |
.client-id | The client ID to connect to Camunda Platform 8. Don't set this for a local Zeebe Broker. | ||
.client-secret | The client secret to connect to Camunda Platform 8. Don't set this for a local Zeebe Broker. | ||
.region | bru-2 | The region of the Camunda Platform 8 cluster. | |
.gateway-address | 0.0.0.0:26500 | The gateway address if you're not connecting to Camunda Platform 8. Must be in format host:port. | |
.use-plain-text-connection | true | Whether to use plain text or a secure connection. This property is not evaluated if connecting to Camunda Platform 8 because that will always use a secure connection. | |
.default-request-timeout | PT20S | The request timeout used if not overridden by the command. | |
.default-job-poll-interval | 100 | The interval which a job worker is periodically polling for new jobs. | |
.default-job-timeout | PT5M | The timeout which is used when none is provided for a job worker. | |
.default-message-time-to-live | PT1H | The time-to-live which is used when none is provided for a message. | |
.default-job-worker-name | default | The name of the worker which is used when none is set for a job worker. | |
.num-job-worker-execution-threads | 1 | The number of threads for invocation of job workers. Setting this value to 0 effectively disables subscriptions and workers. | |
.keep-alive | PT45S | Time interval between keep alive messages sent to the gateway. | |
.ca-certificate-path | default store | Path to a root CA certificate to be used instead of the certificate in the default keystore. |
Here are some example applications:
- Example application which uses the feature.
- Internal example application used during development. Remember that you need to start the Zeebe Cluster first.
The current releases are built with Micronaut 4 and support the following JDKs:
- JDK 21 (LTS)
The lastest release supporting Micronaut 3 is 1.15.0 which supports the following JDKs:
- JDK 8 (LTS)
- JDK 11 (LTS)
- JDK 17 (LTS)
Process tests can be implemented with JUnit 5 and JDK 11 and newer by adding the Zeebe Process Test library as a dependency:
Click to show Gradle dependencies
testImplementation("io.camunda:zeebe-process-test:1.3.0")
Click to show Maven dependencies
<dependency>
<groupId>io.camunda</groupId>
<artifactId>zeebe-process-test</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
and then implement the unit test with the @ZeebeProcessTest
but without the @MicronautTest
annotation:
import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.response.ActivateJobsResponse;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.response.DeploymentEvent;
import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
import io.camunda.zeebe.process.test.assertions.BpmnAssert;
import io.camunda.zeebe.process.test.assertions.ProcessInstanceAssert;
import io.camunda.zeebe.process.test.extensions.ZeebeProcessTest;
import io.camunda.zeebe.process.test.testengine.InMemoryEngine;
import io.camunda.zeebe.process.test.testengine.RecordStreamSource;
import org.junit.jupiter.api.Test;
@ZeebeProcessTest
class ProcessTest {
InMemoryEngine engine;
ZeebeClient client;
@SuppressWarnings("unused")
RecordStreamSource recordStreamSource;
@Test
void workerShouldProcessWork() {
// Deploy process model
DeploymentEvent deploymentEvent = client.newDeployCommand()
.addResourceFromClasspath("bpmn/say_hello.bpmn")
.send()
.join();
BpmnAssert.assertThat(deploymentEvent);
// Start process instance
ProcessInstanceEvent event = client.newCreateInstanceCommand()
.bpmnProcessId("Process_SayHello")
.latestVersion()
.send()
.join();
engine.waitForIdleState();
// Verify that process has started
ProcessInstanceAssert processInstanceAssertions = BpmnAssert.assertThat(event);
processInstanceAssertions.hasPassedElement("start");
processInstanceAssertions.isWaitingAtElement("say_hello");
// Fetch job: say-hello
ActivateJobsResponse response = client.newActivateJobsCommand()
.jobType("say-hello")
.maxJobsToActivate(1)
.send()
.join();
// Complete job: say-hello
ActivatedJob activatedJob = response.getJobs().get(0);
client.newCompleteCommand(activatedJob.getKey()).send().join();
engine.waitForIdleState();
// ...
// Verify completed
engine.waitForIdleState();
processInstanceAssertions.isCompleted();
}
}
See also a test in our example application: ProcessTest
Adding a health endpoint for monitoring purposes in a cloud environment can be achieved by adding the dependency:
runtimeOnly 'io.micronaut:micronaut-management'
The health endpoint can be retrieved by calling GET
on /health
NOTE: If you don't need a health endpoint you can safely remove the runtime dependency runtime("netty")
from your project.
The application will then run as a CLI application without the embedded server.
With GraalVM you can reduce start-up time and memory usage even more! For example, on a developer environment the start-up time will drop to about 35ms!
The following instructions are based on macOS - other operating systems will probably be similar. Feel free to create a pull request with updated instructions for other operating systems.
Install the gu
executable to be able to install native-image
based on instructions: https://www.graalvm.org/docs/getting-started/macos/ which links to https://github.com/graalvm/graalvm-ce-builds/releases/latest
tar -xvf graalvm-ce-java17-darwin-amd64-21.3.0.tar.gz
sudo mv graalvm-ce-java17-21.3.0 /Library/Java/JavaVirtualMachines
/usr/libexec/java_home -V
sudo xattr -r -d com.apple.quarantine /Library/Java/JavaVirtualMachines/graalvm-ce-java17-21.3.0
export PATH=/Library/Java/JavaVirtualMachines/graalvm-ce-java17-21.3.0/Contents/Home/bin:$PATH
gu install native-image
native-image --version
Install GraalVM using SDKMAN!:
curl -s "https://get.sdkman.io" | bash
sdk install java 21.3.0.r17-grl
sdk use java 21.3.0.r17-grl
export PATH=/Library/Java/JavaVirtualMachines/graalvm-ce-java17-21.3.0/Contents/Home/bin:$PATH
export JAVA_HOME=/Library/Java/JavaVirtualMachines/graalvm-ce-java17-21.3.0/Contents/Home
cd micronaut-zeebe-client-example
../gradlew clean build
mkdir -p src/main/resources/META-INF/native-image
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/ -jar build/libs/micronaut-zeebe-client-example-0.0.1-SNAPSHOT-all.jar
Start the server with the provided docker-compose.yml and cancel the client with Ctrl-C
once you see that the client is running when it repeatedly logs like Retrieved value 37. Goodbye, from job 4503599627392427
.
The generated reflect-config.json
misses three entries (why?) which we add manually:
{
"name":"io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.grpc.internal.PickFirstLoadBalancerProvider",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.grpc.internal.DnsNameResolverProvider",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
Now build the native image - note: this will take a few minutes:
../gradlew clean nativeCompile
You can then start the external client (Note: Server must be running):
build/native/nativeCompile/micronaut-zeebe-client-example
The application will be up and processing the first tasks in about 35ms (!):
INFO io.micronaut.runtime.Micronaut - Startup completed in 33ms. Server Running: http://localhost:8087
INFO i.n.m.z.c.example.GreetingHandler - Hello world, from job 2251799813709648
INFO io.camunda.zeebe.client.job.poller - Activated 1 jobs for worker default and job type say-hello
INFO i.n.m.z.c.example.GoodbyeHandler - Retrieved value 18. Goodbye, from job 2251799813709653
INFO io.camunda.zeebe.client.job.poller - Activated 1 jobs for worker default and job type say-goodbye
INFO i.n.m.z.c.example.GreetingHandler - Hello world, from job 4503599627394811
INFO io.camunda.zeebe.client.job.poller - Activated 1 jobs for worker default and job type say-hello
The list of releases contains a detailed changelog.
We use Semantic Versioning.
The following compatibility matrix shows the officially supported Micronaut and Zeebe versions for each release. Other combinations might also work but have not been tested.
Release | Micronaut Framework | Zeebe |
---|---|---|
1.19.0 | 4.3.4 | 8.6.0 |
Click to see older releases
Release | Micronaut Framework | Zeebe |
---|---|---|
1.18.1 | 4.3.4 | 8.5.0 |
1.17.0 | 4.3.4 | 8.4.4 |
1.16.0 | 4.1.0 | 8.3.3 |
1.15.0 | 3.9.4 | 8.2.1 |
1.14.0 | 3.9.0 | 8.2.1 |
1.13.0 | 3.9.0 | 8.1.6 |
1.12.0 | 3.8.9 | 8.1.6 |
1.11.0 | 3.8.0 | 8.1.5 |
1.10.0 | 3.7.1 | 8.1.0 |
1.9.0 | 3.6.1 | 8.0.5 |
1.8.0 | 3.5.2 | 8.0.3 |
1.7.0 | 3.4.1 | 8.0.0 |
1.6.0 | 3.4.0 | 1.3.5 |
1.5.0 | 3.4.0 | 1.3.5 |
1.4.1 | 3.3.4 | 1.3.5 |
1.4.0 | 3.3.0 | 1.3.2 |
1.3.1 | 3.3.0 | 1.3.2 |
1.3.0 | 3.3.0 | 1.3.1 |
1.2.2 | 3.2.7 | 1.3.1 |
1.2.1 | 3.2.7 | 1.3.1 |
1.2.0 | 3.2.6 | 1.3.0 |
1.1.1 | 3.2.3 | 1.2.7 |
1.1.0 | 3.2.0 | 1.2.4 |
1.0.1 | 3.1.3 | 1.2.4 |
1.0.0 | 3.1.0 | 1.2.2 |
0.0.1 | 3.0.2 | 1.1.3 |
Download of Releases:
- 2022-02: Bringing Cloud Native Process Automation to the Micronaut Framework
Blogpost by Tobias Schäfer and Stefan Schultz
If you have any questions or ideas feel free to create an issue or contact us via GitHub Discussions.
We love listening to your feedback, and of course also discussing the project roadmap and possible use cases with you!
This open source project is being developed by envite consulting GmbH and Novatec Consulting GmbH with the support of the open source community.