From f7f67d8565ecbaa9e0f269834bac9737c9795ff3 Mon Sep 17 00:00:00 2001 From: Tomas Walek Date: Mon, 27 Jul 2026 09:57:49 +0200 Subject: [PATCH] First two chapters of getting started added --- docs/explanation/bpm-concepts.md | 8 +- docs/static/client-libraries.md | 316 ++++++++++++------ .../assets/images/approval-process.svg | 31 ++ .../assets/images/first-bpmn-process.svg | 4 + docs/tutorials/getting-started.mdx | 165 --------- .../tutorials/getting-started/_category_.json | 8 + .../getting-started/first-bpmn-process.mdx | 308 +++++++++++++++++ .../orchestrate-human-tasks.mdx | 200 +++++++++++ .../getting-started/run-the-engine.mdx | 98 ++++++ 9 files changed, 864 insertions(+), 274 deletions(-) create mode 100644 docs/tutorials/assets/images/approval-process.svg create mode 100644 docs/tutorials/assets/images/first-bpmn-process.svg delete mode 100644 docs/tutorials/getting-started.mdx create mode 100644 docs/tutorials/getting-started/_category_.json create mode 100644 docs/tutorials/getting-started/first-bpmn-process.mdx create mode 100644 docs/tutorials/getting-started/orchestrate-human-tasks.mdx create mode 100644 docs/tutorials/getting-started/run-the-engine.mdx diff --git a/docs/explanation/bpm-concepts.md b/docs/explanation/bpm-concepts.md index f14283408..eeea27017 100644 --- a/docs/explanation/bpm-concepts.md +++ b/docs/explanation/bpm-concepts.md @@ -6,7 +6,7 @@ description: The core concepts behind ZenBPM - processes, instances, tokens, tas # BPM Concepts -This page explains the ideas behind ZenBPM. You don't need to read it before the [Getting Started tutorial](/tutorials/getting-started) - but whenever a term there feels unclear, this is the place to look it up. +This page explains the ideas behind ZenBPM. You don't need to read it before the [Getting Started tutorial](/tutorials/getting-started/first-bpmn-process) - but whenever a term there feels unclear, this is the place to look it up. If you already know Camunda, Zeebe, or another BPMN engine, skip to [How ZenBPM maps to other engines](#terminology-mapping). @@ -28,8 +28,6 @@ A **process definition** is the blueprint - the BPMN diagram you deploy to the e A **process instance** is one running execution of that blueprint. *"Order #4711 from Alice, currently being handled."* There can be thousands, each with its own state and data, each at a different point in the flow. -{/* Excalidraw source: definition-vs-instance.excalidraw — export as SVG (embed scene, transparent - background) and check dark mode; use ThemedImage with a dark variant if needed. */} ![One process definition producing many process instances, each at a different point of execution](assets/images/process-definition-instances.png) The same relationship as a class and its objects, or a recipe and the meals cooked from it. @@ -111,7 +109,7 @@ This decoupling is intentional. Workers can be written in any language, scaled i Putting it all together, working with ZenBPM follows one loop: **model** the diagram, **deploy** it to the engine, **start instances** with their variables, let workers and people **execute** the work, and **observe** state and history - then improve the diagram and go around again. -The [Getting Started tutorial](/tutorials/getting-started) walks this exact loop once, end to end. +The [Getting Started tutorial](/tutorials/getting-started/first-bpmn-process) walks this exact loop once, end to end. ## How ZenBPM maps to other engines {#terminology-mapping} @@ -132,6 +130,6 @@ ZenBPM follows the Zeebe-style architecture: an external-worker model over gRPC, ## Where to go next -- Run the loop yourself: [Getting Started tutorial](/tutorials/getting-started) +- Run the loop yourself: [Getting Started tutorial](/tutorials/getting-started/first-bpmn-process) - See working processes and workers: [zenbpm-examples](https://github.com/pbinitiative/zenbpm-examples) - Engine internals: [Architecture](/category/architecture) diff --git a/docs/static/client-libraries.md b/docs/static/client-libraries.md index de6c584bb..041f760de 100644 --- a/docs/static/client-libraries.md +++ b/docs/static/client-libraries.md @@ -3,40 +3,67 @@ sidebar_position: 110 --- # Client Libraries -ZenBPM provides officially supported client libraries in multiple programming languages to help developers integrate with the engine more easily. +ZenBPM provides officially supported client libraries that wrap its REST and gRPC APIs so you don't have to hand-roll HTTP calls or gRPC streams. -The versions of the libraries are aligned with the ZenBPM engine versions. +Every client does two things: + +- **REST** — deploy process/decision resources, start instances, query state. +- **gRPC** — run *job workers*: subscribe to a job type, complete or fail jobs over a bidirectional stream. + +## Versioning + +The Go client ships as part of the engine module, so it tracks the engine version. The **Java** client is published to **Maven Central** under group `org.pbinitiative.zenbpm`, and its version tracks the engine version too (e.g. `1.4.0`). + +## Choosing a client + +| Language | Artifact | Use when | +|---|---|---| +| Go | `github.com/pbinitiative/zenbpm/pkg/zenclient` | Any Go application. Ships as part of the engine module. | +| Java | `org.pbinitiative.zenbpm:zenbpm-client-core` | Java apps **not** using Spring Boot. Works on older Java versions. | +| Java | `org.pbinitiative.zenbpm:zenbpm-spring-boot-starter` | Spring Boot apps. Adds auto-configuration and the `@JobWorker` annotation; pulls in `zenbpm-client-core`. | + +> The examples below omit error handling for brevity. Handle returned errors/exceptions in real code. + +--- ## Go Client -The Go client is part of the ZenBPM engine and is available as package `github.com/pbinitiative/zenbpm/pkg/zenclient`. +The Go client lives in package `github.com/pbinitiative/zenbpm/pkg/zenclient` and provides both a REST client and a gRPC worker client. + +### Install + +```bash +go get github.com/pbinitiative/zenbpm@latest +``` -The `zenclient` package provides two clients: -- REST (HTTP) client for managing process/decision resources, starting instances, etc. -- gRPC worker client for subscribing to job types and completing/failing jobs via a bidirectional stream. +The client is part of the engine module, so its version follows the engine version. + +### Deploy and start an instance (REST) -### Usage examples -#### Deploy a BPMN process definition and start a process instance -Simplified example: ```go -restClient, _ := zenclient.NewClient("http://localhost:8080/v1") +// The "WithResponses" client returns typed, parsed responses (with JSON201 etc.). +restClient, _ := zenclient.NewClientWithResponses("http://localhost:8080/v1") +// Deploy is a multipart upload of the .bpmn file. var bodyBuf bytes.Buffer mw := multipart.NewWriter(&bodyBuf) -... -resp1, _ := restClient.CreateProcessDefinitionWithBody( - ctx, - mw.FormDataContentType(), - &bodyBuf, +// ... write the .bpmn file into the "resource" form field ... +mw.Close() + +defResp, _ := restClient.CreateProcessDefinitionWithBodyWithResponse( + ctx, mw.FormDataContentType(), &bodyBuf, ) +key := defResp.JSON201.ProcessDefinitionKey -startBody := zenclient.CreateProcessInstanceJSONRequestBody{ - ProcessDefinitionKey: key, -} -resp2, _ := restClient.CreateProcessInstance(ctx, startBody) +// Start an instance from the returned key. +instResp, _ := restClient.CreateProcessInstanceWithResponse(ctx, + zenclient.CreateProcessInstanceJSONRequestBody{ProcessDefinitionKey: &key}, +) +_ = instResp.JSON201 // the started ProcessInstance ``` -#### Register a worker -Simplified example: + +### Register a worker (gRPC) + ```go conn, _ := grpc.NewClient("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) defer conn.Close() @@ -44,92 +71,141 @@ defer conn.Close() zen := zenclient.NewGrpc(conn) jobWorker := func(ctx context.Context, job *proto.WaitingJob) (map[string]any, *zenclient.WorkerError) { -// ... + vars := job.GetVariables() + + // ... do the work ... + + // Success: return output variables (or an empty map) and nil. + return map[string]any{"result": "done"}, nil + + // To fail the job (and trigger a retry) return a WorkerError instead: + // return nil, &zenclient.WorkerError{ErrorCode: "BUSINESS_ERROR"} } zen.RegisterWorker(context.Background(), "my-client-id", jobWorker, "my-job-type") ``` -## Java Client -The Java client is a lightweight library that wraps the REST and gRPC APIs, providing a type-safe interface for Java applications. +--- -The client is available on GitHub: [zenbpm-java-client](https://github.com/pbinitiative/zenbpm-java-client) +## Java Client -There are 2 artefacts available: -- **zenbpm-client-code:** the core library, can be used independently of Spring Boot and supports old java versions -- **zenbpm-spring-boot-starter:** a Spring Boot starter that provides auto-configuration for the core library and `@JobWorker("jobName")` method annotation. +The Java client wraps the REST and gRPC APIs with a type-safe interface (DTOs are generated from the OpenAPI spec). Source: [zenbpm-java-client](https://github.com/pbinitiative/zenbpm-java-client). -### Features +### Install -* Spring Boot auto-configuration (drop-in starter) -* REST client (`ApiClient` + typed APIs generated from OpenAPI) -* gRPC job workers via `@JobWorker` and ZenbpmJobWorkerManager -* OpenTelemetry interceptors for REST and spans for gRPC -* Configurable HTTP/gRPC logging +The client is on **Maven Central** (group `org.pbinitiative.zenbpm`), so no extra repository is needed. Set `zenbpm.version` to the version matching your engine. -### Build Java Client -``mvn clean package`` +**Spring Boot** — a complete, copy-pasteable `pom.xml` for a worker application: -### Getting started +```xml + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + + com.example + my-worker + 1.0.0 + + + 17 + 1.4.0 + 1.58.0 + + + + + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.pbinitiative.zenbpm + zenbpm-spring-boot-starter + ${zenbpm.version} + + + + io.grpc + grpc-netty-shaded + 1.80.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` -Add the starter to your application and the core client as needed. +**Non-Spring (core only)** — if you're not using Spring Boot, depend on the core client instead of the starter (no OpenTelemetry BOM or `spring-boot-starter` needed): -Maven: ```xml - org.zenbpm - zenbpm-spring-boot-starter - ${zenbpm.version} - - - org.zenbpm + org.pbinitiative.zenbpm zenbpm-client-core ${zenbpm.version} ``` -Configure connection settings in application.yml +### Configure -values shown in `zenbpm` section are defaults. +The Spring Boot starter is configured through `application.yml`. Minimal configuration to connect to a local engine: -`logging` section configures logging for rest and grpc clients separately. - - `DEBUG` levels expose headers of calls and responses. - - `TRACE` level exposes full request and response bodies. **Never use this in production!** ```yaml zenbpm: restUrl: http://localhost:8080/v1 - restLoggingEnabled: true grpcHost: localhost grpcPort: 9090 - grpcPlaintext: true - grpcLoggingEnabled: true - jobWorkerEnabled: true - otelEnabled: true - -logging: - level: - root: INFO - org.zenbpm.rest: TRACE - org.zenbpm.grpc: DEBUG - + grpcPlaintext: true # local/dev only; use TLS in production + jobWorkerEnabled: true # connect job workers on startup ``` -### Working examples +See [Configuration reference](#configuration-reference) for all options, including logging. + +> Using `zenbpm-client-core` without Spring Boot? You configure the `ApiClient` and worker manager programmatically instead of via `application.yml` — see the [client repository](https://github.com/pbinitiative/zenbpm-java-client) for a plain-Java example. -#### 1) Use REST APIs -Inject the provided ZenbpmClientService to obtain the ApiClient, then create a typed API. +### Deploy and start an instance (REST) + +Inject `ZenbpmClientService` to obtain the `ApiClient`, then use the typed APIs. ```java import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; -import org.zenbpm.rest.ZenbpmClientService; -import org.zenbpm.client.ApiException; -import org.zenbpm.client.ApiClient; -import org.zenbpm.client.api.ProcessDefinitionApi; -import org.zenbpm.client.api.ProcessInstanceApi; -import org.zenbpm.client.api.dto.CreateProcessInstanceRequest; - -import java.util.HashMap; +import org.pbinitiative.zenbpm.rest.ZenbpmClientService; +import org.pbinitiative.zenbpm.client.ApiException; +import org.pbinitiative.zenbpm.client.ApiClient; +import org.pbinitiative.zenbpm.client.api.ProcessDefinitionApi; +import org.pbinitiative.zenbpm.client.api.ProcessInstanceApi; +import org.pbinitiative.zenbpm.client.api.dto.CreateProcessInstanceRequest; + import java.util.Map; @Service @@ -137,72 +213,104 @@ public class MyService { @Autowired private ZenbpmClientService zenbpm; - public Long deployExampleProcess() throws ApiException { + public Long deployProcess(String bpmnXml) throws ApiException { ApiClient apiClient = zenbpm.getApiClient(); ProcessDefinitionApi defApi = new ProcessDefinitionApi(apiClient); - // Example: create a process definition from a BPMN string (adjust to your endpoint contract) - String bpmnXml = "..."; - Long definitionKey = defApi.createProcessDefinition(bpmnXml).getProcessDefinitionKey(); - return definitionKey; + // Deploy the BPMN definition; the generated client returns the 201 response body. + return defApi.createProcessDefinition(bpmnXml).getProcessDefinitionKey(); } - public void startMyProcess() throws ApiException { + public void startProcess(Long definitionKey) throws ApiException { ApiClient apiClient = zenbpm.getApiClient(); ProcessInstanceApi piApi = new ProcessInstanceApi(apiClient); - Map vars = new HashMap<>(); - vars.put("orderId", 12345L); - CreateProcessInstanceRequest req = new CreateProcessInstanceRequest() - .processDefinitionKey(123456L) - .variables(vars); + .processDefinitionKey(definitionKey) + .variables(Map.of("orderId", 12345L)); piApi.createProcessInstance(req); } } ``` -Notes: -- Available typed APIs include ProcessDefinitionApi, ProcessInstanceApi, JobApi, MessageApi, etc. Construct them with the provided ApiClient. -- Methods and DTOs come from the generated package `org.zenbpm.client.api` and `org.zenbpm.client.api.dto`. +Available typed APIs include `ProcessDefinitionApi`, `ProcessInstanceApi`, `JobApi`, `MessageApi`, and others. Methods and DTOs come from the generated packages `org.pbinitiative.zenbpm.client.api` and `org.pbinitiative.zenbpm.client.api.dto`. -#### 2) Register a gRPC job worker -Create a Spring bean with a method annotated by `@JobWorker`. Accepted method signatures: -- no parameters -- one parameter of type `org.zenbpm.proto.Zenbpm.WaitingJob` -- one parameter of type `org.zenbpm.grpc.JobContext` -- one parameter of type `Map` +### Register a worker (gRPC) -Return value can be any object and will be serialized as variables for job completion. Throwing an exception fails the job. +Annotate a Spring bean method with `@JobWorker("")`. The gRPC worker manager connects on application startup when `zenbpm.jobWorkerEnabled` is `true`. ```java import org.springframework.stereotype.Component; -import org.zenbpm.grpc.JobWorker; -import org.zenbpm.grpc.JobContext; +import org.pbinitiative.zenbpm.grpc.JobWorker; +import org.pbinitiative.zenbpm.grpc.JobContext; import java.util.Map; -import java.util.HashMap; @Component public class EmailWorker { @JobWorker("send-email") public Map handleJob(JobContext ctx) { - Map vars = ctx.getVariables(); + Map vars = ctx.getVariables(); String to = (String) vars.get("email"); - // send email ... + // ... send the email ... - Map result = new HashMap<>(); - result.put("success", true); - result.put("message", "Email to " + to + " mocked successfully"); - return result; + // Return value is serialized as output variables on job completion. + return Map.of("emailSent", true); + // Throw an exception to fail the job (triggering a retry). } } ``` -The gRPC worker manager connects on application start if `zenbpm.jobWorkerEnabled` is true. +Accepted `@JobWorker` method parameters (pick one): + +- no parameters +- `org.pbinitiative.zenbpm.proto.Zenbpm.WaitingJob` +- `org.pbinitiative.zenbpm.grpc.JobContext` +- `Map` (the job variables) + +--- + +## Configuration reference + +Full set of `application.yml` options for the Java Spring Boot starter. + +| Key | Purpose | +|---|---| +| `zenbpm.restUrl` | Base URL of the engine REST API (include `/v1`). | +| `zenbpm.grpcHost` | Engine gRPC host. | +| `zenbpm.grpcPort` | Engine gRPC port. | +| `zenbpm.grpcPlaintext` | Use plaintext gRPC (no TLS). Local/dev only. | +| `zenbpm.jobWorkerEnabled` | Connect registered job workers on startup. | +| `zenbpm.otelEnabled` | Enable OpenTelemetry interceptors (REST) and spans (gRPC). | +| `zenbpm.restLoggingEnabled` | Enable request/response logging for the REST client. | +| `zenbpm.grpcLoggingEnabled` | Enable logging for the gRPC client. | + +> See the [client repository](https://github.com/pbinitiative/zenbpm-java-client) for the authoritative list of options and their defaults. + +Logging verbosity is controlled through standard Spring logging levels, configured **per client** (`org.pbinitiative.zenbpm.rest`, `org.pbinitiative.zenbpm.grpc`): + +- `DEBUG` — exposes request/response headers. +- `TRACE` — exposes full request and response **bodies**. **Never use `TRACE` in production**; it can leak sensitive data. + +```yaml +logging: + level: + org.pbinitiative.zenbpm.rest: DEBUG + org.pbinitiative.zenbpm.grpc: DEBUG +``` + +## Building the Java client from source + +Most users consume the published Maven Central artifacts and don't need this. To build the Java client from source: + +```bash +mvn clean package +``` ## Future Clients +Officially supported clients are planned for: + - Python - JavaScript / TypeScript diff --git a/docs/tutorials/assets/images/approval-process.svg b/docs/tutorials/assets/images/approval-process.svg new file mode 100644 index 000000000..034378beb --- /dev/null +++ b/docs/tutorials/assets/images/approval-process.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + Request received + + + + + + + + + + Approve request + + + + + + + Done + diff --git a/docs/tutorials/assets/images/first-bpmn-process.svg b/docs/tutorials/assets/images/first-bpmn-process.svg new file mode 100644 index 000000000..fad3d3dcb --- /dev/null +++ b/docs/tutorials/assets/images/first-bpmn-process.svg @@ -0,0 +1,4 @@ + + + +StartLog GreetingDone \ No newline at end of file diff --git a/docs/tutorials/getting-started.mdx b/docs/tutorials/getting-started.mdx deleted file mode 100644 index 2330e3e2a..000000000 --- a/docs/tutorials/getting-started.mdx +++ /dev/null @@ -1,165 +0,0 @@ -import ApiOperation from "@theme/ApiOperation"; - -# Getting Started with ZenBPM - -This tutorial will guide you through the basics of ZenBPM, helping you set up your first BPMN process and execute it. - -## What You'll Learn - -- How to install and run ZenBPM -- How to create a simple BPMN process -- How to deploy the process to ZenBPM -- How to start a process instance -- How to monitor the process execution - -## Prerequisites - -- Basic understanding of what BPMN is -- Docker installed on your machine - -## Step 1: Install and Run ZenBPM - -The easiest way to get started with ZenBPM is to use Docker. - -1. Pull the ZenBPM Docker image: - -```bash -docker pull ghcr.io/pbinitiative/zenbpm:latest -``` - -::: - -2. Run the ZenBPM container: - -```bash -docker run -d -p 8080:8080 -p 9090:9090 --name zenbpm ghcr.io/pbinitiative/zenbpm:latest -``` - -::: - -This will start ZenBPM with: - -- REST API available at localhost:8080 -- gRPC API available at localhost:9090 - -## Step 2: Create a Simple BPMN Process - -For this tutorial, we'll create a simple "Hello World" process with a start event, a message throw event, and an end event. - -1. Create a file named `hello-world.bpmn` with the following content: - -```xml - - - - - Flow_1l01xu9 - - - - Flow_02ezsew - - - - Flow_1l01xu9 - Flow_02ezsew - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -This BPMN file defines a simple process with: - -- A start event -- A Message intermediate throw event -- An end event - -## Step 3: Deploy the Process to ZenBPM - -Now that we have our BPMN process, let's deploy it to ZenBPM: - - - -You should receive a response with details about the deployed process definition, including a `processDefinitionKey` that uniquely identifies it. - -## Step 4: Start a Process Instance - -With the process definition deployed, we can now start a process instance: - - - -This will start a new instance of our "Hello World" process. The response will include a `processInstanceKey` that uniquely identifies this instance. - -## Step 5: Monitor the Process Execution - -Let's check the status of our process instance: - - - -Replace `{processInstanceKey}` with the actual key from the previous step. - -You should see details about the process instance, including its current state. Since our process is very simple, it might have already completed by the time you check. - -## Step 6: View Process history - -:::warning -this feature is not implemented yet -::: -To see what activities were executed in our process: - - - -This will show you all the activities that were executed as part of the process instance, including the message throw event. - -## Conclusion - -Congratulations! You've successfully: - -- Set up ZenBPM -- Created a simple BPMN process -- Deployed the process to ZenBPM -- Started a process instance -- Monitored the process execution - -## Next Steps - -[//]: # "TODO: Link documents after creation" - -Now that you've completed this basic tutorial, you might want to: - -- Learn how to [create more complex BPMN processes](/future-feature) _`TODO`_ -- Explore [user tasks and forms](/future-feature) _`TODO`_ -- Understand [message events and correlation](/future-feature) _`TODO`_ -- Set up [process monitoring and observability](/future-feature) _`TODO`_ diff --git a/docs/tutorials/getting-started/_category_.json b/docs/tutorials/getting-started/_category_.json new file mode 100644 index 000000000..fb126c888 --- /dev/null +++ b/docs/tutorials/getting-started/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Getting Started", + "position": 1, + "link": { + "type": "generated-index", + "description": "Get up and running with the ZenBPM engine: start it, deploy and run your first BPMN process, then orchestrate a human task." + } +} diff --git a/docs/tutorials/getting-started/first-bpmn-process.mdx b/docs/tutorials/getting-started/first-bpmn-process.mdx new file mode 100644 index 000000000..602340241 --- /dev/null +++ b/docs/tutorials/getting-started/first-bpmn-process.mdx @@ -0,0 +1,308 @@ +--- +id: first-bpmn-process +title: First BPMN process +sidebar_position: 2 +description: Deploy a BPMN process over REST, write a worker that connects over gRPC, and watch a process instance execute end to end. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# First BPMN process + +With the engine running from the [previous chapter](run-the-engine), you'll now put it to work — talking to it over its REST and gRPC APIs, exactly as your own code will. By the end you'll have deployed a process, written a worker that does real work, and watched an instance run to completion. + +{/* TODO: restore once the UI/platform docs land. +If you'd rather deploy and run processes visually, start with the [ZenBPM UI documentation](/ui) or the [Getting Started platform basics](/platform) instead — this page uses only the terminal and code. +*/} + +:::info New to BPM? +The engine executes business processes described as **BPMN** diagrams. A **process definition** is the blueprint; a running copy of it is a **process instance**; a **worker** is a program *you* write that carries out a step. That's enough for this tutorial — for the full picture, see [BPM Concepts](/explanation/bpm-concepts). + +Coming from Camunda or Zeebe? This is the familiar external-worker model over gRPC; skim [the terminology mapping](/explanation/bpm-concepts#terminology-mapping) and read straight on. +::: + +## What you'll build {#what-youll-build} + +You'll run a minimal process (`first-bpmn-process.bpmn`): a single service task, *Log Greeting*. When an instance reaches that task, the engine creates a **job** and waits. Nothing happens until a **worker** — which you'll write — connects, picks the job up, reads a variable, and prints a greeting. + +{/* Rendered from getting-started/02-first-bpmn-process/first-bpmn-process.bpmn via bpmn-to-image. + Keep the .bpmn source next to the SVG so the figure always matches the real process. */} +![The first BPMN process: Start → Log Greeting (service task) → Done](../assets/images/first-bpmn-process.svg) + +That "the engine creates a job and waits for your worker" mechanic is the heart of ZenBPM — this tutorial exists mainly to make it concrete. + +## Prerequisites {#prerequisites} + +- The **engine running locally** and the examples repo cloned — see [Run the engine](run-the-engine) +- `curl` — or any HTTP client +- For the worker (Step 3), **either** [Java](https://adoptium.net/) 17+ with [Maven](https://maven.apache.org/) **or** [Go](https://go.dev/dl/) 1.22+ + +## Step 1: Deploy a process {#step-1-deploy-a-process} + +A **process definition** is a `.bpmn` file. This chapter's process is `first-bpmn-process.bpmn` in the repo you cloned. From `getting-started/`, move into the chapter folder: + +```bash +cd 02-first-bpmn-process +``` + +Deploy it with a multipart upload: + +```bash +curl -X POST http://localhost:8080/v1/process-definitions \ + -F "resource=@first-bpmn-process.bpmn" +``` + +The response contains a `processDefinitionKey` — a generated number that uniquely identifies this deployed definition. Note it; you'll start an instance from it next. + +```json +{ "processDefinitionKey": 4503599627370498 } +``` + +## Step 2: Start an instance (and watch it wait) {#step-2-start-an-instance} + +Starting an instance asks the engine to execute one copy of the definition. Use the key from Step 1: + +```bash +curl -X POST http://localhost:8080/v1/process-instances \ + -H "Content-Type: application/json" \ + -d '{"processDefinitionKey": 4503599627370498, "variables": {}}' +``` + +You get back a `processInstanceKey` and a state of `active`: + +```json +{ + "key": 4503599627370501, + "processDefinitionKey": 4503599627370498, + "state": "active" +} +``` + +Query the instance by its key: + +```bash +curl http://localhost:8080/v1/process-instances/4503599627370501 +``` + +It's **still active** — the token has reached the *Log Greeting* service task, the engine has created a job for it, and there it sits. The engine will never run your business logic itself; it's waiting for a worker to take the job. That's what you'll build now. + +## Step 3: Write a worker {#step-3-write-a-worker} + +A **worker** connects to the engine over gRPC, subscribes to a **job type**, and for each job: reads the variables, does the work, and reports completion. This process's service task creates jobs of type `log-worker`; the work is simply to print the `log` variable. + +ZenBPM ships officially supported clients for Java and Go, and this chapter includes a ready-to-run worker project in each (the `java/` and `go/` folders of [`getting-started/02-first-bpmn-process`](https://github.com/pbinitiative/zenbpm-examples/tree/main/getting-started/02-first-bpmn-process)). Pick your language: + + + + +The Java client is a Spring Boot starter: you declare a worker as a bean method annotated with `@JobWorker`, and the starter connects to the engine on startup. + +The client is on **Maven Central** under `org.pbinitiative.zenbpm`, versioned in step with the engine (`1.4.0`). Here is a complete, copy-pasteable `pom.xml`: + +```xml + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + + org.pbinitiative.examples + first-bpmn-process-worker + 1.0.0 + + + 17 + 1.4.0 + 1.58.0 + + + + + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.pbinitiative.zenbpm + zenbpm-spring-boot-starter + ${zenbpm.version} + + + + io.grpc + grpc-netty-shaded + 1.80.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` + +Point the starter at your engine in `application.yml`: + +```yaml +zenbpm: + grpcHost: localhost + grpcPort: 9090 + grpcPlaintext: true + jobWorkerEnabled: true +``` + +Declare the worker. The method subscribes to the `log-worker` job type, reads the `log` variable, and prints it: + +```java +import org.springframework.stereotype.Component; +import org.pbinitiative.zenbpm.grpc.JobWorker; +import org.pbinitiative.zenbpm.grpc.JobContext; +import java.util.Map; + +@Component +public class LogWorker { + @JobWorker("log-worker") + public Map handleJob(JobContext ctx) { + System.out.println("[log-worker] " + ctx.getVariables().get("log")); + return Map.of(); // no output variables; job complete + } +} +``` + +This worker is ready in the chapter's `java/` folder. From `02-first-bpmn-process/`, start it: + +```bash +cd java +mvn spring-boot:run +``` + + + + +The Go client is part of the engine module (`github.com/pbinitiative/zenbpm/pkg/zenclient`). Register a worker with a handler function: + +```go +package main + +import ( + "context" + "fmt" + + "github.com/pbinitiative/zenbpm/pkg/zenclient" + "github.com/pbinitiative/zenbpm/pkg/zenclient/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func main() { + conn, err := grpc.NewClient( + "127.0.0.1:9090", + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + panic(err) + } + defer conn.Close() + + zen := zenclient.NewGrpc(conn) + + // Subscribe to "log-worker" jobs. + zen.RegisterWorker(context.Background(), "first-bpmn-process-worker", + func(ctx context.Context, job *proto.WaitingJob) (map[string]any, *zenclient.WorkerError) { + vars := job.GetVariables() + fmt.Printf("[log-worker] %v\n", vars["log"]) + return map[string]any{}, nil // no output variables; job complete + }, + "log-worker", + ) + + select {} // keep the worker running +} +``` + +This worker is ready in the chapter's `go/` folder. From `02-first-bpmn-process/`, start it: + +```bash +cd go +go mod tidy +go run . +``` + + + + +The worker connects, finds the waiting job from Step 2, and prints: + +``` +[log-worker] Hello, World! +``` + +The moment it reports the job complete, the engine advances the token past the service task to the end event. + +:::note Client reference +Full dependency coordinates, configuration options, and REST/gRPC usage for both languages are in the [Client Libraries](/static/client-libraries) reference. +::: + +## Step 4: Observe completion {#step-4-observe-completion} + +Query the same instance again: + +```bash +curl http://localhost:8080/v1/process-instances/4503599627370501 +``` + +Its state is now `completed`. You just watched the full engine loop: a token entered the process, waited at a service task, your worker did the work, and the instance finished — driven entirely through the API, exactly as your own application would drive it. + +## Step 5: Make it yours {#step-5-make-it-yours} + +Change the greeting and redeploy to see versioning in action. In `first-bpmn-process.bpmn`, find the input mapping that sets `log` to `Hello, World!` and change the text. Redeploy the same file: + +```bash +curl -X POST http://localhost:8080/v1/process-definitions \ + -F "resource=@first-bpmn-process.bpmn" +``` + +Because the process id is unchanged, the engine stores this as a **new version** and returns a new `processDefinitionKey`. Start an instance against the new key (Step 2) with your worker still running — your new greeting prints. Older instances keep running on the version they started with. + +## Clean up {#clean-up} + +Stop the worker (Ctrl+C), then stop the engine from the `getting-started/` folder: + +```bash +docker compose down +``` + +## Next steps {#next-steps} + +- **Understand the model.** [BPM Concepts](/explanation/bpm-concepts) — definitions, instances, tokens, and the job/worker mechanic in one short read. +- **A realistic process.** The [Commission Payout example](https://github.com/pbinitiative/zenbpm-examples/tree/main/examples/processes/02-commission-payout) adds user tasks and an exclusive gateway. +- **More workers.** [`examples/workers/`](https://github.com/pbinitiative/zenbpm-examples/tree/main/examples/workers) shows several workers in one Go project. +- **The APIs in full.** [REST (OpenAPI)](/static/openapi) and the [gRPC proto](/zenbpm.proto). +- **Engine internals.** [Architecture](/category/architecture). diff --git a/docs/tutorials/getting-started/orchestrate-human-tasks.mdx b/docs/tutorials/getting-started/orchestrate-human-tasks.mdx new file mode 100644 index 000000000..7ac6b0ed9 --- /dev/null +++ b/docs/tutorials/getting-started/orchestrate-human-tasks.mdx @@ -0,0 +1,200 @@ +--- +id: orchestrate-human-tasks +title: Orchestrate human tasks +sidebar_position: 3 +description: Model a BPMN user task, run it on the ZenBPM engine, and drive the human step over REST — list the pending task, assign it, and complete it with a decision. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Orchestrate human tasks + +In the [previous chapter](first-bpmn-process), a *service task* handed work to a worker you wrote. Many processes also need a step that waits for a **person** — an approval, a review, a form to fill in. In BPMN that's a **user task**, and this chapter shows how to drive one with the engine. + +The key difference: a service task is completed by a worker that *polls* the engine; a user task is completed by *your application* (or a person through the UI) calling the engine when the human is done. At the engine level both are **jobs** — you just interact with a user-task job through the REST `/jobs` API instead of a gRPC worker. + +:::info Prerequisites +The engine running locally and the examples repo cloned, as set up in [Run the engine](run-the-engine). No worker is needed this time — you'll play the human. +::: + +## What you'll build {#what-youll-build} + +A minimal **approval** process: a request comes in, a person approves or rejects it, done. + +![The Approval process: Request received → Approve request (user task) → Done](../assets/images/approval-process.svg) + +This chapter's process is `approval.bpmn`. From `getting-started/`, move into the chapter folder: + +```bash +cd 03-orchestrate-human-tasks +``` + +In the BPMN, the human step is a `userTask` marked with a `` extension. That's what tells the engine to create a **user-task job** and wait, rather than dispatching to a worker. + +## Step 1: Deploy and start {#step-1-deploy-and-start} + +Deploy the process and start an instance, exactly as in the previous tutorial: + +```bash +# Deploy +curl -X POST http://localhost:8080/v1/process-definitions \ + -F "resource=@approval.bpmn" +# -> note the returned processDefinitionKey + +# Start an instance +curl -X POST http://localhost:8080/v1/process-instances \ + -H "Content-Type: application/json" \ + -d '{"processDefinitionKey": , "variables": {}}' +# -> note the returned processInstanceKey +``` + +Query the instance — it's `active`, because the token has reached the user task and is waiting for a person: + +```bash +curl http://localhost:8080/v1/process-instances/ +``` + +## Step 2: Find the pending task {#step-2-find-the-task} + +User tasks are jobs of type `user-task`. List the ones waiting to be worked on: + + + + +```bash +curl "http://localhost:8080/v1/jobs?jobType=user-task&state=active" +``` + + + + +```java +// JobApi is generated from the OpenAPI spec; construct it with the ApiClient. +// verify: exact method/param names against the generated JobApi. +JobApi jobApi = new JobApi(zenbpm.getApiClient()); +JobPartitionPage jobs = jobApi.getJobs( + /* processInstanceKey */ null, + /* jobType */ "user-task", + /* assignee */ null, + /* state */ "active", + /* page */ 1, + /* size */ 10, + /* sortBy */ null, + /* sortOrder */ null +); +``` + + + + +```go +// verify: exact params/struct names against the generated zenclient. +restClient, _ := zenclient.NewClient("http://localhost:8080/v1") +jobType := "user-task" +state := zenclient.JobStateActive +resp, _ := restClient.GetJobs(ctx, &zenclient.GetJobsParams{ + JobType: &jobType, + State: &state, +}) +``` + + + + +The response lists each waiting task with its `key`, the `elementId` (`Task_Approve`), the `processInstanceKey`, and its `inputVariables` — here the `instruction` the process passed in for the person to act on. Note the job `key`; you'll use it next. + +## Step 3: Assign the task {#step-3-assign-the-task} + +Claiming a task records who is responsible for it. In a real app this happens when a user opens the task in their inbox. + + + + +```bash +curl -X POST http://localhost:8080/v1/jobs//assign \ + -H "Content-Type: application/json" \ + -d '{"assignee": "john.doe"}' +``` + + + + +```java +// verify: DTO name (AssignJobRequest) against the generated client. +jobApi.assignJob(jobKey, new AssignJobRequest().assignee("john.doe")); +``` + + + + +```go +// verify: request body type name against the generated zenclient. +restClient.AssignJob(ctx, jobKey, zenclient.AssignJobJSONRequestBody{ + Assignee: "john.doe", +}) +``` + + + + +A successful assign returns `204 No Content`. + +## Step 4: Complete the task with a decision {#step-4-complete-the-task} + +When the person is done, complete the job. The variables you send become part of the process instance — this is how the human's decision flows into the rest of the process (for example, a later gateway could branch on `approved`). + + + + +```bash +curl -X POST http://localhost:8080/v1/jobs//complete \ + -H "Content-Type: application/json" \ + -d '{"variables": {"approved": true}}' +``` + + + + +```java +// verify: DTO name (CompleteJobRequest) against the generated client. +jobApi.completeJob(jobKey, new CompleteJobRequest() + .variables(Map.of("approved", true))); +``` + + + + +```go +// verify: request body type name against the generated zenclient. +restClient.CompleteJob(ctx, jobKey, zenclient.CompleteJobJSONRequestBody{ + Variables: &map[string]any{"approved": true}, +}) +``` + + + + +The token advances to the end event. Query the instance again — it's now `completed`, and its variables include the `approved` decision you submitted: + +```bash +curl http://localhost:8080/v1/process-instances/ +``` + +## Rejecting, and where your app fits {#rejecting-and-where-your-app-fits} + +Completing with `{"approved": false}` is a perfectly normal outcome — "reject" is a decision, not an error, so you still **complete** the job (with a different variable) rather than failing it. Reserve `POST /jobs/{jobKey}/fail` for genuine failures (a downstream system was unreachable, invalid data), which trigger a retry. + +In a real system you don't call these endpoints by hand. Your application backend: + +1. lists user-task jobs (Step 2) to build each person's task inbox, +2. assigns a task when someone opens it (Step 3), +3. shows a form, and on submit completes the task with the entered variables (Step 4). + +The engine stays the source of truth for what's pending and what was decided; your app is just the human-facing surface — and the ZenBPM UI is one ready-made such surface. + +## Next steps {#next-steps} + +- **A realistic process.** The [Commission Payout example](https://github.com/pbinitiative/zenbpm-examples/tree/main/examples/processes/02-commission-payout) combines user tasks with an exclusive gateway (branching on the human decision) and an AI service task. +- **The concepts.** [BPM Concepts → tasks](/explanation/bpm-concepts#tasks) explains user vs. service tasks and the job model. +- **The full API.** The `/jobs` endpoints in the [REST reference](/static/openapi). diff --git a/docs/tutorials/getting-started/run-the-engine.mdx b/docs/tutorials/getting-started/run-the-engine.mdx new file mode 100644 index 000000000..f540185e8 --- /dev/null +++ b/docs/tutorials/getting-started/run-the-engine.mdx @@ -0,0 +1,98 @@ +--- +id: run-the-engine +title: Run the engine +sidebar_position: 1 +description: Start the ZenBPM engine with Docker Compose and verify it is healthy — the foundation for every other Getting Started chapter. +--- + +# Run the engine + +Everything in Getting Started builds on a running engine. This short chapter gets one up on your machine and confirms it's healthy — no BPMN yet. Once you're done here, move on to [First BPMN process](first-bpmn-process). + +:::info New to BPM? +ZenBPM is a **process engine**: you give it business processes described as BPMN diagrams and it executes them. You don't need to know BPMN to start the engine. For background, see [BPM Concepts](/explanation/bpm-concepts). +::: + +## Prerequisites {#prerequisites} + +- [Docker](https://docs.docker.com/get-docker/) with Compose +- [Git](https://git-scm.com/downloads) +- `curl` — or any HTTP client + +:::note Windows +Run the commands in this tutorial from **Git Bash** (installed with Git) or **WSL**. They use bash syntax — line continuations (`\`), quoting, and the real `curl`. In PowerShell, `curl` is an alias for a different command and won't behave the same. +::: + +## Get the examples {#get-the-examples} + +The companion projects for this tutorial live in the [zenbpm-examples](https://github.com/pbinitiative/zenbpm-examples) repository. Clone it and move into the Getting Started track: + +```bash +git clone https://github.com/pbinitiative/zenbpm-examples.git +cd zenbpm-examples/getting-started +``` + +Every chapter below is a folder here, and the engine is defined once in `compose.yaml` at this level. + +## Start the engine {#start-the-engine} + +From the `getting-started/` folder: + +```bash +docker compose up -d +``` + +The first run pulls the image and takes a moment. This starts **only** the engine (the container is named `zenbpm`); the workers are something you run yourself in later chapters. It exposes two APIs: + +| Address | Protocol | What you use it for | +|---|---|---| +| `http://localhost:8080` | REST | Deploy processes, start instances, query state, drive user tasks | +| `localhost:9090` | gRPC | Connect workers that carry out service tasks | + +## Verify it's running {#verify} + +Ask the engine for its deployed processes: + +```bash +curl http://localhost:8080/v1/process-definitions +``` + +An empty list (`[]` or an empty `items` array) is exactly what you want: the engine is up and answering, with nothing deployed yet. **You're ready to deploy your first process.** + +If the request is refused, give the engine a few seconds to finish starting and try again, or see [Troubleshooting](#troubleshooting). + +## Optional: web UI {#optional-ui} + +ZenBPM has a web UI. It's off by default; start it alongside the engine with: + +```bash +docker compose --profile ui up -d +``` + +Then open http://localhost:9000. This tutorial uses only the terminal, but the UI is handy for watching instances. + +## Stop {#stop} + +Stop the engine but keep everything you deployed: + +```bash +docker compose down +``` + +Engine state is kept in a named volume, so your deployed processes and instances survive a restart. To wipe it and start completely fresh: + +```bash +docker compose down -v +``` + +## Troubleshooting {#troubleshooting} + +**Port already in use** (`bind: address already in use`) — another process holds `8080` or `9090`. Stop it, or change the host ports in `compose.yaml` (e.g. `18080:8080`) and use those ports in the following chapters. + +:::note Beyond local +The compose file mounts a small single-node config (`conf/zenbpm/conf.yaml`) that bootstraps the engine's cluster with one partition — enough to run everything in this tutorial. That file is where cluster, partition, and script-engine settings live. Durable persistence (rqlite), multi-node clustering, and observability are operational topics covered in the [How-to guides](/category/how-to). +::: + +## Next {#next} + +The engine is running. Next, [First BPMN process](first-bpmn-process) — deploy a process, write a worker, and watch an instance execute.