bpm-engine 0.2.0

Lightweight embeddable BPM runtime for long-running, stateful workflows with tokens, timers, Saga compensation, and crash recovery
Documentation
---
layout: default
---

# Rust Worker SDK

The **Rust Worker SDK** lets you implement external-task workers that poll the engine, lock tasks, run your logic, and complete or fail tasks. No BPM knowledge is required in worker code.

---

## Crates and example

- **Crate:** [crates/worker-sdk]crates/worker-sdk
- **Example:** [crates/worker-sdk/examples/payment.rs]crates/worker-sdk/examples/payment.rs

---

## Quick start

1. Start the engine: `cargo run -p bpm-server-rest`
2. Deploy or use a process that has an ExternalTask node (e.g. `task_type = "payment"`).
3. Run the worker: `cargo run -p bpm-worker-sdk --example payment`

---

## Delivery and idempotency

- The **engine** guarantees **exactly-once token completion**: each token is advanced at most once.
- **Workers** receive tasks **at least once**: the same task may be fetched again after a crash or lease expiry. Handlers **must be idempotent** (e.g. keyed by business id or task id).

---

## Worker Responsibility Contract

- Tasks may be **redelivered** (same task_id / token after worker crash or lease expiry).
- Handlers **must be idempotent** (e.g. keyed by business id or task id; duplicate completion must be safe).
- Do **not** perform non-rollbackable side effects in the handler unless you implement your own deduplication or compensation (e.g. outbox or saga step).

---

## Core types

- **EngineClient** — HTTP client for the engine REST API (base URL, fetch-and-lock, complete, fail).
- **WorkerConfig** — Worker id, max tasks, lock duration, poll interval.
- **TaskHandler** — Trait you implement: `task_type()` and `handle(task, ctx) -> TaskResult`.
- **Worker** — Poll loop: fetches tasks, dispatches to your handler, completes or fails.

---

## Implementing a handler

Handler must be idempotent; the same task may be delivered more than once.

```rust
use async_trait::async_trait;
use bpm_worker_sdk::{
    EngineClient, ExternalTask, TaskContext, TaskHandler, TaskResult, Worker, WorkerConfig,
};
use std::collections::HashMap;

struct PaymentHandler;

#[async_trait]
impl TaskHandler for PaymentHandler {
    fn task_type(&self) -> &str {
        "payment"
    }

    async fn handle(&self, task: ExternalTask, _ctx: TaskContext) -> TaskResult {
        let amount = task.variables.get("amount").cloned().unwrap_or_else(|| "0".to_string());
        // ... your logic ...
        let mut variables = HashMap::new();
        variables.insert("status".to_string(), "PAID".to_string());
        TaskResult::Complete { variables }
    }
}
```

---

## Running the worker

```rust
let client = EngineClient::new("http://127.0.0.1:3000");
let config = WorkerConfig::new("payment-worker-1")
    .max_tasks(5)
    .lock_duration(Duration::from_secs(30))
    .poll_interval(Duration::from_secs(1));

let worker = Worker::builder()
    .client(client)
    .handler(PaymentHandler)
    .config(config)
    .build();

worker.start().await;
```

---

## Task result

- **TaskResult::Complete { variables }** — Complete the task and merge variables back into the process.
- **TaskResult::Fail { error, retry_after }** — Fail the task; engine may retry according to retries/timeout.

---

## Dependencies

Add to your `Cargo.toml`:

```toml
bpm-worker-sdk = { path = "../crates/worker-sdk" }
# or from the workspace root
```

You need `tokio` and `async-trait` for the async handler.

---

## See also

- [api-spec.md]api-spec.md — REST API (external-tasks endpoints)
- [execution-model.md]execution-model.md — Token claim and concurrency
- [architecture.md]architecture.md — Engine overview