id | title |
---|---|
go-client |
Go |
The Zeebe Go Client is available for Go applications.
Watch a video tutorial on YouTube walking through this Getting Started Guide.
Estimated time to complete: 60 minutes
- 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/...
- Log in to https://camunda.io.
- Create a new Zeebe 0.23.3 cluster.
- When the new cluster appears in the console, create a new set of client credentials.
- Copy the client Connection Info environment variables block.
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.
- 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.
- 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". - Save the diagram to
test-process.bpmn
in your project.
- 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" >
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.
- 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.
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 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:"{}"
- 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}}"
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:
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
Congratulations. You've completed the Getting Started Guide for Camunda Cloud using Golang.