Skip to content

Commit 656e254

Browse files
committed
Sample code for Nexus Standalone
1 parent 5a897aa commit 656e254

9 files changed

Lines changed: 609 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
## Standalone Nexus Operations
2+
3+
This sample shows how to invoke and manage **standalone Nexus operations** — Nexus operations
4+
started directly by a client rather than from within a caller workflow. The long-running operation
5+
(`startGreeting`) is backed by a `GreetingWorkflow` that blocks until it is cancelled or terminated;
6+
the quick operation (`greet`) is synchronous and completes immediately.
7+
8+
`StandaloneClientStarter` runs each capability in turn:
9+
1. **Execute** an operation and read its result — synchronously (`execute`) and asynchronously
10+
(`executeAsync`).
11+
2. **Cancel** a running operation (`handle.cancel`).
12+
3. **Terminate** a running operation (`handle.terminate`). Operation-terminate is a known gap that
13+
does not stop the backing workflow, so the sample also terminates the backing workflow by ID.
14+
4. **Visibility**`list` operations with a status filter and `count` them (total and grouped) via
15+
`NexusClient`.
16+
5. **Client options and interceptors** — set the identity and data converter, and register two
17+
logging interceptors.
18+
19+
> [!WARNING]
20+
> Standalone Nexus operations are experimental and may be subject to backwards-incompatible
21+
> changes. They require a Temporal server that implements and enables them via the dynamic configs
22+
> shown below.
23+
24+
### Running
25+
26+
Start a Temporal server with the standalone-Nexus dynamic configs enabled:
27+
28+
```bash
29+
temporal server start-dev \
30+
--dynamic-config-value nexusoperation.enableStandalone=true \
31+
--dynamic-config-value history.enableChasmCallbacks=true
32+
```
33+
34+
Create the namespace and the Nexus endpoint:
35+
36+
```bash
37+
temporal operator namespace create --namespace default
38+
39+
temporal operator nexus endpoint create \
40+
--name nexusstandalone-endpoint \
41+
--target-namespace default \
42+
--target-task-queue nexusstandalone-handler-task-queue
43+
```
44+
45+
Both the handler worker and the starter connect using the `default` profile in
46+
`core/src/main/resources/config.toml` (address `localhost:7233`, namespace `default`). Edit that
47+
profile, or override it with `TEMPORAL_*` environment variables, to point at a different server or a
48+
Temporal Cloud namespace.
49+
50+
In one terminal, start the handler worker:
51+
52+
```bash
53+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.handler.HandlerWorker
54+
```
55+
56+
In a second terminal, run the starter:
57+
58+
```bash
59+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.StandaloneClientStarter
60+
```
61+
62+
Expected output (operation IDs and Visibility counts will differ between runs):
63+
64+
```
65+
execute() returned: Hello, execute!
66+
executeAsync() returned: Hello, executeAsync!
67+
Started 'to-cancel' id=<uuid>, requesting cancellation
68+
Operation id=<uuid> final status: NEXUS_OPERATION_EXECUTION_STATUS_CANCELED
69+
Started 'to-terminate' id=<uuid>, terminating
70+
Final status of 'to-terminate': NEXUS_OPERATION_EXECUTION_STATUS_TERMINATED
71+
Terminated backing workflow greeting-to-terminate-<runId>
72+
List filtered to Completed returned 2 operation(s)
73+
Total operation count: 4
74+
Grouped count total=4, groups:
75+
group values=[[Canceled]] count=1
76+
group values=[[Completed]] count=2
77+
group values=[[Terminated]] count=1
78+
[interceptor second] -> startNexusOperationExecution
79+
[interceptor first] -> startNexusOperationExecution
80+
[interceptor first] <- startNexusOperationExecution
81+
[interceptor second] <- startNexusOperationExecution
82+
Result through interceptor chain: Hello, interceptors!
83+
```
84+
85+
The four interceptor lines come from a single operation: `execute()` issues one
86+
`startNexusOperationExecution` call that passes through both interceptors. The last-registered
87+
interceptor is outermost, so the call flows in `second → first → root` and back out `first → second`,
88+
and each interceptor logs once on the way in and once on the way out.
89+
90+
### Cancellation vs. termination
91+
92+
A workflow-backed Nexus operation does **not** need any explicit cancel handling to be cancellable.
93+
When you call `handle.cancel(...)`, the server delivers a cancellation request to the backing
94+
workflow, which makes the blocking call (`Workflow.await` in `GreetingWorkflowImpl`) throw a
95+
`CanceledFailure`; letting it propagate out of the workflow method ends both the workflow and the
96+
operation as cancelled. Cancellation is **cooperative**, though: if the backing workflow caught and
97+
ignored `CanceledFailure` (or did all of its waiting inside a detached cancellation scope), the
98+
cancel request would have no effect and the operation would run until it completes or hits its
99+
schedule-to-close timeout.
100+
101+
`handle.terminate(...)` is different. It forcefully closes the **operation** record, but currently
102+
does **not** propagate to the backing workflow (a known gap) — the workflow keeps running and
103+
nothing appears in its history. Until that gap is closed, terminate the backing workflow directly by
104+
its workflow ID, as `StandaloneClientStarter.terminateBackingWorkflow` does.

0 commit comments

Comments
 (0)