Skip to content

Commit 12ae22f

Browse files
committed
Updating
1 parent 87c691b commit 12ae22f

15 files changed

+549
-68
lines changed
Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
This sample shows how to create a Nexus service that is backed by a long-running workflow and
2-
exposes operations that execute updates and queries against that workflow. The long-running
3-
workflow, and the updates/queries, are private implementation details of the Nexus service: the
4-
caller does not know how the operations are implemented.
1+
This sample shows how to expose a long-running workflow's queries, updates, and signals as Nexus
2+
operations. The caller interacts only with the Nexus service; the workflow is a private
3+
implementation detail.
54

6-
This is a Java port of the
7-
[nexus_sync_operations Python sample](https://github.com/temporalio/samples-python/tree/main/nexus_sync_operations).
5+
There are **two caller patterns** that share the same handler workflow (`GreetingWorkflow`):
86

9-
### Sample directory structure
7+
| | `caller/` (entity pattern) | `caller_remote/` (remote-start pattern) |
8+
|---|---|---|
9+
| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a `runFromRemote` Nexus operation |
10+
| **Who knows the workflow ID?** | Only the handler | The caller chooses and passes it in every operation |
11+
| **Nexus service** | `NexusGreetingService` — inputs carry only business data | `NexusRemoteGreetingService` — every input includes a `workflowId` |
12+
| **When to use** | Single shared entity; callers don't need lifecycle control | Caller needs to create and target specific workflow instances |
1013

11-
- [`service/GreetingService.java`](./service/GreetingService.java) — shared Nexus service definition with input/output types
12-
- [`service/Language.java`](./service/Language.java) — shared language enum
13-
- [`handler/`](./handler/) — Nexus operation handlers, the long-running entity workflow they back, an activity, and a handler worker
14-
- [`caller/`](./caller/) — a caller workflow that executes Nexus operations, together with a worker and starter
14+
### Directory structure
1515

16-
### Instructions
16+
- `service/` — shared Nexus service definitions (`NexusGreetingService`, `NexusRemoteGreetingService`) and `Language` enum
17+
- `handler/``GreetingWorkflow` and its implementation, `GreetingActivity`, both Nexus service impls (`NexusGreetingServiceImpl`, `NexusRemoteGreetingServiceImpl`), and the handler worker
18+
- `caller/` — entity-pattern caller (workflow, worker, starter)
19+
- `caller_remote/` — remote-start caller (workflow, worker, starter)
1720

18-
Start a Temporal server:
21+
### Running
22+
23+
Start a Temporal server and create namespaces/endpoint:
1924

2025
```bash
2126
temporal server start-dev
22-
```
2327

24-
Create the caller and handler namespaces and the Nexus endpoint:
25-
26-
```bash
2728
temporal operator namespace create --namespace nexus-sync-operations-handler-namespace
2829
temporal operator namespace create --namespace nexus-sync-operations-caller-namespace
2930

@@ -33,13 +34,14 @@ temporal operator nexus endpoint create \
3334
--target-task-queue nexus-sync-operations-handler-task-queue
3435
```
3536

36-
In one terminal, run the handler worker (starts the long-running entity workflow and polls for
37-
Nexus, workflow, and activity tasks):
37+
In one terminal, start the handler worker (shared by both patterns):
3838

3939
```bash
4040
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.handler.HandlerWorker
4141
```
4242

43+
#### Entity pattern
44+
4345
In a second terminal, run the caller worker:
4446

4547
```bash
@@ -52,25 +54,82 @@ In a third terminal, start the caller workflow:
5254
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerStarter
5355
```
5456

55-
You should see output like:
57+
Expected output:
5658

5759
```
5860
supported languages: [CHINESE, ENGLISH]
5961
language changed: ENGLISH -> ARABIC
62+
workflow approved
63+
```
64+
65+
#### Remote-start pattern
66+
67+
In a second terminal, run the remote caller worker:
68+
69+
```bash
70+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteWorker
71+
```
72+
73+
In a third terminal, start the remote caller workflow:
74+
75+
```bash
76+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteStarter
77+
```
78+
79+
Expected output:
80+
81+
```
82+
started remote greeting workflow: nexus-sync-operations-remote-greeting-workflow
83+
supported languages: [CHINESE, ENGLISH]
84+
language changed: ENGLISH -> ARABIC
85+
workflow approved
86+
workflow result: مرحبا بالعالم
6087
```
6188

6289
### How it works
6390

64-
The handler starts a single long-running `GreetingWorkflow` entity workflow when the worker boots.
65-
This workflow holds the current language and a map of greetings, and exposes:
91+
#### The handler (shared by both patterns)
92+
93+
`GreetingWorkflow` is a long-running "entity" workflow that holds the current language and a map of
94+
greetings. It exposes its state through standard Temporal primitives:
95+
96+
- `getLanguages` / `getLanguage``@QueryMethod`s for reading state
97+
- `setLanguage` — an `@UpdateMethod` for switching between already-loaded languages
98+
- `setLanguageUsingActivity` — an `@UpdateMethod` that calls an activity to fetch a greeting for
99+
a language not yet in the map (uses `WorkflowLock` to serialize concurrent activity calls)
100+
- `approve` — a `@SignalMethod` that lets the workflow complete
101+
102+
The workflow waits until approved and all in-flight update handlers have finished, then returns the
103+
greeting in the current language.
104+
105+
Both Nexus service implementations translate incoming Nexus operations into calls against
106+
`GreetingWorkflow` stubs — queries, updates, and signals. The caller never interacts with the
107+
workflow directly.
108+
109+
#### Entity pattern (`caller/` + `NexusGreetingService`)
110+
111+
The handler worker starts a single `GreetingWorkflow` on boot with a fixed workflow ID.
112+
`NexusGreetingServiceImpl` holds that workflow ID in its constructor and routes every operation to
113+
it. The caller's inputs contain only business data (language, name), not workflow IDs.
114+
115+
`CallerWorkflowImpl` creates a `NexusGreetingService` stub and:
116+
1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`)
117+
2. Changes the language to Arabic (`setLanguage` — backed by an `@UpdateMethod` that calls an activity)
118+
3. Confirms the change via a second query (`getLanguage`)
119+
4. Approves the workflow (`approve` — backed by a `@SignalMethod`)
120+
121+
#### Remote-start pattern (`caller_remote/` + `NexusRemoteGreetingService`)
122+
123+
No workflow is pre-started. Instead, `NexusRemoteGreetingService` adds a `runFromRemote` operation
124+
that starts a new `GreetingWorkflow` with a caller-chosen workflow ID using
125+
`WorkflowRunOperation`. Every other operation also includes the `workflowId` in its input so that
126+
`NexusRemoteGreetingServiceImpl` can look up the right workflow stub.
66127

67-
- `getLanguages` — a `@QueryMethod` listing supported languages
68-
- `getLanguage` — a `@QueryMethod` returning the current language
69-
- `setLanguage` — an `@UpdateMethod` (sync) for switching between already-loaded languages
70-
- `setLanguageUsingActivity` — an `@UpdateMethod` (async) that calls an activity to fetch a
71-
greeting for a new language before switching
72-
- `approve` — a `@SignalMethod` that allows the workflow to complete
128+
`CallerRemoteWorkflowImpl` creates a `NexusRemoteGreetingService` stub and:
129+
1. Starts a remote `GreetingWorkflow` via `runFromRemote` and waits for it to be running
130+
2. Queries, updates, and approves that workflow — same operations as the entity pattern, but each
131+
input carries the workflow ID
132+
3. Waits for the remote workflow to complete and returns its result (the greeting string)
73133

74-
The three `GreetingService` Nexus operations delegate to these workflow handlers via the Temporal
75-
client inside each `OperationHandler.sync` implementation. The caller workflow sees only the Nexus
76-
operations; the entity workflow is a private implementation detail.
134+
This pattern is useful when the caller needs to control the lifecycle of individual workflow
135+
instances rather than sharing a single entity.

core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static void main(String[] args) throws InterruptedException {
3030
WorkflowImplementationOptions.newBuilder()
3131
.setNexusServiceOptions(
3232
Collections.singletonMap(
33-
"GreetingService",
33+
"NexusGreetingService",
3434
NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build()))
3535
.build(),
3636
CallerWorkflowImpl.class);

core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.temporal.samples.nexus_sync_operations.caller;
22

33
import io.temporal.failure.ApplicationFailure;
4-
import io.temporal.samples.nexus_sync_operations.service.GreetingService;
54
import io.temporal.samples.nexus_sync_operations.service.Language;
5+
import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService;
66
import io.temporal.workflow.NexusOperationOptions;
77
import io.temporal.workflow.NexusServiceOptions;
88
import io.temporal.workflow.Workflow;
@@ -14,9 +14,9 @@ public class CallerWorkflowImpl implements CallerWorkflow {
1414

1515
// The endpoint is configured at the worker level in CallerWorker; only operation options are
1616
// set here.
17-
GreetingService greetingService =
17+
NexusGreetingService greetingService =
1818
Workflow.newNexusServiceStub(
19-
GreetingService.class,
19+
NexusGreetingService.class,
2020
NexusServiceOptions.newBuilder()
2121
.setOperationOptions(
2222
NexusOperationOptions.newBuilder()
@@ -29,16 +29,17 @@ public List<String> run() {
2929
List<String> log = new ArrayList<>();
3030

3131
// 👉 Call a Nexus operation backed by a query against the entity workflow.
32-
GreetingService.GetLanguagesOutput languagesOutput =
33-
greetingService.getLanguages(new GreetingService.GetLanguagesInput(false));
32+
NexusGreetingService.GetLanguagesOutput languagesOutput =
33+
greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false));
3434
log.add("supported languages: " + languagesOutput.getLanguages());
3535

3636
// 👉 Call a Nexus operation backed by an update against the entity workflow.
3737
Language previousLanguage =
38-
greetingService.setLanguage(new GreetingService.SetLanguageInput(Language.ARABIC));
38+
greetingService.setLanguage(new NexusGreetingService.SetLanguageInput(Language.ARABIC));
3939

4040
// 👉 Call a Nexus operation backed by a query to confirm the language change.
41-
Language currentLanguage = greetingService.getLanguage(new GreetingService.GetLanguageInput());
41+
Language currentLanguage =
42+
greetingService.getLanguage(new NexusGreetingService.GetLanguageInput());
4243
if (currentLanguage != Language.ARABIC) {
4344
throw ApplicationFailure.newFailure(
4445
"expected language ARABIC, got " + currentLanguage, "AssertionError");
@@ -47,7 +48,7 @@ public List<String> run() {
4748
log.add("language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name());
4849

4950
// 👉 Call a Nexus operation backed by a signal against the entity workflow.
50-
greetingService.approve(new GreetingService.ApproveInput("caller"));
51+
greetingService.approve(new NexusGreetingService.ApproveInput("caller"));
5152
log.add("workflow approved");
5253

5354
return log;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.temporal.samples.nexus_sync_operations.caller_remote;
2+
3+
import io.temporal.client.WorkflowClient;
4+
import io.temporal.client.WorkflowClientOptions;
5+
import io.temporal.client.WorkflowOptions;
6+
import io.temporal.serviceclient.WorkflowServiceStubs;
7+
import java.util.List;
8+
import java.util.UUID;
9+
10+
public class CallerRemoteStarter {
11+
12+
public static void main(String[] args) {
13+
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
14+
WorkflowClient client =
15+
WorkflowClient.newInstance(
16+
service,
17+
WorkflowClientOptions.newBuilder().setNamespace(CallerRemoteWorker.NAMESPACE).build());
18+
19+
CallerRemoteWorkflow workflow =
20+
client.newWorkflowStub(
21+
CallerRemoteWorkflow.class,
22+
WorkflowOptions.newBuilder()
23+
.setWorkflowId("nexus-sync-operations-remote-caller-" + UUID.randomUUID())
24+
.setTaskQueue(CallerRemoteWorker.TASK_QUEUE)
25+
.build());
26+
27+
List<String> log = workflow.run();
28+
log.forEach(System.out::println);
29+
}
30+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.temporal.samples.nexus_sync_operations.caller_remote;
2+
3+
import io.temporal.client.WorkflowClient;
4+
import io.temporal.client.WorkflowClientOptions;
5+
import io.temporal.serviceclient.WorkflowServiceStubs;
6+
import io.temporal.worker.Worker;
7+
import io.temporal.worker.WorkerFactory;
8+
import io.temporal.worker.WorkflowImplementationOptions;
9+
import io.temporal.workflow.NexusServiceOptions;
10+
import java.util.Collections;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
public class CallerRemoteWorker {
15+
private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorker.class);
16+
17+
public static final String NAMESPACE = "nexus-sync-operations-caller-namespace";
18+
public static final String TASK_QUEUE = "nexus-sync-operations-caller-remote-task-queue";
19+
static final String NEXUS_ENDPOINT = "nexus-sync-operations-nexus-endpoint";
20+
21+
public static void main(String[] args) throws InterruptedException {
22+
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
23+
WorkflowClient client =
24+
WorkflowClient.newInstance(
25+
service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build());
26+
27+
WorkerFactory factory = WorkerFactory.newInstance(client);
28+
Worker worker = factory.newWorker(TASK_QUEUE);
29+
worker.registerWorkflowImplementationTypes(
30+
WorkflowImplementationOptions.newBuilder()
31+
.setNexusServiceOptions(
32+
Collections.singletonMap(
33+
"NexusRemoteGreetingService",
34+
NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build()))
35+
.build(),
36+
CallerRemoteWorkflowImpl.class);
37+
38+
factory.start();
39+
logger.info("Caller remote worker started, ctrl+c to exit");
40+
Thread.currentThread().join();
41+
}
42+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.temporal.samples.nexus_sync_operations.caller_remote;
2+
3+
import io.temporal.workflow.WorkflowInterface;
4+
import io.temporal.workflow.WorkflowMethod;
5+
import java.util.List;
6+
7+
@WorkflowInterface
8+
public interface CallerRemoteWorkflow {
9+
@WorkflowMethod
10+
List<String> run();
11+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.temporal.samples.nexus_sync_operations.caller_remote;
2+
3+
import io.temporal.samples.nexus_sync_operations.service.Language;
4+
import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService;
5+
import io.temporal.samples.nexus_sync_operations.service.NexusRemoteGreetingService;
6+
import io.temporal.workflow.NexusOperationHandle;
7+
import io.temporal.workflow.NexusOperationOptions;
8+
import io.temporal.workflow.NexusServiceOptions;
9+
import io.temporal.workflow.Workflow;
10+
import java.time.Duration;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow {
17+
18+
private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class);
19+
20+
private static final String REMOTE_WORKFLOW_ID = "nexus-sync-operations-remote-greeting-workflow";
21+
22+
NexusRemoteGreetingService greetingRemoteService =
23+
Workflow.newNexusServiceStub(
24+
NexusRemoteGreetingService.class,
25+
NexusServiceOptions.newBuilder()
26+
.setOperationOptions(
27+
NexusOperationOptions.newBuilder()
28+
.setScheduleToCloseTimeout(Duration.ofSeconds(10))
29+
.build())
30+
.build());
31+
32+
@Override
33+
public List<String> run() {
34+
List<String> log = new ArrayList<>();
35+
36+
// Start a new GreetingWorkflow on the handler side via Nexus.
37+
NexusOperationHandle<String> handle =
38+
Workflow.startNexusOperation(
39+
greetingRemoteService::runFromRemote,
40+
new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_ID));
41+
// Wait for the operation to be started (workflow is now running on the handler).
42+
handle.getExecution().get();
43+
log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_ID);
44+
45+
// Query the remote workflow for supported languages.
46+
NexusGreetingService.GetLanguagesOutput languagesOutput =
47+
greetingRemoteService.getLanguages(
48+
new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ID));
49+
log.add("supported languages: " + languagesOutput.getLanguages());
50+
51+
// Update the language on the remote workflow.
52+
Language previousLanguage =
53+
greetingRemoteService.setLanguage(
54+
new NexusRemoteGreetingService.SetLanguageInput(Language.ARABIC, REMOTE_WORKFLOW_ID));
55+
logger.info("Language changed from {}", previousLanguage);
56+
57+
// Confirm the change by querying.
58+
Language currentLanguage =
59+
greetingRemoteService.getLanguage(
60+
new NexusRemoteGreetingService.GetLanguageInput(REMOTE_WORKFLOW_ID));
61+
log.add("language changed: " + previousLanguage.name() + " -> " + currentLanguage.name());
62+
63+
// Approve the remote workflow so it can complete.
64+
greetingRemoteService.approve(
65+
new NexusRemoteGreetingService.ApproveInput("remote-caller", REMOTE_WORKFLOW_ID));
66+
log.add("workflow approved");
67+
68+
// Wait for the remote workflow to finish and return its result.
69+
String result = handle.getResult().get();
70+
log.add("workflow result: " + result);
71+
72+
return log;
73+
}
74+
}

0 commit comments

Comments
 (0)