busybee 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -7
- data/README.md +70 -42
- data/docs/client/quick_start.md +279 -0
- data/docs/client.md +825 -0
- data/docs/configuration.md +550 -0
- data/docs/grpc.md +50 -25
- data/docs/testing.md +118 -28
- data/docs/workers.md +982 -0
- data/exe/busybee +6 -0
- data/lib/busybee/cli.rb +173 -0
- data/lib/busybee/client/error_handling.rb +37 -0
- data/lib/busybee/client/job_operations.rb +236 -0
- data/lib/busybee/client/message_operations.rb +84 -0
- data/lib/busybee/client/process_operations.rb +108 -0
- data/lib/busybee/client/variable_operations.rb +64 -0
- data/lib/busybee/client.rb +87 -0
- data/lib/busybee/configure.rb +290 -0
- data/lib/busybee/credentials/camunda_cloud.rb +58 -0
- data/lib/busybee/credentials/insecure.rb +24 -0
- data/lib/busybee/credentials/oauth.rb +157 -0
- data/lib/busybee/credentials/tls.rb +43 -0
- data/lib/busybee/credentials.rb +200 -0
- data/lib/busybee/defaults.rb +20 -0
- data/lib/busybee/error.rb +50 -0
- data/lib/busybee/grpc/error.rb +60 -0
- data/lib/busybee/grpc.rb +2 -2
- data/lib/busybee/job.rb +219 -0
- data/lib/busybee/job_stream.rb +85 -0
- data/lib/busybee/logging.rb +61 -0
- data/lib/busybee/railtie.rb +113 -0
- data/lib/busybee/runner/hybrid.rb +64 -0
- data/lib/busybee/runner/multi.rb +101 -0
- data/lib/busybee/runner/polling.rb +54 -0
- data/lib/busybee/runner/streaming.rb +159 -0
- data/lib/busybee/runner.rb +97 -0
- data/lib/busybee/runtime_config.rb +184 -0
- data/lib/busybee/serialization.rb +100 -0
- data/lib/busybee/testing/activated_job.rb +33 -8
- data/lib/busybee/testing/helpers/execution.rb +139 -0
- data/lib/busybee/testing/helpers/support.rb +78 -0
- data/lib/busybee/testing/helpers.rb +56 -66
- data/lib/busybee/testing/matchers/complete_job.rb +55 -0
- data/lib/busybee/testing/matchers/fail_job.rb +75 -0
- data/lib/busybee/testing/matchers/have_activated.rb +1 -1
- data/lib/busybee/testing/matchers/have_available_jobs.rb +44 -0
- data/lib/busybee/testing/matchers/throw_bpmn_error_on.rb +72 -0
- data/lib/busybee/testing.rb +5 -33
- data/lib/busybee/version.rb +1 -1
- data/lib/busybee/worker/configuration.rb +287 -0
- data/lib/busybee/worker/dsl.rb +187 -0
- data/lib/busybee/worker/shutdown.rb +27 -0
- data/lib/busybee/worker.rb +130 -0
- data/lib/busybee.rb +134 -2
- metadata +80 -3
data/docs/client.md
ADDED
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
# Busybee::Client
|
|
2
|
+
|
|
3
|
+
The `Busybee::Client` class is the main entry point for interacting with the Zeebe workflow engine from your Ruby app. It provides methods for deploying workflows, starting and cancelling process instances, publishing messages, and activating jobs for processing. The Client wraps the low-level GRPC layer with keyword arguments, sensible defaults, and proper exception handling.
|
|
4
|
+
|
|
5
|
+
If you haven't used Zeebe or Busybee before, check out the [quick start guide](client/quick_start.md), which will get you from zero to a deployed process and running instance in about 10 minutes. This doc is a more complete explanation and reference.
|
|
6
|
+
|
|
7
|
+
| Section | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| [Providing Credentials](#providing-credentials) | How to connect and authenticate to a Zeebe cluster |
|
|
10
|
+
| [Error Handling](#error-handling) | Exception hierarchy and retry configuration |
|
|
11
|
+
| [Working with Jobs](#working-with-jobs) | Conceptual guide to job processing (polling vs streaming) |
|
|
12
|
+
| [API Reference](#api-reference) | Complete method documentation |
|
|
13
|
+
| [Configuration Reference](configuration.md) | Full configuration options (logging, retry, Rails integration) |
|
|
14
|
+
|
|
15
|
+
## Providing Credentials
|
|
16
|
+
|
|
17
|
+
An instance of Busybee::Client relies on an instance of Busybee::Credentials to tell it where to find the Zeebe cluster and how to authenticate to it.
|
|
18
|
+
|
|
19
|
+
There are four types of credentials supported by Busybee for different environments:
|
|
20
|
+
|
|
21
|
+
| Credential Class | Type Symbol | Use Cases | SSL/TLS | Authentication |
|
|
22
|
+
|------|----------|-----|----------------|----|
|
|
23
|
+
| Busybee::Credentials::Insecure | `:insecure` | Local development, Docker, CI | No | None |
|
|
24
|
+
| Busybee::Credentials::TLS | `:tls` | Self-hosted with SSL/TLS | Yes | Server cert only |
|
|
25
|
+
| Busybee::Credentials::OAuth | `:oauth` | Self-hosted with OAuth | Yes | OAuth2 client credentials |
|
|
26
|
+
| Busybee::Credentials::CamundaCloud | `:camunda_cloud` | Camunda Cloud SaaS | Yes | OAuth2 (auto-configured) |
|
|
27
|
+
|
|
28
|
+
The most long-form way to create a Busybee::Client is to create an instance of one of these classes first, and pass that as the argument to Client.new:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
credentials = Busybee::Credentials::Insecure.new(cluster_address: "zeebe:26500")
|
|
32
|
+
client = Busybee::Client.new(credentials)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You can also configure the gem with a single set of credentials, and call Client.new with no arguments in order to use the configured credentials implicitly:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# in config/application.rb or config/initializers/busybee.rb:
|
|
39
|
+
Busybee.configure do |config|
|
|
40
|
+
config.credentials = Busybee::Credentials::TLS.new(
|
|
41
|
+
cluster_address: "zeebe:26500",
|
|
42
|
+
certificate_file: "/path/to/ca.crt" # optional, uses system default otherwise
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# then, anywhere in application code:
|
|
47
|
+
client = Busybee::Client.new
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or, just configure the cluster_address and credential_type, and let Busybee read your secret values out of your ENV vars:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# in config/application.rb or config/initializers/busybee.rb:
|
|
54
|
+
Busybee.configure do |config|
|
|
55
|
+
config.cluster_address = "zeebe:26500"
|
|
56
|
+
config.credential_type = :oauth # token URL, audience, scope, client ID, and client secret will be read from env vars
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# then, anywhere in application code:
|
|
60
|
+
client = Busybee::Client.new
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For testing, it can be helpful to create multiple clients with different credentials. You can always pass a complete set of credentials directly to Client.new and let it figure out what type of credentials to build automatically, if you wish:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
insecure_client = Busybee::Client.new(
|
|
67
|
+
cluster_address: "insecure_cluster:26500",
|
|
68
|
+
insecure: true
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
tls_client = Busybee::Client.new(
|
|
72
|
+
# if cluster_address is not given to any of these, it will use the configured cluster_address (see below):
|
|
73
|
+
certificate_file: "/path/to/ca.crt"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
oauth_client = Busybee::Client.new(
|
|
77
|
+
cluster_address: "oauth_cluster:26500",
|
|
78
|
+
token_url: "https://auth.example.com/oauth/token",
|
|
79
|
+
client_id: "my-client-id",
|
|
80
|
+
client_secret: "my-client-secret",
|
|
81
|
+
audience: "my-token-audience"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
camunda_cloud_client = Busybee::Client.new(
|
|
85
|
+
# for Camunda Cloud, the cluster address and OAuth configuration are derived automatically:
|
|
86
|
+
client_id: "my-client-id",
|
|
87
|
+
client_secret: "my-client-secret",
|
|
88
|
+
cluster_id: "my-cluster-id", # usually a UUID
|
|
89
|
+
region: "my-cluster-region" # e.g., "bru-2"
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Cluster Address Resolution
|
|
94
|
+
|
|
95
|
+
When `cluster_address` is not explicitly provided, Busybee uses this precedence:
|
|
96
|
+
|
|
97
|
+
1. Explicit `cluster_address:` parameter (highest priority)
|
|
98
|
+
2. `Busybee.cluster_address` configuration value
|
|
99
|
+
3. `CLUSTER_ADDRESS` environment variable
|
|
100
|
+
4. Default: `"localhost:26500"` (lowest priority)
|
|
101
|
+
|
|
102
|
+
This allows you to set a default cluster address once and override it selectively when needed.
|
|
103
|
+
|
|
104
|
+
### Environment Variables
|
|
105
|
+
|
|
106
|
+
For convenience, many of the credential parameters may be read implicitly from the following env vars:
|
|
107
|
+
|
|
108
|
+
| Environment Variable | Purpose | Used By |
|
|
109
|
+
|---------------------|---------|---------|
|
|
110
|
+
| `CLUSTER_ADDRESS` | Zeebe cluster address (host:port) | All credential types |
|
|
111
|
+
| `BUSYBEE_CREDENTIAL_TYPE` | Credential type (insecure, tls, oauth, camunda_cloud) | Auto-detection |
|
|
112
|
+
| `ZEEBE_TOKEN_URL` | OAuth token endpoint | OAuth |
|
|
113
|
+
| `ZEEBE_AUDIENCE` | OAuth audience | OAuth |
|
|
114
|
+
| `ZEEBE_SCOPE` | OAuth scope (optional) | OAuth |
|
|
115
|
+
| `ZEEBE_CERTIFICATE_FILE` | Path to CA certificate | TLS, OAuth |
|
|
116
|
+
| `CAMUNDA_CLIENT_ID` | OAuth client ID | OAuth, Camunda Cloud |
|
|
117
|
+
| `CAMUNDA_CLIENT_SECRET` | OAuth client secret | OAuth, Camunda Cloud |
|
|
118
|
+
| `CAMUNDA_CLUSTER_ID` | Cluster UUID | Camunda Cloud |
|
|
119
|
+
| `CAMUNDA_CLUSTER_REGION` | Cluster region (e.g., "bru-2") | Camunda Cloud |
|
|
120
|
+
|
|
121
|
+
## Error Handling
|
|
122
|
+
|
|
123
|
+
Busybee wraps low-level GRPC errors in Ruby exceptions that are easier to work with. The goal is to let you rescue errors by type without needing to understand GRPC status codes, while still giving you access to the underlying details when you need them.
|
|
124
|
+
|
|
125
|
+
### Error Hierarchy
|
|
126
|
+
|
|
127
|
+
All Busybee errors inherit from `Busybee::Error`, so you can rescue broadly or narrowly:
|
|
128
|
+
|
|
129
|
+
| Error Class | When Raised |
|
|
130
|
+
|-------------|-------------|
|
|
131
|
+
| `Busybee::Error` | Base class for all Busybee errors. Never raised directly; exists for `rescue Busybee::Error`. |
|
|
132
|
+
| `Busybee::GRPC::Error` | Any GRPC operation failure (network issues, invalid requests, server errors). See below. |
|
|
133
|
+
| `Busybee::InvalidOAuthResponse` | OAuth token endpoint returned invalid JSON. |
|
|
134
|
+
| `Busybee::InvalidJobJson` | Job variables or headers contain malformed JSON. |
|
|
135
|
+
| `Busybee::JobAlreadyHandled` | Attempted to complete, fail, or throw error on a job that has already been handled. |
|
|
136
|
+
| `Busybee::OAuthTokenRefreshFailed` | HTTP error received from OAuth refresh token endpoint. |
|
|
137
|
+
| `Busybee::StreamAlreadyClosed` | Attempted to iterate a job stream that was already closed. |
|
|
138
|
+
|
|
139
|
+
### GRPC::Error Wrapper Class
|
|
140
|
+
|
|
141
|
+
`Busybee::GRPC::Error` is the error you're likely to encounter most often. It wraps the underlying `::GRPC::BadStatus` exception and provides convenient accessors for GRPC-specific information:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
begin
|
|
145
|
+
client.start_instance("missing-process", vars: { orderId: 123 })
|
|
146
|
+
rescue Busybee::GRPC::Error => e
|
|
147
|
+
e.message # => "GRPC call failed (NOT_FOUND: no process found with ID 'missing-process')"
|
|
148
|
+
e.grpc_status # => :not_found
|
|
149
|
+
e.grpc_code # => 5 (the numeric GRPC status code)
|
|
150
|
+
e.grpc_details # => "no process found with ID 'missing-process'"
|
|
151
|
+
e.cause # => #<GRPC::NotFound: ...> (the original GRPC exception)
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The original GRPC exception is preserved in `#cause` through Ruby's automatic exception chaining, so you can always dig into the raw error if needed.
|
|
156
|
+
|
|
157
|
+
### Automatic Retry
|
|
158
|
+
|
|
159
|
+
Busybee can automatically retry GRPC calls that fail due to transient errors. This is disabled by default to minimize surprise, because retries can mask problems during development.
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
Busybee.configure do |config|
|
|
163
|
+
config.grpc_retry_enabled = true # Enable retry (default: false)
|
|
164
|
+
config.grpc_retry_delay_ms = 500 # Delay between attempts (default: 500ms)
|
|
165
|
+
config.grpc_retry_errors = [ # Which errors trigger retry (default below)
|
|
166
|
+
GRPC::Unavailable,
|
|
167
|
+
GRPC::DeadlineExceeded,
|
|
168
|
+
GRPC::ResourceExhausted
|
|
169
|
+
]
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
When retry is enabled, Busybee makes up to 2 attempts before raising. A warning is logged on retry:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
[busybee] GRPC call failed, retrying in 500ms (error_class: GRPC::Unavailable)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If both attempts fail, the error message indicates this: `"GRPC call failed after retry"`.
|
|
180
|
+
|
|
181
|
+
## Working with Jobs
|
|
182
|
+
|
|
183
|
+
Jobs represent units of work that your application performs as part of a workflow. When a BPMN process instance reaches a service task, Zeebe creates a job that workers can claim and process. This section covers the conceptual model; see [Job Operations](#job-operations) in the API Reference for method details.
|
|
184
|
+
|
|
185
|
+
### Polling vs Streaming
|
|
186
|
+
|
|
187
|
+
Busybee provides two ways to receive jobs:
|
|
188
|
+
|
|
189
|
+
| Approach | Method | Model | Best For |
|
|
190
|
+
|----------|--------|-------|----------|
|
|
191
|
+
| Long-Polling | `with_each_job` | Request/Response | Batch processing, cron jobs, serverless functions |
|
|
192
|
+
| Streaming | `open_job_stream` | Push | Long-running workers, real-time processing |
|
|
193
|
+
|
|
194
|
+
**`with_each_job` (long-polling)** makes a request and waits. If jobs are available, they're returned immediately. If not, the request waits until jobs become available or the request timeout expires, then returns whatever it has. This is like checking your inbox: you see everything that's there at the moment you look, no matter when it arrived.
|
|
195
|
+
|
|
196
|
+
**`open_job_stream` (streaming)** opens a persistent connection and receives jobs as they become available. Jobs that existed before you opened the stream are NOT delivered. This is like subscribing to a magazine: you get new issues as they're published, but you don't get back issues.
|
|
197
|
+
|
|
198
|
+
This means if you start a streaming worker after jobs have already been created, those jobs won't be delivered to your stream. For workers that need to process both existing and new jobs, either:
|
|
199
|
+
- Use `with_each_job` in a polling loop, or
|
|
200
|
+
- Call `with_each_job` at startup to drain existing jobs, then switch to streaming
|
|
201
|
+
|
|
202
|
+
> The [Worker pattern framework](workers.md) makes it easy to select between these behaviors automatically via [worker modes](workers.md#worker-modes).
|
|
203
|
+
|
|
204
|
+
### The Job Object
|
|
205
|
+
|
|
206
|
+
When you receive jobs through `with_each_job` or `open_job_stream`, each job is wrapped in a `Busybee::Job` object. This is the primary way you'll interact with jobs.
|
|
207
|
+
|
|
208
|
+
**Attributes:**
|
|
209
|
+
|
|
210
|
+
| Method | Returns | Description |
|
|
211
|
+
|--------|---------|-------------|
|
|
212
|
+
| `key` | `Integer` | Unique job identifier |
|
|
213
|
+
| `type` | `String` | Job type (from BPMN task definition) |
|
|
214
|
+
| `process_instance_key` | `Integer` | The process instance this job belongs to |
|
|
215
|
+
| `bpmn_process_id` | `String` | The BPMN process ID |
|
|
216
|
+
| `retries` | `Integer` | Remaining retry attempts |
|
|
217
|
+
| `deadline` | `Time` | When the job lock expires (frozen Time object) |
|
|
218
|
+
| `variables` | `Hash` | Job input variables (with indifferent access) |
|
|
219
|
+
| `headers` | `Hash` | Custom headers from the BPMN task definition |
|
|
220
|
+
| `status` | `Symbol` | Current status: `:ready`, `:complete`, `:failed`, or `:error` |
|
|
221
|
+
|
|
222
|
+
**Variables: Reading and Writing**
|
|
223
|
+
|
|
224
|
+
When you **read** variables and headers from a job, they're returned as `ActiveSupport::HashWithIndifferentAccess`. You can access keys with strings or symbols, and they support method-style access with automatic camelCase conversion:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
client.with_each_job("process-order") do |job|
|
|
228
|
+
# All of these work:
|
|
229
|
+
job.variables[:orderId] # Symbol access
|
|
230
|
+
job.variables["orderId"] # String access
|
|
231
|
+
job.variables.orderId # Method access (camelCase)
|
|
232
|
+
job.variables.order_id # Method access (snake_case → camelCase)
|
|
233
|
+
|
|
234
|
+
# Nested hashes also support method access
|
|
235
|
+
job.variables.customer.email
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Variables and headers are frozen to prevent accidental mutation.
|
|
240
|
+
|
|
241
|
+
When you **write** variables (via `complete!`, `start_instance`, `publish_message`, etc.), Busybee calls `as_json` on your data before JSON-encoding it. This means objects with custom serialization—like ActiveRecord models—work sensibly:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
user = User.find(user_id)
|
|
245
|
+
order = Order.find(order_id)
|
|
246
|
+
|
|
247
|
+
# ActiveRecord models serialize via as_json, not inspect
|
|
248
|
+
job.complete!(user: user, order: order)
|
|
249
|
+
# => {"user": {"id": 1, "name": "Alice", ...}, "order": {"id": 42, ...}}
|
|
250
|
+
|
|
251
|
+
# Plain hashes, arrays, and primitives work as expected
|
|
252
|
+
job.complete!(items: ["a", "b"], count: 3, metadata: { source: "api" })
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
If you need custom serialization for your own classes, implement `as_json`.
|
|
256
|
+
|
|
257
|
+
**Actions:**
|
|
258
|
+
|
|
259
|
+
The Job object provides convenience methods that are the preferred way to complete, fail, or error jobs:
|
|
260
|
+
|
|
261
|
+
**`complete!(vars = {})`** — Complete the job with optional output variables.
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
job.complete!
|
|
265
|
+
job.complete!(result: "success", processedAt: Time.now.iso8601)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**`fail!(error_message_or_exception, retries: nil, backoff: nil)`** — Fail the job. You can pass a string or an exception:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
job.fail!("Payment gateway timeout")
|
|
272
|
+
job.fail!("Rate limited", retries: 3, backoff: 30.seconds)
|
|
273
|
+
|
|
274
|
+
# Pass an exception directly—Busybee formats it nicely
|
|
275
|
+
begin
|
|
276
|
+
risky_operation
|
|
277
|
+
rescue => e
|
|
278
|
+
job.fail!(e) # Message: "[ExceptionClass] message (caused by: ...)"
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**`throw_bpmn_error!(code_or_exception, message = "")`** — Throw a BPMN error. You can pass a string, symbol, or exception:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
job.throw_bpmn_error!("ORDER_NOT_FOUND", "Order #{order_id} not found")
|
|
286
|
+
job.throw_bpmn_error!(:order_not_found) # Symbol converted to "ORDER_NOT_FOUND"
|
|
287
|
+
|
|
288
|
+
# Pass an exception—class name becomes the error code
|
|
289
|
+
begin
|
|
290
|
+
order = Order.find!(order_id)
|
|
291
|
+
rescue OrderNotFoundError => e
|
|
292
|
+
job.throw_bpmn_error!(e) # Code: "ORDER_NOT_FOUND_ERROR"
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Status Tracking:**
|
|
297
|
+
|
|
298
|
+
The Job tracks its status to prevent double-handling bugs:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
job.ready? # => true (job can be completed/failed)
|
|
302
|
+
job.complete? # => true after calling complete!
|
|
303
|
+
job.failed? # => true after calling fail!
|
|
304
|
+
job.error? # => true after calling throw_bpmn_error!
|
|
305
|
+
|
|
306
|
+
# Attempting to handle a job twice raises an error
|
|
307
|
+
job.complete!
|
|
308
|
+
job.fail!("oops") # => raises Busybee::JobAlreadyHandled
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### JobStream
|
|
312
|
+
|
|
313
|
+
`Busybee::JobStream` wraps a gRPC server stream and provides a Ruby-idiomatic interface. It includes `Enumerable`, so you can use `each`, `map`, `select`, and other collection methods.
|
|
314
|
+
|
|
315
|
+
| Method | Description |
|
|
316
|
+
|--------|-------------|
|
|
317
|
+
| `each { \|job\| ... }` | Iterate over jobs (blocks until stream closes) |
|
|
318
|
+
| `close` | Close the stream (idempotent) |
|
|
319
|
+
| `closed?` | Check if the stream has been closed |
|
|
320
|
+
|
|
321
|
+
**Gotchas:**
|
|
322
|
+
|
|
323
|
+
1. **Blocking behavior:** Calling `stream.each` blocks the calling thread indefinitely. The iteration only ends when another thread calls `stream.close`, or when the server closes the connection. Plan for this—use signal handlers or a separate management thread.
|
|
324
|
+
|
|
325
|
+
2. **Single-pass:** Streams are single-pass. Once consumed via `each` or other Enumerable methods, the stream is exhausted. If you need to process the same jobs multiple times, collect them into an array first.
|
|
326
|
+
|
|
327
|
+
3. **No back issues:** As noted in [Polling vs Streaming](#polling-vs-streaming), streams only receive jobs created *after* the stream opens.
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
stream = client.open_job_stream("send-email")
|
|
331
|
+
|
|
332
|
+
# Stream blocks on each—close from another thread or signal handler
|
|
333
|
+
trap("INT") { stream.close }
|
|
334
|
+
|
|
335
|
+
stream.each do |job|
|
|
336
|
+
puts "Processing job #{job.key}"
|
|
337
|
+
job.complete!
|
|
338
|
+
end
|
|
339
|
+
# This line only runs after the stream is closed
|
|
340
|
+
puts "Stream closed, shutting down"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## API Reference
|
|
346
|
+
|
|
347
|
+
### Overview
|
|
348
|
+
|
|
349
|
+
| Category | Method | Description |
|
|
350
|
+
|----------|--------|-------------|
|
|
351
|
+
| [Process](#process-operations) | [`deploy_process`](#deploy_process) | Deploy BPMN files |
|
|
352
|
+
| | [`start_instance`](#start_instance) | Start a process instance |
|
|
353
|
+
| | [`cancel_instance`](#cancel_instance) | Cancel a running instance |
|
|
354
|
+
| [Job](#job-operations) | [`with_each_job`](#with_each_job) | Poll for jobs (bounded) |
|
|
355
|
+
| | [`open_job_stream`](#open_job_stream) | Stream jobs (push-based) |
|
|
356
|
+
| | [`complete_job`](#complete_job) | Complete a job |
|
|
357
|
+
| | [`fail_job`](#fail_job) | Fail a job with retry |
|
|
358
|
+
| | [`throw_bpmn_error`](#throw_bpmn_error) | Throw a BPMN error |
|
|
359
|
+
| | [`update_job_retries`](#update_job_retries) | Update job retry count |
|
|
360
|
+
| | [`update_job_timeout`](#update_job_timeout) | Extend job deadline |
|
|
361
|
+
| | [`resolve_incident`](#resolve_incident) | Resolve a failed-job incident |
|
|
362
|
+
| [Message](#message-operations) | [`publish_message`](#publish_message) | Publish a correlated message |
|
|
363
|
+
| | [`broadcast_signal`](#broadcast_signal) | Broadcast a signal to all listeners |
|
|
364
|
+
| [Variable](#variable-operations) | [`set_variables`](#set_variables) | Set variables on an instance |
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
### Process Operations
|
|
369
|
+
|
|
370
|
+
Process operations manage BPMN workflow deployments and instances.
|
|
371
|
+
|
|
372
|
+
#### deploy_process
|
|
373
|
+
|
|
374
|
+
Deploy one or more BPMN files to the Zeebe cluster.
|
|
375
|
+
|
|
376
|
+
```ruby
|
|
377
|
+
deploy_process(*paths, tenant_id: nil) → Hash{String => Integer}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
| Parameter | Type | Description |
|
|
381
|
+
|-----------|------|-------------|
|
|
382
|
+
| `*paths` | `String` | One or more paths to BPMN files |
|
|
383
|
+
| `tenant_id:` | `String`, `nil` | Tenant ID for multi-tenancy (optional) |
|
|
384
|
+
|
|
385
|
+
**Returns:** A hash mapping each BPMN process ID to its process definition key.
|
|
386
|
+
|
|
387
|
+
**Raises:** `Errno::ENOENT` if a file doesn't exist; `Busybee::GRPC::Error` if deployment fails (e.g., invalid BPMN syntax).
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
# Deploy a single workflow
|
|
391
|
+
result = client.deploy_process("workflows/order-fulfillment.bpmn")
|
|
392
|
+
# => { "order-fulfillment" => 2251799813685249 }
|
|
393
|
+
|
|
394
|
+
# Deploy multiple workflows at once
|
|
395
|
+
result = client.deploy_process("order.bpmn", "payment.bpmn", "shipping.bpmn")
|
|
396
|
+
# => { "order-fulfillment" => 123, "payment-process" => 456, "shipping-process" => 789 }
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
#### start_instance
|
|
400
|
+
|
|
401
|
+
Start a new process instance from a deployed workflow.
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
start_instance(bpmn_process_id, vars: {}, version: :latest, tenant_id: nil) → Integer
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
| Parameter | Type | Description |
|
|
408
|
+
|-----------|------|-------------|
|
|
409
|
+
| `bpmn_process_id` | `String` | The BPMN process ID (from the workflow definition) |
|
|
410
|
+
| `vars:` | `Hash` | Variables to initialize the process with (default: `{}`) |
|
|
411
|
+
| `version:` | `Integer`, `:latest`, `nil` | Process version to start (default: `:latest`) |
|
|
412
|
+
| `tenant_id:` | `String`, `nil` | Tenant ID for multi-tenancy (optional) |
|
|
413
|
+
|
|
414
|
+
**Returns:** The process instance key (an integer uniquely identifying this instance).
|
|
415
|
+
|
|
416
|
+
**Raises:** `ArgumentError` if `vars` is not a Hash; `Busybee::GRPC::Error` if the process doesn't exist or starting fails.
|
|
417
|
+
|
|
418
|
+
```ruby
|
|
419
|
+
# Start with variables
|
|
420
|
+
instance_key = client.start_instance("order-fulfillment", vars: {
|
|
421
|
+
orderId: "ORD-123",
|
|
422
|
+
customer: { name: "Alice", email: "alice@example.com" }
|
|
423
|
+
})
|
|
424
|
+
# => 2251799813685300
|
|
425
|
+
|
|
426
|
+
# Start a specific version
|
|
427
|
+
instance_key = client.start_instance("order-fulfillment", version: 3)
|
|
428
|
+
|
|
429
|
+
# Alias: start_process_instance
|
|
430
|
+
instance_key = client.start_process_instance("order-fulfillment", vars: { orderId: "ORD-456" })
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### cancel_instance
|
|
434
|
+
|
|
435
|
+
Cancel a running process instance.
|
|
436
|
+
|
|
437
|
+
```ruby
|
|
438
|
+
cancel_instance(process_instance_key, ignore_missing: false) → Boolean
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
| Parameter | Type | Description |
|
|
442
|
+
|-----------|------|-------------|
|
|
443
|
+
| `process_instance_key` | `Integer` | The process instance key to cancel |
|
|
444
|
+
| `ignore_missing:` | `Boolean` | Return `false` instead of raising if instance not found (default: `false`) |
|
|
445
|
+
|
|
446
|
+
**Returns:** `true` if cancelled, `false` if not found and `ignore_missing: true`.
|
|
447
|
+
|
|
448
|
+
**Raises:** `Busybee::GRPC::Error` if cancellation fails (unless instance not found and `ignore_missing: true`).
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
# Cancel an instance (raises if not found)
|
|
452
|
+
client.cancel_instance(2251799813685300)
|
|
453
|
+
# => true
|
|
454
|
+
|
|
455
|
+
# Cancel without raising if already completed/cancelled
|
|
456
|
+
cancelled = client.cancel_instance(2251799813685300, ignore_missing: true)
|
|
457
|
+
# => false (if instance was already gone)
|
|
458
|
+
|
|
459
|
+
# Alias: cancel_process_instance
|
|
460
|
+
client.cancel_process_instance(instance_key, ignore_missing: true)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### Job Operations
|
|
466
|
+
|
|
467
|
+
For conceptual background on jobs, the Job object, and JobStream, see [Working with Jobs](#working-with-jobs) above.
|
|
468
|
+
|
|
469
|
+
#### with_each_job
|
|
470
|
+
|
|
471
|
+
Poll for available jobs and process them with a block.
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
with_each_job(job_type, max_jobs: 25, job_timeout: 60_000, request_timeout: 60_000) { |job| ... } → Integer
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
| Parameter | Type | Description |
|
|
478
|
+
|-----------|------|-------------|
|
|
479
|
+
| `job_type` | `String` | The job type to activate (from the BPMN task definition) |
|
|
480
|
+
| `max_jobs:` | `Integer` | Maximum jobs to return (default: 25) |
|
|
481
|
+
| `job_timeout:` | `Integer`, `Duration` | How long a job stays locked to this worker (default: 60s) |
|
|
482
|
+
| `request_timeout:` | `Integer`, `Duration` | How long to wait for jobs before returning (default: 60s) |
|
|
483
|
+
|
|
484
|
+
**Yields:** Each activated job as a `Busybee::Job` object.
|
|
485
|
+
|
|
486
|
+
**Returns:** The number of jobs processed.
|
|
487
|
+
|
|
488
|
+
**Raises:** `ArgumentError` if no block is given; `Busybee::GRPC::Error` if activation fails.
|
|
489
|
+
|
|
490
|
+
**Long-polling behavior:** If no jobs are immediately available, the request waits up to `request_timeout` for jobs to become available. When jobs arrive (or the timeout expires), the method returns with whatever jobs it collected.
|
|
491
|
+
|
|
492
|
+
**Configuration:** The worker name sent to Zeebe comes from `Busybee.worker_name`, which defaults to the machine hostname. You can override it:
|
|
493
|
+
|
|
494
|
+
```ruby
|
|
495
|
+
Busybee.configure do |config|
|
|
496
|
+
config.worker_name = "order-processor-1"
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
```ruby
|
|
501
|
+
# Process jobs in a loop
|
|
502
|
+
loop do
|
|
503
|
+
count = client.with_each_job("send-email", max_jobs: 10) do |job|
|
|
504
|
+
EmailService.deliver(to: job.variables.email, subject: job.variables.subject)
|
|
505
|
+
job.complete!(sentAt: Time.now.iso8601)
|
|
506
|
+
rescue EmailService::DeliveryError => e
|
|
507
|
+
job.fail!(e, backoff: 1.minute)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
puts "Processed #{count} jobs"
|
|
511
|
+
end
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
#### open_job_stream
|
|
515
|
+
|
|
516
|
+
Open a long-lived stream for job activation. Jobs are pushed to your code as they become available.
|
|
517
|
+
|
|
518
|
+
```ruby
|
|
519
|
+
open_job_stream(job_type, job_timeout: 60_000) → Busybee::JobStream
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
| Parameter | Type | Description |
|
|
523
|
+
|-----------|------|-------------|
|
|
524
|
+
| `job_type` | `String` | The job type to activate |
|
|
525
|
+
| `job_timeout:` | `Integer`, `Duration` | How long a job stays locked (default: 60s) |
|
|
526
|
+
|
|
527
|
+
**Returns:** A `Busybee::JobStream` object.
|
|
528
|
+
|
|
529
|
+
**Raises:** `Busybee::GRPC::Error` if stream creation fails.
|
|
530
|
+
|
|
531
|
+
**Remember:** Streams only receive jobs created *after* the stream opens. See [Polling vs Streaming](#polling-vs-streaming).
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
stream = client.open_job_stream("send-email", job_timeout: 2.minutes)
|
|
535
|
+
|
|
536
|
+
# Handle graceful shutdown
|
|
537
|
+
trap("INT") { stream.close }
|
|
538
|
+
trap("TERM") { stream.close }
|
|
539
|
+
|
|
540
|
+
stream.each do |job|
|
|
541
|
+
process_email(job)
|
|
542
|
+
job.complete!
|
|
543
|
+
rescue StandardError => e
|
|
544
|
+
job.fail!(e)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
puts "Stream closed, shutting down"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### complete_job
|
|
551
|
+
|
|
552
|
+
Mark a job as successfully completed, optionally returning variables to the workflow.
|
|
553
|
+
|
|
554
|
+
```ruby
|
|
555
|
+
complete_job(job_key, vars: {}) → Object
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
| Parameter | Type | Description |
|
|
559
|
+
|-----------|------|-------------|
|
|
560
|
+
| `job_key` | `Integer` | The job key |
|
|
561
|
+
| `vars:` | `Hash` | Variables to return to the workflow (default: `{}`) |
|
|
562
|
+
|
|
563
|
+
**Returns:** A truthy response from the gateway.
|
|
564
|
+
|
|
565
|
+
**Raises:** `Busybee::GRPC::Error` if completion fails.
|
|
566
|
+
|
|
567
|
+
**Prefer `Job#complete!`** when processing jobs through `with_each_job` or `open_job_stream`.
|
|
568
|
+
|
|
569
|
+
```ruby
|
|
570
|
+
# Direct client call (when you only have the job key)
|
|
571
|
+
client.complete_job(job_key, vars: { result: "success" })
|
|
572
|
+
|
|
573
|
+
# Preferred: use the Job object
|
|
574
|
+
job.complete!(result: "success")
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### fail_job
|
|
578
|
+
|
|
579
|
+
Mark a job as failed. The workflow engine will retry the job (if retries remain) after the backoff period.
|
|
580
|
+
|
|
581
|
+
```ruby
|
|
582
|
+
fail_job(job_key, error_message, retries: nil, backoff: nil) → Object
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
| Parameter | Type | Description |
|
|
586
|
+
|-----------|------|-------------|
|
|
587
|
+
| `job_key` | `Integer` | The job key |
|
|
588
|
+
| `error_message` | `String` | Error message describing the failure |
|
|
589
|
+
| `retries:` | `Integer`, `nil` | Override the remaining retry count (default: decrement by 1) |
|
|
590
|
+
| `backoff:` | `Integer`, `Duration`, `nil` | Delay before retry (see below) |
|
|
591
|
+
|
|
592
|
+
**Returns:** A truthy response from the gateway.
|
|
593
|
+
|
|
594
|
+
**Raises:** `Busybee::GRPC::Error` if the fail operation fails.
|
|
595
|
+
|
|
596
|
+
**Default backoff:** When `backoff:` is omitted, Busybee uses `Busybee.default_fail_job_backoff` (default: 5 seconds). You can change this globally:
|
|
597
|
+
|
|
598
|
+
```ruby
|
|
599
|
+
Busybee.configure do |config|
|
|
600
|
+
config.default_fail_job_backoff = 30_000 # 30 seconds
|
|
601
|
+
end
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Prefer `Job#fail!`** when processing jobs through `with_each_job` or `open_job_stream`.
|
|
605
|
+
|
|
606
|
+
```ruby
|
|
607
|
+
# Direct client call
|
|
608
|
+
client.fail_job(job_key, "Payment gateway timeout", backoff: 30.seconds)
|
|
609
|
+
|
|
610
|
+
# Preferred: use the Job object
|
|
611
|
+
job.fail!("Payment gateway timeout", backoff: 30.seconds)
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
#### throw_bpmn_error
|
|
615
|
+
|
|
616
|
+
Throw a BPMN error that can be caught by an error boundary event in the workflow. Use this for business-level errors that the workflow is designed to handle (as opposed to technical failures, which should use `fail_job`).
|
|
617
|
+
|
|
618
|
+
```ruby
|
|
619
|
+
throw_bpmn_error(job_key, error_code, message: "") → Object
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
| Parameter | Type | Description |
|
|
623
|
+
|-----------|------|-------------|
|
|
624
|
+
| `job_key` | `Integer` | The job key |
|
|
625
|
+
| `error_code` | `String` | BPMN error code (must match the error catch event) |
|
|
626
|
+
| `message:` | `String` | Optional error message for context (default: `""`) |
|
|
627
|
+
|
|
628
|
+
**Returns:** A truthy response from the gateway.
|
|
629
|
+
|
|
630
|
+
**Raises:** `Busybee::GRPC::Error` if the operation fails.
|
|
631
|
+
|
|
632
|
+
**Prefer `Job#throw_bpmn_error!`** when processing jobs through `with_each_job` or `open_job_stream`.
|
|
633
|
+
|
|
634
|
+
```ruby
|
|
635
|
+
# Direct client call
|
|
636
|
+
client.throw_bpmn_error(job_key, "ORDER_NOT_FOUND", message: "Order ORD-123 does not exist")
|
|
637
|
+
|
|
638
|
+
# Preferred: use the Job object (also accepts symbols and exceptions)
|
|
639
|
+
job.throw_bpmn_error!(:order_not_found, "Order ORD-123 does not exist")
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### update_job_retries
|
|
643
|
+
|
|
644
|
+
Update the retry count for a job. Useful for giving a job more attempts after fixing an underlying issue.
|
|
645
|
+
|
|
646
|
+
```ruby
|
|
647
|
+
update_job_retries(job_key, retries) → Object
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
| Parameter | Type | Description |
|
|
651
|
+
|-----------|------|-------------|
|
|
652
|
+
| `job_key` | `Integer` | The job key |
|
|
653
|
+
| `retries` | `Integer` | The new retry count |
|
|
654
|
+
|
|
655
|
+
**Returns:** A truthy response from the gateway.
|
|
656
|
+
|
|
657
|
+
**Raises:** `Busybee::GRPC::Error` if the update fails.
|
|
658
|
+
|
|
659
|
+
```ruby
|
|
660
|
+
client.update_job_retries(job_key, 5)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
#### update_job_timeout
|
|
664
|
+
|
|
665
|
+
Extend the deadline for a job that's taking longer than expected. Call this before the current deadline expires to prevent the job from timing out and being reassigned to another worker.
|
|
666
|
+
|
|
667
|
+
```ruby
|
|
668
|
+
update_job_timeout(job_key, timeout) → Object
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
| Parameter | Type | Description |
|
|
672
|
+
|-----------|------|-------------|
|
|
673
|
+
| `job_key` | `Integer` | The job key |
|
|
674
|
+
| `timeout` | `Integer`, `Duration` | New timeout in milliseconds or as a Duration |
|
|
675
|
+
|
|
676
|
+
**Returns:** A truthy response from the gateway.
|
|
677
|
+
|
|
678
|
+
**Raises:** `Busybee::GRPC::Error` if the update fails.
|
|
679
|
+
|
|
680
|
+
```ruby
|
|
681
|
+
client.update_job_timeout(job_key, 30.seconds)
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### resolve_incident
|
|
685
|
+
|
|
686
|
+
Resolve an incident so the workflow can continue. Incidents occur when a job fails with no retries remaining, or when an expression evaluation fails.
|
|
687
|
+
|
|
688
|
+
```ruby
|
|
689
|
+
resolve_incident(incident_key) → true
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
| Parameter | Type | Description |
|
|
693
|
+
|-----------|------|-------------|
|
|
694
|
+
| `incident_key` | `Integer` | The incident key to resolve |
|
|
695
|
+
|
|
696
|
+
**Returns:** `true` if resolved.
|
|
697
|
+
|
|
698
|
+
**Raises:** `Busybee::GRPC::Error` if the incident doesn't exist or resolution fails.
|
|
699
|
+
|
|
700
|
+
Before resolving, you typically need to fix the underlying problem (e.g., set missing variables, fix external services, or update job retries):
|
|
701
|
+
|
|
702
|
+
```ruby
|
|
703
|
+
# Fix the problem first
|
|
704
|
+
client.set_variables(process_instance_key, vars: { missingField: "now provided" })
|
|
705
|
+
|
|
706
|
+
# Or give the job more retries
|
|
707
|
+
client.update_job_retries(job_key, 3)
|
|
708
|
+
|
|
709
|
+
# Then resolve the incident
|
|
710
|
+
client.resolve_incident(incident_key)
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
### Message Operations
|
|
716
|
+
|
|
717
|
+
Messages enable communication with waiting process instances. A message correlates to instances by matching a correlation key against process variables.
|
|
718
|
+
|
|
719
|
+
#### publish_message
|
|
720
|
+
|
|
721
|
+
Publish a message that correlates to waiting process instances.
|
|
722
|
+
|
|
723
|
+
```ruby
|
|
724
|
+
publish_message(name, correlation_key:, vars: {}, ttl: nil, tenant_id: nil) → Integer
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
| Parameter | Type | Description |
|
|
728
|
+
|-----------|------|-------------|
|
|
729
|
+
| `name` | `String` | The message name (must match the message catch event in BPMN) |
|
|
730
|
+
| `correlation_key:` | `String` | Key to match against process instance variables |
|
|
731
|
+
| `vars:` | `Hash` | Variables to pass with the message (default: `{}`) |
|
|
732
|
+
| `ttl:` | `Integer`, `ActiveSupport::Duration`, `nil` | Time-to-live (see below) |
|
|
733
|
+
| `tenant_id:` | `String`, `nil` | Tenant ID for multi-tenancy (optional) |
|
|
734
|
+
|
|
735
|
+
**Returns:** The message key.
|
|
736
|
+
|
|
737
|
+
**Raises:** `ArgumentError` if `vars` is not a Hash; `Busybee::GRPC::Error` if publishing fails.
|
|
738
|
+
|
|
739
|
+
**Default TTL:** When `ttl:` is omitted, Busybee uses `Busybee.default_message_ttl` (default: 10 seconds). You can change this globally:
|
|
740
|
+
|
|
741
|
+
```ruby
|
|
742
|
+
Busybee.configure do |config|
|
|
743
|
+
config.default_message_ttl = 60_000 # 1 minute, in milliseconds
|
|
744
|
+
end
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
The TTL determines how long the message remains buffered if no matching instance is found. If an instance starts waiting before the TTL expires, the message is delivered. After the TTL expires, the message is discarded.
|
|
748
|
+
|
|
749
|
+
```ruby
|
|
750
|
+
# Publish with default TTL
|
|
751
|
+
client.publish_message("order-confirmed", correlation_key: "ORD-123")
|
|
752
|
+
|
|
753
|
+
# Publish with variables and custom TTL using ActiveSupport::Duration
|
|
754
|
+
client.publish_message("payment-received",
|
|
755
|
+
correlation_key: order.id.to_s,
|
|
756
|
+
vars: { paymentId: payment.id, amount: payment.amount },
|
|
757
|
+
ttl: 5.minutes
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
# Publish with TTL in milliseconds
|
|
761
|
+
client.publish_message("order-shipped", correlation_key: "ORD-123", ttl: 60_000)
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
#### broadcast_signal
|
|
765
|
+
|
|
766
|
+
Broadcast a signal to all process instances with matching signal catch events. Unlike messages, signals don't use correlation—they're delivered to all waiting instances.
|
|
767
|
+
|
|
768
|
+
```ruby
|
|
769
|
+
broadcast_signal(signal_name, vars: {}, tenant_id: nil) → Integer
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
| Parameter | Type | Description |
|
|
773
|
+
|-----------|------|-------------|
|
|
774
|
+
| `signal_name` | `String` | The signal name (must match signal catch events in BPMN) |
|
|
775
|
+
| `vars:` | `Hash` | Variables to pass with the signal (default: `{}`) |
|
|
776
|
+
| `tenant_id:` | `String`, `nil` | Tenant ID for multi-tenancy (optional) |
|
|
777
|
+
|
|
778
|
+
**Returns:** The signal key.
|
|
779
|
+
|
|
780
|
+
**Raises:** `ArgumentError` if `vars` is not a Hash; `Busybee::GRPC::Error` if broadcasting fails.
|
|
781
|
+
|
|
782
|
+
```ruby
|
|
783
|
+
# Broadcast a simple signal
|
|
784
|
+
client.broadcast_signal("system-shutdown")
|
|
785
|
+
|
|
786
|
+
# Broadcast with variables
|
|
787
|
+
client.broadcast_signal("price-updated", vars: {
|
|
788
|
+
productId: "PROD-789",
|
|
789
|
+
newPrice: 29.99
|
|
790
|
+
})
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
### Variable Operations
|
|
796
|
+
|
|
797
|
+
Variable operations let you modify process instance state from outside the workflow.
|
|
798
|
+
|
|
799
|
+
#### set_variables
|
|
800
|
+
|
|
801
|
+
Set variables on a process instance or element (e.g., a service task).
|
|
802
|
+
|
|
803
|
+
```ruby
|
|
804
|
+
set_variables(element_instance_key, vars: {}, local: false) → Integer
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
| Parameter | Type | Description |
|
|
808
|
+
|-----------|------|-------------|
|
|
809
|
+
| `element_instance_key` | `Integer` | The process instance key or element instance key |
|
|
810
|
+
| `vars:` | `Hash` | Variables to set (default: `{}`) |
|
|
811
|
+
| `local:` | `Boolean` | If `true`, variables are scoped locally (default: `false`) |
|
|
812
|
+
|
|
813
|
+
**Returns:** The set variables operation key.
|
|
814
|
+
|
|
815
|
+
**Raises:** `ArgumentError` if `vars` is not a Hash; `Busybee::GRPC::Error` if setting variables fails.
|
|
816
|
+
|
|
817
|
+
When `local: false` (the default), variables propagate up to the process instance scope and are visible everywhere. When `local: true`, variables are scoped to the specific element and won't be visible to parent or sibling elements.
|
|
818
|
+
|
|
819
|
+
```ruby
|
|
820
|
+
# Set variables on a process instance (propagates globally)
|
|
821
|
+
client.set_variables(process_instance_key, vars: { status: "approved", approvedBy: "manager@example.com" })
|
|
822
|
+
|
|
823
|
+
# Set local variables on a specific element (won't propagate)
|
|
824
|
+
client.set_variables(element_instance_key, vars: { tempCalculation: 42 }, local: true)
|
|
825
|
+
```
|