Setting up your first development project
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
Approximate time to complete: 1 hour
The Zeebe C# Client is available for .NET Zeebe applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
Estimated time to complete: 60 minutes
The Zeebe Go Client is available for Go applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
The Spring Zeebe Client is available for Spring and Spring Boot applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
The Spring Zeebe Client is available for Spring and Spring Boot applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
The Zeebe Node Client exists for Node.js applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
Prerequisites​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
Scaffolding the project​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
- Create a new .NET Core Web API application:
dotnet new webapi -o Cloudstarter
cd Cloudstarter
- Add the Zeebe C# Client from Nuget:
dotnet add package zb-client --version 0.16.1
Configure NLog for logging
- Install NLog packages (we'll use NLog):
dotnet add package NLog
dotnet add package NLog.Schema
dotnet add package NLog.Web.AspNetCore
- Create a file
NLog.config
, with the following content:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
>
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target name="logconsole" xsi:type="Console"
layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message} ${exception:format=tostring}"/>
 </targets>
<rules>
  <logger name="*" minlevel="Trace" writeTo="logconsole" />
 </rules>
</nlog>
- Edit the file
Program.cs
to configure NLog:
public class Program
{
public static async Task Main(string[] args)
{
var logger = NLogBuilder.ConfigureNLog("NLog.config").GetCurrentClassLogger();
try
{
logger.Debug("init main");
await CreateHostBuilder(args).Build().RunAsync();
}
catch (Exception exception)
{
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
}
- Run the following command to create a new Go project:
mkdir -p $GOPATH/src/github.com/$USER/cloud-starter
cd $GOPATH/src/github.com/$USER/cloud-starter
- Add the Zeebe Go Client to the project:
go get -u github.com/zeebe-io/zeebe/clients/go/...
Download a maven Spring starter from here.
Unzip it into a new directory.
- Add the Spring Zeebe Client dependency to the
pom.xml
file:
<dependency>
<groupId>io.zeebe.spring</groupId>
<artifactId>spring-zeebe-starter</artifactId>
<version>0.23.0</version>
</dependency>
Download a maven Spring starter from here.
Unzip it into a new directory.
- Add the Spring Zeebe Client dependency to the
pom.xml
file:
<dependency>
<groupId>io.zeebe.spring</groupId>
<artifactId>spring-zeebe-starter</artifactId>
<version>0.23.0</version>
</dependency>
- Install tools:
npm i -g typescript ts-node
- Create project:
mkdir camunda-cloud-get-started-node
cd camunda-cloud-get-started-node
npm init -y
tsc --init
- Edit
tsconfig.json
with the following config:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
- Install
zeebe-node
anddotenv
:
npm i zeebe-node dotenv
Create Camunda Cloud cluster​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
- Log in to https://camunda.io.
- Create a new Zeebe Cluster.
- When the new cluster appears in the console, create a new set of client credentials.
- Copy the client Connection Info environment variables block.
Configure connection​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
- Add the
dotenv.net
package to the project:
dotnet add package dotenv.net.DependencyInjection.Microsoft
- Edit
Startup.cs
and add the service in theConfigureServices
method:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddEnv(builder => {
builder
.AddEnvFile("CamundaCloud.env")
.AddThrowOnError(false)
.AddEncoding(Encoding.ASCII);
});
services.AddEnvReader();
}
- Create a file in the root of the project
CamundaCloud.env
, and paste the client connection details into it, removing theexport
from each line:
ZEEBE_ADDRESS=656a9fc4-c874-49a3-b67b-20c31ae12fa0.zeebe.camunda.io:443
ZEEBE_CLIENT_ID=~2WQlDeV1yFdtePBRQgsrNXaKMs4IwAw
ZEEBE_CLIENT_SECRET=3wFRuCJb4YPcKL4W9Fn7kXlsepSNNJI5h7Mlkqxk2E.coMEtYdA5E58lnkCmoN_0
ZEEBE_AUTHORIZATION_SERVER_URL=https://login.cloud.camunda.io/oauth/token
Note: if you change cluster configuration at a later date, you may need to delete the file ~/zeebe/cloud.token
. See this bug report.
- Add an
ItemGroup
inCloudStarter.csproj
to copy the.env
file into the build:
<ItemGroup>
<None Update="CamundaCloud.env" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
- Create a file in
Services/ZeebeService.cs
, with the following content:
namespace Cloudstarter.Services
{
public interface IZeebeService
{
public Task<ITopology> Status();
}
public class ZeebeService: IZeebeService
{
private readonly IZeebeClient _client;
private readonly ILogger<ZeebeService> _logger;
public ZeebeService(IEnvReader envReader, ILogger<ZeebeService> logger)
{
_logger = logger;
var authServer = envReader.GetStringValue("ZEEBE_AUTHORIZATION_SERVER_URL");
var clientId = envReader.GetStringValue("ZEEBE_CLIENT_ID");
var clientSecret = envReader.GetStringValue("ZEEBE_CLIENT_SECRET");
var zeebeUrl = envReader.GetStringValue("ZEEBE_ADDRESS");
char[] port =
{
'4', '3', ':'
};
var audience = zeebeUrl?.TrimEnd(port);
_client =
ZeebeClient.Builder()
.UseGatewayAddress(zeebeUrl)
.UseTransportEncryption()
.UseAccessTokenSupplier(
CamundaCloudTokenProvider.Builder()
.UseAuthServer(authServer)
.UseClientId(clientId)
.UseClientSecret(clientSecret)
.UseAudience(audience)
.Build())
.Build();
}
public Task<ITopology> Status()
{
return _client.TopologyRequest().Send();
}
}
}
- Save the file.
We will use GoDotEnv to environmentalize the client connection credentials.
- Add GoDotEnv to the project:
go get github.com/joho/godotenv
- Add the client connection credentials for your cluster to the file
.env
:
Note: make sure to remove the export
keyword from each line.
ZEEBE_ADDRESS='aae86771-0906-4186-8d82-e228097e1ef7.zeebe.camunda.io:443'
ZEEBE_CLIENT_ID='hj9PHRIiRqT0~qHvFeqXZV-J8fLRfifB'
ZEEBE_CLIENT_SECRET='.95Vlv6joiuVR~mJDjGPlyYk5Pz6iIwFYmmQyX8yU3xdB1gezntVMoT1SQTdrCsl'
ZEEBE_AUTHORIZATION_SERVER_URL='https://login.cloud.camunda.io/oauth/token'
- Save the file.
- Add the client connection credentials for your cluster to the file
src/main/resources/application.properties
:
zeebe.client.cloud.clusterId=3b640f45-0dcd-469a-8551-7f68a5d4f53b
zeebe.client.cloud.clientId=rvQhH1LgzZ8hWxYpnX-WCFoqxl3ps6_o
zeebe.client.cloud.clientSecret=Y_tumI88mpbDbxlY0ueVyPK6BHjMAe5FpBtPU4TQPPyr4FuDxpMN7P9Mj7M26j6a
zeebe.client.worker.defaultName=myworker
- Save the file.
- Add the client connection credentials for your cluster to the file
src/main/resources/application.properties
:
zeebe.client.cloud.clusterId=3b640f45-0dcd-469a-8551-7f68a5d4f53b
zeebe.client.cloud.clientId=rvQhH1LgzZ8hWxYpnX-WCFoqxl3ps6_o
zeebe.client.cloud.clientSecret=Y_tumI88mpbDbxlY0ueVyPK6BHjMAe5FpBtPU4TQPPyr4FuDxpMN7P9Mj7M26j6a
zeebe.client.worker.defaultName=myworker
- Save the file.
- Create a file
.env
in the root of the project - Paste the client connection environment variable block
- Delete the
export
from in front of each line in the file
You will end up something that looks like this:
ZEEBE_ADDRESS='231bb36a-1588-4f1e-b4f6-e09944d7efd7.zeebe.camunda.io:443'
ZEEBE_CLIENT_ID='Ny-WTmQniq4XluEG0_L9KAl-G8~i_dH1'
ZEEBE_CLIENT_SECRET='9QZWpArT_2C1jU7Kru3Kll~7Hev9jyMsuo5tCk2ko0ZpzNRDb7nbiVqmcUBL'
ZEEBE_AUTHORIZATION_SERVER_URL='https://login.cloud.camunda.io/oauth/token'
- Save the file.
Test Connection with Camunda Cloud​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will create a controller route at /status
that retrieves the status and topology of the cluster.
- Create a file
Controllers/ZeebeController.cs
, with the following content:
namespace Cloudstarter.Controllers
{
public class ZeebeController : Controller
{
private readonly IZeebeService _zeebeService;
public ZeebeController(IZeebeService zeebeService)
{
_zeebeService = zeebeService;
}
[Route("/status")]
[HttpGet]
public async Task<string> Get()
{
return (await _zeebeService.Status()).ToString();
}
}
}
- Edit the file
Startup.cs
, and inject theZeebeService
class into the service container in theConfigureServices
method, like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IZeebeService, ZeebeService>();
services.AddControllers();
}
- Run the application with the command
dotnet run
(remember to set the client connection variables in the environment first).
Note: you can use dotnet watch run
to automatically restart your application when you change your code.
- Open http://localhost:5000/status in your web browser.
You will see the topology response from the cluster.
- Paste the following code into the file
main.go
:
package main
import (
"context"
"fmt"
"github.com/joho/godotenv"
"github.com/zeebe-io/zeebe/clients/go/pkg/pb"
"github.com/zeebe-io/zeebe/clients/go/pkg/zbc"
"log"
"os"
)
func main() {
zbClient := getClient()
getStatus(zbClient)
}
func getClient() zbc.Client {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
gatewayAddress := os.Getenv("ZEEBE_ADDRESS")
zbClient, err := zbc.NewClient(&zbc.ClientConfig{
GatewayAddress: gatewayAddress,
})
if err != nil {
panic(err)
}
return zbClient
}
func getStatus(zbClient zbc.Client) {
ctx := context.Background()
topology, err := zbClient.NewTopologyCommand().Send(ctx)
if err != nil {
panic(err)
}
for _, broker := range topology.Brokers {
fmt.Println("Broker", broker.Host, ":", broker.Port)
for _, partition := range broker.Partitions {
fmt.Println(" Partition", partition.PartitionId, ":", roleToString(partition.Role))
}
}
}
func roleToString(role pb.Partition_PartitionBrokerRole) string {
switch role {
case pb.Partition_LEADER:
return "Leader"
case pb.Partition_FOLLOWER:
return "Follower"
default:
return "Unknown"
}
}
Run the program with the command
go run main.go
.You will see output similar to the following:
2020/07/29 06:21:08 Broker zeebe-0.zeebe-broker-service.aae86771-0906-4186-8d82-e228097e1ef7-zeebe.svc.cluster.local : 26501
2020/07/29 06:21:08 Partition 1 : Leader
This is the topology response from the cluster.
- Annotate the
CloudStarterApplication
class in the filesrc/main/java/io.camunda/CloudStarterApplication.java
with the@EnableZeebeClient
annotation, and add the@Autowired
ZeebeClientLifecycle
property:
@SpringBootApplication
@EnableZeebeClient
public class CloudStarterApplication {
@Autowired
private ZeebeClientLifecycle client;
}
- Add the
@RestController
annotation to the class, and create a REST mapping that returns the cluster topology:
@SpringBootApplication
@RestController
@EnableZeebeClient
public class CloudStarterApplication {
@Autowired
private ZeebeClientLifecycle client;
public static void main(String[] args) {
SpringApplication.run(CloudStarterApplication.class, args);
}
@GetMapping("/status")
public String getStatus() {
Topology topology = client.newTopologyRequest().send().join();
return topology.toString();
}
}
Run the application with the command
mvn spring-boot:run
.Open http://localhost:8080/status in your web browser.
You will see the topology response from the cluster.
- Annotate the
CloudStarterApplication
class in the filesrc/main/java/io.camunda/CloudStarterApplication.kt
with the@EnableZeebeClient
annotation, and add the@Autowired
ZeebeClientLifecycle
property:
@SpringBootApplication
@EnableZeebeClient
class CloudstarterApplication {
@Autowired
private val client: ZeebeClientLifecycle? = null
fun main(args: Array<String>) {
runApplication<CloudstarterApplication>(*args)
}
}
- Add the
@RestController
annotation to the class, and create a REST mapping that returns the cluster topology:
@SpringBootApplication
@EnableZeebeClient
@RestController
class CloudstarterApplication {
@Autowired
private val client: ZeebeClientLifecycle? = null
@GetMapping("/status")
fun getStatus(): String? {
val topology = client!!.newTopologyRequest().send().join()
return topology.toString()
}
fun main(args: Array<String>) {
runApplication<CloudstarterApplication>(*args)
}
}
Run the application with the command
mvn spring-boot:run
.Open http://localhost:8080/status in your web browser.
You will see the topology response from the cluster.
We will connect to the Zeebe cluster in Camunda Cloud, and request its topology.
- In the
src
folder, create a file calledapp.ts
. - Edit the file, and put in the following code:
import { ZBClient } from "zeebe-node";
require("dotenv").config();
async function main() {
const zbc = new ZBClient();
const res = await zbc.topology();
console.log(res);
}
main();
- Run the program with the command:
ts-node src/app.ts
You will see output like this:
03:19:46.658 | zeebe | INFO: Authenticating client with Camunda Cloud...
03:19:49.998 | zeebe | INFO: Established encrypted connection to Camunda Cloud.
{
brokers: [
{
partitions: [Array],
nodeId: 0,
host: 'zeebe-0.zeebe-broker-service.231bb36a-1588-4f1e-b4f6-e09944d7efd7-zeebe.svc.cluster.local',
port: 26501
}
],
clusterSize: 1,
partitionsCount: 1,
replicationFactor: 1
}
Create a BPMN model​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotling + Spring
- NodeJS
- Download and install the Zeebe Modeler.
- Open Zeebe Modeler and create a new BPMN Diagram.
- Create a new BPMN diagram.
- Add a StartEvent, an EndEvent, and a Task.
- Click on the Task, click on the little spanner/wrench icon, and select "Service Task".
- Set the Name of the Service Task to
Get Time
, and the Type toget-time
.
It should look like this:
- Click on the blank canvas of the diagram, and set the Id to
test-process
, and the Name to "Test Process".
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotling + Spring
- NodeJS
- Save the diagram to
Resources/test-process.bpmn
in your project.
- Save the diagram to
test-process.bpmn
in your project.
- Save the diagram to
src/main/resources/test-process.bpmn
in your project.
- Save the diagram to
src/main/resources/test-process.bpmn
in your project.
- Save the diagram to
bpmn/test-process.bpmn
in your project.
Deploy the BPMN model to Camunda Cloud​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We need to copy the bpmn file into the build, so that it is available to our program at runtime.
- Edit the
Cloudstarter.csproj
file, and add the following to theItemGroup
:
<ItemGroup>
<None Update="Resources\**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Now we create a method in our service to deploy a bpmn model to the cluster.
- Edit
ZeebeService.cs
, and add aDeploy
method:
public async Task<IDeployResponse> Deploy(string modelFilename)
{
var filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory!, "Resources", modelFilename);
var deployment = await _client.NewDeployCommand().AddResourceFile(filename).Send();
var res = deployment.Workflows[0];
_logger.LogInformation("Deployed BPMN Model: " + res?.BpmnProcessId +
" v." + res?.Version);
return deployment;
}
- In the
ZeebeService.cs
file, update the interface definition:
public interface IZeebeService
{
public Task<IDeployResponse> Deploy(string modelFilename);
public Task<ITopology> Status();
}
Now, we call the Deploy
method during the initialization of the service at startup. We need to do it here, because the service is not instantiated
- Edit
Startup.cs
, and add the following lines to theConfigure
method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var zeebeService = app.ApplicationServices.GetService<IZeebeService>();
zeebeService.Deploy("test-process.bpmn");
// ...
}
- Edit the
main.go
file, and add a new functiondeploy
:
func deploy (zbClient zbc.Client) {
ctx := context.Background()
response, err := zbClient.NewDeployWorkflowCommand().AddResourceFile("test-process.bpmn").Send(ctx)
if err != nil {
panic(err)
}
log.Println(response.String())
}
- Now update the
main()
function to look like this:
func main() {
zbClient := getClient()
getStatus(zbClient)
deploy(zbClient)
}
- Run the program with
go run main.go
.
You will see the deployment response:
2020/07/29 06:23:07 Broker zeebe-0.zeebe-broker-service.aae86771-0906-4186-8d82-e228097e1ef7-zeebe.svc.cluster.local : 26501
2020/07/29 06:23:07 Partition 1 : Leader
2020/07/29 06:23:08 key:2251799813685251 workflows:<bpmnProcessId:"test-process" version:1 workflowKey:2251799813685249 resourceName:"test-process.bpmn" >
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and add the@ZeebeDeployment
annotation to theCloudStarterApplication
class:
// ...
@ZeebeDeployment(classPathResources = {"test-process.bpmn"})
public class CloudStarterApplication {
// ...
}
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and add the@ZeebeDeployment
annotation to theCloudStarterApplication
class:
// ...
@ZeebeDeployment(classPathResources = ["test-process.bpmn"])
class CloudStarterApplication {
// ...
}
- Edit the
src/app.ts
file, to be this:
import { ZBClient } from "zeebe-node";
import * as path from "path";
require("dotenv").config();
async function main() {
const zbc = new ZBClient();
const filename = path.join(__dirname, "..", "bpmn", "test-process");
const res = await zbc.deployWorkflow(filename);
console.log(res);
}
main();
- Run the program with the command:
ts-node src/app.ts
You will see output similar to this:
01:37:30.710 | zeebe | INFO: Authenticating client with Camunda Cloud...
01:37:36.466 | zeebe | INFO: Established encrypted connection to Camunda Cloud.
{
workflows: [
{
bpmnProcessId: 'test-process',
version: 1,
workflowKey: '2251799813687791',
resourceName: 'test-process.bpmn'
}
],
key: '2251799813688440'
}
The workflow is now deployed to the cluster.
Start a Workflow Instance​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will create a controller route at /start
that will start a new instance of the workflow.
- Add fastJSON to the project:
dotnet add package fastJSON
- Edit
Services/ZeebeService.cs
and add aStartWorkflowInstance
method:
public async Task<String> StartWorkflowInstance(string bpmProcessId)
{
var instance = await _client.NewCreateWorkflowInstanceCommand()
.BpmnProcessId(bpmProcessId)
.LatestVersion()
.Send();
var jsonParams = new JSONParameters {ShowReadOnlyProperties = true};
return JSON.ToJSON(instance, jsonParams);
}
- Update the service interface definition:
public interface IZeebeService
{
public Task<IDeployResponse> Deploy(string modelFile);
public Task<ITopology> Status();
public Task<String> StartWorkflowInstance(string bpmProcessId);
}
- Edit
Controllers/ZeebeController.cs
, and add a REST method to start an instance of the workflow:
// ...
public class ZeebeController : Controller
// ...
[Route("/start")]
[HttpGet]
public async Task<string> StartWorkflowInstance()
{
var instance = await _zeebeService.StartWorkflowInstance("test-process");
return instance;
}
}
Run the program with the command:
dotnet run
.Visit http://localhost:5000/start in your browser.
You will see output similar to the following:
{"$types":{"Zeebe.Client.Impl.Responses.WorkflowInstanceResponse, Client, Version=0.16.1.0, Culture=neutral, PublicKeyToken=null":"1"},"$type":"1","WorkflowKey":2251799813685454,"BpmnProcessId":"test-process","Version":3,"WorkflowInstanceKey":2251799813686273}
A workflow instance has been started. Let's view it in Operate.
We will add a webserver, and use it to provide a REST interface for our program.
Add
"net/http"
to the imports inmain.go
.Edit the
main.go
file, and add a REST handler to start an instance:
type BoundHandler func(w http.ResponseWriter, r * http.Request)
func createStartHandler(client zbc.Client) BoundHandler {
f := func (w http.ResponseWriter, r * http.Request) {
ctx := context.Background()
request, err := client.NewCreateInstanceCommand().BPMNProcessId("test-process").LatestVersion().Send(ctx)
if err != nil {
panic(err)
}
fmt.Fprint(w, request.String())
}
return f
}
- Now update the
main()
function to add an HTTP server and the/start
route:
func main() {
zbClient := getClient()
getStatus(zbClient)
deploy(zbClient)
http.HandleFunc("/start", createStartHandler(zbClient))
http.ListenAndServe(":3000", nil)
}
Run the program with the command:
go run main.go
.Visit http://localhost:3000/start in your browser.
You will see output similar to the following:
workflowKey:2251799813685249 bpmnProcessId:"test-process" version:1 workflowInstanceKey:2251799813685257
A workflow instance has been started. Let's view it in Operate.
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and add a REST method to start an instance of the workflow:
// ...
public class CloudStarterApplication {
// ...
@GetMapping("/start")
public String startWorkflowInstance() {
WorkflowInstanceEvent workflowInstanceEvent = client
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.send()
.join();
return workflowInstanceEvent.toString();
}
}
Run the program with the command:
mvn spring-boot:run
.Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698314}
A workflow instance has been started. Let's view it in Operate.
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and add a REST method to start an instance of the workflow:
// ...
class CloudStarterApplication {
// ...
@GetMapping("/start")
fun startWorkflowInstance(): String? {
val workflowInstanceEvent = client!!
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.send()
.join()
return workflowInstanceEvent.toString()
}
}
Run the program with the command:
mvn spring-boot:run
.Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698314}
A workflow instance has been started. Let's view it in Operate.
- Edit the
src/app.ts
file, and make it look like this:
import { ZBClient } from "zeebe-node";
import * as path from "path";
require("dotenv").config();
async function main() {
const zbc = new ZBClient();
const file = path.join(__dirname, "..", "bpmn", "test-process.bpmn");
await zbc.deployWorkflow(file);
const res = await zbc.createWorkflowInstance("test-process", {});
console.log(res);
}
main();
- Run the program with the command:
ts-node src/app.ts
You will see output similar to:
02:00:20.689 | zeebe | INFO: Authenticating client with Camunda Cloud...
02:00:23.769 | zeebe | INFO: Established encrypted connection to Camunda Cloud.
{
workflowKey: '2251799813687791',
bpmnProcessId: 'test-process',
version: 1,
workflowInstanceKey: '2251799813688442'
}
A workflow instance has been started. Let's view it in Operate.
View a Workflow Instance in Operate​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotling + Spring
- NodeJS
- Go to your cluster in the Camunda Cloud Console.
- In the cluster detail view, click on "View Workflow Instances in Camunda Operate".
- In the "Instances by Workflow" column, click on "Test Process - 1 Instance in 1 Version".
- Click the Instance Id to open the instance.
- You will see the token is stopped at the "Get Time" task.
Let's create a task worker to serve the job represented by this task.
Create a Job Worker​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will create a worker program that logs out the job metadata, and completes the job with success.
- Edit the
Services/ZeebeService.cs
file, and add a_createWorker
method to theZeebeService
class:
// ...
private void _createWorker(String jobType, JobHandler handleJob)
{
_client.NewWorker()
.JobType(jobType)
.Handler(handleJob)
.MaxJobsActive(5)
.Name(jobType)
.PollInterval(TimeSpan.FromSeconds(50))
.PollingTimeout(TimeSpan.FromSeconds(50))
.Timeout(TimeSpan.FromSeconds(10))
.Open();
}
- Now add a
CreateGetTimeWorker
method, where we supply the task-type for the worker, and a job handler function:
public void CreateGetTimeWorker()
{
_createWorker("get-time", async (client, job) =>
{
_logger.LogInformation("Received job: " + job);
await client.NewCompleteJobCommand(job.Key).Send();
});
}
The worker handler function is async
so that it runs on its own thread.
- Now create a method
StartWorkers
:
public void StartWorkers()
{
CreateGetTimeWorker();
}
- And add it to the
IZeebeService
interface:
public interface IZeebeService
{
public Task<IDeployResponse> Deploy(string modelFile);
public Task<ITopology> Status();
public Task<string> StartWorkflowInstance(string bpmProcessId);
public void StartWorkers();
}
- Now call this method in the
Configure
method inStartup.cs
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var zeebeService = app.ApplicationServices.GetService<IZeebeService>();
zeebeService.Deploy("test-process.bpmn");
zeebeService.StartWorkers();
// ...
}
- Run the program with the command:
dotnet run
.
You will see output similar to:
2020-07-16 20:34:25.4971 | DEBUG | Zeebe.Client.Impl.Worker.JobWorker | Job worker (get-time) activated 1 of 5 successfully.
2020-07-16 20:34:25.4971 | INFO | Cloudstarter.Services.ZeebeService | Received job: Key: 2251799813686173, Type: get-time, WorkflowInstanceKey: 2251799813686168, BpmnProcessId: test-process, WorkflowDefinitionVersion: 3, WorkflowKey: 2251799813685454, ElementId: Activity_1ucrvca, ElementInstanceKey: 2251799813686172, Worker: get-time, Retries: 3, Deadline: 07/16/2020 20:34:35, Variables: {}, CustomHeaders: {}
- Go back to Operate. You will see that the workflow instance is gone.
- Click on "Running Instances".
- In the filter on the left, select "Finished Instances".
You will see the completed workflow instance.
We will create a worker that logs out the job metadata, and completes the job with success.
- Edit the
main.go
file, and add a handler function for the worker:
func handleGetTime(client worker.JobClient, job entities.Job) {
log.Println(job)
ctx := context.Background()
client.NewCompleteJobCommand().JobKey(job.Key).Send(ctx)
}
- Update the
main
function to create a worker in a go routine, which allows it to run in a background thread:
func main() {
zbClient := getClient()
getStatus(zbClient)
deploy(zbClient)
go zbClient.NewJobWorker().JobType("get-time").Handler(handleGetTime).Open()
http.HandleFunc("/start", createStartHandler(zbClient))
http.ListenAndServe(":3000", nil)
}
- Run the worker program with the command:
go run main.go
.
You will see output similar to:
2020/07/29 23:48:15 {{2251799813685262 get-time 2251799813685257 test-process 1 2251799813685249 Activity_1ucrvca 2251799813685261 {} default 3 1596023595126 {} {} [] 0}}
- Go back to Operate. You will see that the workflow instance is gone.
- Click on "Running Instances".
- In the filter on the left, select "Finished Instances".
You will see the completed workflow instance.
We will create a worker program that logs out the job metadata, and completes the job with success.
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and add a REST method to start an instance of the workflow:
// ...
public class CloudStarterApplication {
Logger logger = LoggerFactory.getLogger(CloudStarterApplication.class);
// ...
@ZeebeWorker(type = "get-time")
public void handleGetTime(final JobClient client, final ActivatedJob job) {
logger.info(job.toString());
client.newCompleteCommand(job.getKey())
.send().join();
}
}
- Run the worker program with the command:
mvn spring-boot:run
.
You will see output similar to:
2020-06-29 09:33:40.420 INFO 5801 --- [ault-executor-1] io.zeebe.client.job.poller : Activated 1 jobs for worker whatever and job type get-time
2020-06-29 09:33:40.437 INFO 5801 --- [pool-2-thread-1] i.c.c.CloudStarterApplication : {"key":2251799813698319,"type":"get-time","customHeaders":{},"workflowInstanceKey":2251799813698314,"bpmnProcessId":"test-process","workflowDefinitionVersion":1,"workflowKey":2251799813685249,"elementId":"Activity_1ucrvca","elementInstanceKey":2251799813698318,"worker":"whatever","retries":3,"deadline":1593380320176,"variables":"{}","variablesAsMap":{}}
- Go back to Operate. You will see that the workflow instance is gone.
- Click on "Running Instances".
- In the filter on the left, select "Finished Instances".
You will see the completed workflow instance.
We will create a worker program that logs out the job metadata, and completes the job with success.
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and add a REST method to start an instance of the workflow:
// ...
class CloudStarterApplication {
var logger: Logger = LoggerFactory.getLogger(javaClass)
// ...
@ZeebeWorker(type = "get-time")
fun handleGetTime(client: JobClient, job: ActivatedJob) {
logger.info(job.toString())
client.newCompleteCommand(job.getKey())
.send().join()
}
}
- Run the worker program with the command:
mvn spring-boot:run
.
You will see output similar to:
2020-06-29 09:33:40.420 INFO 5801 --- [ault-executor-1] io.zeebe.client.job.poller : Activated 1 jobs for worker whatever and job type get-time
2020-06-29 09:33:40.437 INFO 5801 --- [pool-2-thread-1] i.c.c.CloudStarterApplication : {"key":2251799813698319,"type":"get-time","customHeaders":{},"workflowInstanceKey":2251799813698314,"bpmnProcessId":"test-process","workflowDefinitionVersion":1,"workflowKey":2251799813685249,"elementId":"Activity_1ucrvca","elementInstanceKey":2251799813698318,"worker":"whatever","retries":3,"deadline":1593380320176,"variables":"{}","variablesAsMap":{}}
- Go back to Operate. You will see that the workflow instance is gone.
- Click on "Running Instances".
- In the filter on the left, select "Finished Instances".
You will see the completed workflow instance.
We will create a worker program that logs out the job metadata, and completes the job with success.
- Create a new file
src/worker.ts
. - Edit the file to look like this:
import { ZBClient } from "zeebe-node";
require("dotenv").config();
const zbc = new ZBClient();
const worker = zbc.createWorker("get-time", (job, complete) => {
console.log(job);
complete.success();
});
- Run the worker program with the command:
ts-node src/worker.ts
.
You will see output similar to:
{
"key": "2251799813688447",
"type": "get-time",
"workflowInstanceKey": "2251799813688442",
"bpmnProcessId": "test-process",
"workflowDefinitionVersion": 1,
"workflowKey": "2251799813687791",
"elementId": "Activity_18gdgop",
"elementInstanceKey": "2251799813688446",
"customHeaders": {},
"worker": "get-time",
"retries": 3,
"deadline": "1592750237366",
"variables": {}
}
- Go back to Operate. You will see that the workflow instance is gone.
- Click on "Running Instances".
- In the filter on the left, select "Finished Instances".
You will see the completed workflow instance.
Create and Await the Outcome of a Workflow Instance​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will now create the workflow instance, and get the final outcome in the calling code.
- Edit the
ZeebeService.cs
file, and edit theStartWorkflowInstance
method, to make it look like this:
// ...
public async Task<String> StartWorkflowInstance(string bpmProcessId)
{
var instance = await _client.NewCreateWorkflowInstanceCommand()
.BpmnProcessId(bpmProcessId)
.LatestVersion()
.WithResult()
.Send();
var jsonParams = new JSONParameters {ShowReadOnlyProperties = true};
return JSON.ToJSON(instance, jsonParams);
}
Run the program with the command:
dotnet run
.Visit http://localhost:5000/start in your browser.
You will see output similar to the following:
{"$types":{"Zeebe.Client.Impl.Responses.WorkflowInstanceResultResponse, Client, Version=0.16.1.0, Culture=neutral, PublicKeyToken=null":"1"},"$type":"1","WorkflowKey":2251799813686366,"BpmnProcessId":"test-process","Version":4,"WorkflowInstanceKey":2251799813686409,"Variables":"{}"}
We will now create the workflow instance, and get the final outcome in the calling code.
- Edit the
createStartHandler
function in themain.go
file, and make the following change:
// ...
request, err := client.NewCreateInstanceCommand().BPMNProcessId("test-process").LatestVersion().WithResult().Send(ctx)
// ...
Run the program with the command:
go run main.go
.Visit http://localhost:3000/start in your browser.
You will see output similar to the following:
workflowKey:2251799813685249 bpmnProcessId:"test-process" version:1 workflowInstanceKey:2251799813689873 variables:"{}"
We will now create the workflow instance, and get the final outcome in the calling code.
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and edit thestartWorkflowInstance
method, to make it look like this:
// ...
public class CloudStarterApplication {
// ...
@GetMapping("/start")
public String startWorkflowInstance() {
WorkflowInstanceResult workflowInstanceResult = client
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.withResult()
.send()
.join();
return workflowInstanceResult.toString();
}
}
Run the program with the command:
mvn spring-boot:run
.Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceWithResultResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698527, variables='{}'}
We will now create the workflow instance, and get the final outcome in the calling code.
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and edit thestartWorkflowInstance
method, to make it look like this:
// ...
class CloudStarterApplication {
// ...
@GetMapping("/start")
fun startWorkflowInstance(): String? {
val workflowInstanceEventWithResult = client!!
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.withResult()
.send()
.join()
return workflowInstanceEventWithResult.toString()
}
}
Run the program with the command:
mvn spring-boot:run
.Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceWithResultResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698527, variables='{}'}
We will now create the workflow instance, and get the final outcome in the calling code.
- Keep the worker program running in one terminal.
- Edit the
src/app.ts
file, and make it look like this:
import { ZBClient } from "zeebe-node";
import * as path from "path";
require("dotenv").config();
async function main() {
const zbc = new ZBClient();
const file = path.join(__dirname, "..", "bpmn", "test-process.bpmn");
await zbc.deployWorkflow(file);
const res = await zbc.createWorkflowInstanceWithResult("test-process", {});
console.log(res);
}
main();
- Run the program with the command:
ts-node src/app.ts
.
You will see your worker log out the job as it serves it, and your program will produce output similar to the following:
{
"workflowKey": "2251799813688541",
"bpmnProcessId": "test-process",
"version": 1,
"workflowInstanceKey": "2251799813688543",
"variables": {}
}
Call a REST Service from the Worker​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We are going to make a REST call in the worker handler, to query a remote API for the current GMT time.
- Edit the
ZeebeService.cs
file, and edit theCreateGetTimeWorker
method, to make it look like this:
// ...
public void CreateGetTimeWorker()
{
_createWorker("get-time", async (client, job) =>
{
_logger.LogInformation("Received job: " + job);
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.GetAsync("https://json-api.joshwulf.com/time"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
await client.NewCompleteJobCommand(job.Key)
.Variables("{\"time\":" + apiResponse + "}")
.Send();
}
}
});
}
// ...
- Run the program with the command:
dotnet run
. - Visit http://localhost:5000/start in your browser.
You will see output similar to the following:
{"$types":{"Zeebe.Client.Impl.Responses.WorkflowInstanceResultResponse, Client, Version=0.16.1.0, Culture=neutral, PublicKeyToken=null":"1"},"$type":"1","WorkflowKey":2251799813686366,"BpmnProcessId":"test-process","Version":4,"WorkflowInstanceKey":2251799813686463,"Variables":"{\"time\":{\"time\":\"Thu, 16 Jul 2020 10:26:13 GMT\",\"hour\":10,\"minute\":26,\"second\":13,\"day\":4,\"month\":6,\"year\":2020}}"}
- Edit the file
main.go
and add these structs for the REST response payload and the worker variable payload:
type Time struct {
Time string `json:"time"`
Hour int `json:"hour"`
Minute int `json:"minute"`
Second int `json:"second"`
Day int `json:"day"`
Month int `json:"month"`
Year int `json:"year"`
}
type GetTimeCompleteVariables struct {
Time Time `json:"time"`
}
- Replace the
handleGetTime
function inmain.go
with the following code:
func handleGetTime(client worker.JobClient, job entities.Job) {
var (
data []byte
)
response, err := http.Get("https://json-api.joshwulf.com/time")
if err != nil {
fmt.Printf("The HTTP request failed with error %s\n", err)
return
} else {
data, _ = ioutil.ReadAll(response.Body)
}
var time Time
err = json.Unmarshal(data, &time)
if err != nil {
log.Fatalln(err)
return
}
payload := &GetTimeCompleteVariables{time}
ctx := context.Background()
cmd, _ := client.NewCompleteJobCommand().JobKey(job.Key).VariablesFromObject(payload)
_, err = cmd.Send(ctx)
if err !=nil {
log.Fatalln(err)
}
}
- Run the program with the command:
go run main.go
. - Visit http://localhost:3000/start in your browser.
You will see output similar to the following:
workflowKey:2251799813685249 bpmnProcessId:"test-process" version:1 workflowInstanceKey:2251799813693481
variables:"{\"time\":{\"time\":\"Thu, 30 Jul 2020 13:33:13 GMT\",\"hour\":13,\"minute\":33,\"second\":13,\"day\":4,\"month\":6,\"year\":2020}}"
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and edit thehandleGetTime
method, to make it look like this:
// ...
public class CloudStarterApplication {
// ...
@ZeebeWorker(type = "get-time")
public void handleGetTime(final JobClient client, final ActivatedJob job) {
final String uri = "https://json-api.joshwulf.com/time";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
client.newCompleteCommand(job.getKey())
.variables("{\"time\":" + result + "}")
.send().join();
}
}
- Run the program with the command:
mvn spring-boot:run
. - Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceWithResultResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698527, variables='{"time":{"time":"Sun, 28 Jun 2020 21:49:48 GMT","hour":21,"minute":49,"second":48,"day":0,"month":5,"year":2020}}'}
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and edit thehandleGetTime
method, to make it look like this:
// ...
class CloudStarterApplication {
// ...
@ZeebeWorker(type = "get-time")
fun handleGetTime(client: JobClient, job: ActivatedJob) {
logger.info(job.toString())
val uri = "https://json-api.joshwulf.com/time"
val restTemplate = RestTemplate()
val result = restTemplate.getForObject(uri, String::class.java)!!
client.newCompleteCommand(job.key)
.variables("{\"time\":$result}")
.send().join()
}
}
- Run the program with the command:
mvn spring-boot:run
. - Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
CreateWorkflowInstanceWithResultResponseImpl{workflowKey=2251799813685249, bpmnProcessId='test-process', version=1, workflowInstanceKey=2251799813698527, variables='{"time":{"time":"Sun, 28 Jun 2020 21:49:48 GMT","hour":21,"minute":49,"second":48,"day":0,"month":5,"year":2020}}'}
- Stop the worker program.
- Install the
got
package to your project:
npm i got
- Edit the file
src/worker.ts
, and make it look like this:
import { ZBClient } from "zeebe-node";
import got from "got";
require("dotenv").config();
const zbc = new ZBClient();
const url = "https://json-api.joshwulf.com/time";
const worker = zbc.createWorker("get-time", async (job, complete) => {
const time = await got(url).json();
console.log(time);
complete.success({ time });
});
- Run the worker program with the command:
ts-node src/worker.ts
- In another terminal, run the program with the command:
ts-node src/app.ts
You will see output similar to the following:
{
"workflowKey": "2251799813688541",
"bpmnProcessId": "test-process",
"version": 1,
"workflowInstanceKey": "2251799813688598",
"variables": {
"time": {
"time": "Sun, 21 Jun 2020 15:08:22 GMT",
"hour": 15,
"minute": 8,
"second": 22,
"day": 0,
"month": 5,
"year": 2020
}
}
}
Make a Decision​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will edit the model to add a Conditional Gateway.
- Open the BPMN model file
bpmn/test-process.bpmn
in the Zeebe Modeler. - Drop a Gateway between the Service Task and the End event.
- Add two Service Tasks after the Gateway.
- In one, set the Name to
Before noon
and the Type tomake-greeting
. - Switch to the Headers tab on that Task, and create a new Key
greeting
with the ValueGood morning
. - In the second, set the Name to
After noon
and the Type tomake-greeting
. - Switch to the Headers tab on that Task, and create a new Key
greeting
with the ValueGood afternoon
. - Click on the arrow connecting the Gateway to the Before noon task.
- Under Details enter the following in Condition expression:
=time.hour >=0 and time.hour <=11
- Click on the arrow connecting the Gateway to the After noon task.
- Click the spanner/wrench icon and select "Default Flow".
- Connect both Service Tasks to the End Event.
It should look like this:
Create a Worker that acts based on Custom Headers​
- C# / ASP.NET Core 3
- Go
- Java + Spring
- Kotlin + Spring
- NodeJS
We will create a second worker that combines the value of a custom header with the value of a variable in the workflow.
- Edit the
ZeebeService.cs
file and create a couple of DTO classes to aid with deserialization of the job:
public class MakeGreetingCustomHeadersDTO
{
public string greeting { get; set; }
}
public class MakeGreetingVariablesDTO
{
public string name { get; set; }
}
- In the same file, create a
CreateMakeGreetingWorker
method:
public void CreateMakeGreetingWorker()
{
_createWorker("make-greeting", async (client, job) =>
{
_logger.LogInformation("Make Greeting Received job: " + job);
var headers = JSON.ToObject<MakeGreetingCustomHeadersDTO>(job.CustomHeaders);
var variables = JSON.ToObject<MakeGreetingVariablesDTO>(job.Variables);
string greeting = headers.greeting;
string name = variables.name;
await client.NewCompleteJobCommand(job.Key)
.Variables("{\"say\": \"" + greeting + " " + name + "\"}")
.Send();
_logger.LogInformation("Make Greeting Worker completed job");
});
}
- Now call this method in the
StartWorkers
method of theZeebeService
:
public void StartWorkers()
{
CreateGetTimeWorker();
CreateMakeGreetingWorker();
}
- Edit the
startWorkflowInstance
method, and pass in a variablename
when you create the workflow:
// ...
public async Task<String> StartWorkflowInstance(string bpmProcessId)
{
var instance = await _client.NewCreateWorkflowInstanceCommand()
.BpmnProcessId(bpmProcessId)
.LatestVersion()
.Variables("{\"name\": \"Josh Wulf\"}")
.WithResult()
.Send();
var jsonParams = new JSONParameters {ShowReadOnlyProperties = true};
return JSON.ToJSON(instance, jsonParams);
}
You can change the variable name
value to your own name (or derive it from the url path or a parameter).
- Run the program with the command:
dotnet run
. - Visit http://localhost:5000/start in your browser.
You will see output similar to the following:
{"$types":{"Zeebe.Client.Impl.Responses.WorkflowInstanceResultResponse, Client, Version=0.16.1.0, Culture=neutral, PublicKeyToken=null":"1"},"$type":"1","WorkflowKey":2251799813686683,"BpmnProcessId":"test-process","Version":5,"WorkflowInstanceKey":2251799813687157,"Variables":"{\"say\":\"Good Afternoon Josh Wulf\",\"name\":\"Josh Wulf\",\"time\":{\"time\":\"Thu, 16 Jul 2020 12:45:33 GMT\",\"hour\":12,\"minute\":45,\"second\":33,\"day\":4,\"month\":6,\"year\":2020}}"}
We will create a second worker that takes the custom header and applies it to the variables in the workflow.
- Edit the
main.go
file, and add structs for the new worker's request and response payloads:
type MakeGreetingCompleteVariables struct {
Say string `json:"say"`
}
type MakeGreetingCustomHeaders struct {
Greeting string `json:"greeting"`
}
type MakeGreetingJobVariables struct {
Name string `json:"name"`
}
- Create the
handleMakeGreeting
method inmain.go
:
func handleMakeGreeting(client worker.JobClient, job entities.Job) {
variables, _ := job.GetVariablesAsMap()
name := variables["name"]
var headers, _ = job.GetCustomHeadersAsMap()
greeting := headers["greeting"]
greetingString := greeting + " " + name.(string)
say := &MakeGreetingCompleteVariables{greetingString}
log.Println(say)
ctx := context.Background()
response, _ := client.NewCompleteJobCommand().JobKey(job.Key).VariablesFromObject(say)
response.Send(ctx)
}
- Edit the
main
function and create a worker:
// ...
go zbClient.NewJobWorker().JobType("make-greeting").Handler(handleMakeGreeting).Open()
/ ...
- Edit the
startWorkflowInstance
method, and make it look like this:
type InitialVariables struct {
Name string `json:"name"`
}
func createStartHandler(client zbc.Client) BoundHandler {
f := func (w http.ResponseWriter, r * http.Request) {
variables := &InitialVariables{"Josh Wulf"}
ctx := context.Background()
request, err := client.NewCreateInstanceCommand().BPMNProcessId("test-process").LatestVersion().VariablesFromObject(variables)
if err != nil {
panic(err)
}
response, _ := request.WithResult().Send(ctx)
var result map[string]interface{}
json.Unmarshal([]byte(response.Variables), &result)
fmt.Fprint(w, result["say"])
}
return f
}
You can change the variable value to your own name (or derive it from the url path or a parameter).
- Run the program with the command:
go run main
. - Visit http://localhost:3000/start in your browser.
You will see output similar to the following:
Good Morning Josh Wulf
We will create a second worker that takes the custom header and applies it to the variables in the workflow.
- Edit the
src/main/java/io.camunda/CloudStarterApplication.java
file, and add thehandleMakeGreeting
method, to make it look like this:
// ...
public class CloudStarterApplication {
// ...
@ZeebeWorker(type = "make-greeting")
public void handleMakeGreeting(final JobClient client, final ActivatedJob job) {
Map<String, String> headers = job.getCustomHeaders();
String greeting = headers.getOrDefault("greeting", "Good day");
Map<String, Object> variablesAsMap = job.getVariablesAsMap();
String name = (String) variablesAsMap.getOrDefault("name", "there");
String say = greeting + " " + name;
client.newCompleteCommand(job.getKey())
.variables("{\"say\": \"" + say + "\"}")
.send().join();
}
}
- Edit the
startWorkflowInstance
method, and make it look like this:
// ...
public class CloudStarterApplication {
// ...
@GetMapping("/start")
public String startWorkflowInstance() {
WorkflowInstanceResult workflowInstanceResult = client
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.variables("{\"name\": \"Josh Wulf\"}")
.withResult()
.send()
.join();
return (String) workflowInstanceResult
.getVariablesAsMap()
.getOrDefault("say", "Error: No greeting returned");
}
}
You can change the variable name
value to your own name (or derive it from the url path or a parameter).
- Run the program with the command:
mvn spring-boot:run
. - Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
Good Morning Josh Wulf
We will create a second worker that takes the custom header and applies it to the variables in the workflow.
- Edit the
src/main/kotlin/io.camunda/CloudStarterApplication.kt
file, and add thehandleMakeGreeting
method, to make it look like this:
// ...
class CloudStarterApplication {
// ...
@ZeebeWorker(type = "make-greeting")
fun handleMakeGreeting(client: JobClient, job: ActivatedJob) {
val headers = job.customHeaders
val greeting = headers.getOrDefault("greeting", "Good day")
val variablesAsMap = job.variablesAsMap
val name = variablesAsMap.getOrDefault("name", "there") as String
val say = "$greeting $name"
client.newCompleteCommand(job.key)
.variables("{\"say\": \"$say\"}")
.send().join()
}
}
- Edit the
startWorkflowInstance
method, and make it look like this:
// ...
class CloudStarterApplication {
// ...
@GetMapping("/start")
fun startWorkflowInstance(): String? {
val workflowInstanceResult = client!!
.newCreateInstanceCommand()
.bpmnProcessId("test-process")
.latestVersion()
.variables("{\"name\": \"Josh Wulf\"}")
.withResult()
.send()
.join()
return workflowInstanceResult
.variablesAsMap
.getOrDefault("say", "Error: No greeting returned") as String?
}
}
You can change the variable name
value to your own name (or derive it from the url path or a parameter).
- Run the program with the command:
mvn spring-boot:run
. - Visit http://localhost:8080/start in your browser.
You will see output similar to the following:
Good Morning Josh Wulf
We will create a second worker that takes the custom header and applies it to the variables in the workflow.
- Stop the worker running.
- Edit the file
src/worker.ts
, and make it look like this:
import { ZBClient } from "zeebe-node";
import got from "got";
require("dotenv").config();
const zbc = new ZBClient();
const url = "https://json-api.joshwulf.com/time";
const worker = zbc.createWorker("get-time", async (job, complete) => {
const time = await got(url).json();
console.log(time);
complete.success({ time });
});
const greetingWorker = zbc.createWorker("make-greeting", (job, complete) => {
const { name } = job.variables;
const { greeting } = job.customHeaders;
complete.success({
say: `${greeting} ${name}`,
});
});
- Edit the file
src/app.ts
, and make it look like this:
import { ZBClient } from "zeebe-node";
import * as path from "path";
require("dotenv").config();
async function main() {
const zbc = new ZBClient();
const file = path.join(__dirname, "..", "bpmn", "test-process.bpmn");
await zbc.deployWorkflow(file);
const res = await zbc.createWorkflowInstanceWithResult("test-process", {
name: "Josh Wulf",
});
console.log("Process Instance (Complete)", res.variables.say);
}
main();
- Start the workers with the command:
ts-node src/worker.ts
- Start the app with the command:
ts-node src/app.ts
You will see output similar to the following:
06:17:42.273 | zeebe | INFO: Authenticating client with Camunda Cloud...
06:17:45.039 | zeebe | INFO: Established encrypted connection to Camunda Cloud.
Process Instance (Complete) Good Afternoon Josh Wulf
Profit!​
Congratulations. You've completed the Getting Started Guide for Camunda Cloud.