Skip to content

Commit 87c691b

Browse files
committed
Status checkin
1 parent 385adc4 commit 87c691b

30 files changed

+1613
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.
5+
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).
8+
9+
### Sample directory structure
10+
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
15+
16+
### Instructions
17+
18+
Start a Temporal server:
19+
20+
```bash
21+
temporal server start-dev
22+
```
23+
24+
Create the caller and handler namespaces and the Nexus endpoint:
25+
26+
```bash
27+
temporal operator namespace create --namespace nexus-sync-operations-handler-namespace
28+
temporal operator namespace create --namespace nexus-sync-operations-caller-namespace
29+
30+
temporal operator nexus endpoint create \
31+
--name nexus-sync-operations-nexus-endpoint \
32+
--target-namespace nexus-sync-operations-handler-namespace \
33+
--target-task-queue nexus-sync-operations-handler-task-queue
34+
```
35+
36+
In one terminal, run the handler worker (starts the long-running entity workflow and polls for
37+
Nexus, workflow, and activity tasks):
38+
39+
```bash
40+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.handler.HandlerWorker
41+
```
42+
43+
In a second terminal, run the caller worker:
44+
45+
```bash
46+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerWorker
47+
```
48+
49+
In a third terminal, start the caller workflow:
50+
51+
```bash
52+
./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerStarter
53+
```
54+
55+
You should see output like:
56+
57+
```
58+
supported languages: [CHINESE, ENGLISH]
59+
language changed: ENGLISH -> ARABIC
60+
```
61+
62+
### How it works
63+
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:
66+
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
73+
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.
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;
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 CallerStarter {
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(CallerWorker.NAMESPACE).build());
18+
19+
CallerWorkflow workflow =
20+
client.newWorkflowStub(
21+
CallerWorkflow.class,
22+
WorkflowOptions.newBuilder()
23+
.setWorkflowId("nexus-sync-operations-caller-" + UUID.randomUUID())
24+
.setTaskQueue(CallerWorker.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;
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 CallerWorker {
15+
private static final Logger logger = LoggerFactory.getLogger(CallerWorker.class);
16+
17+
public static final String NAMESPACE = "nexus-sync-operations-caller-namespace";
18+
public static final String TASK_QUEUE = "nexus-sync-operations-caller-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+
"GreetingService",
34+
NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build()))
35+
.build(),
36+
CallerWorkflowImpl.class);
37+
38+
factory.start();
39+
logger.info("Caller 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;
2+
3+
import io.temporal.workflow.WorkflowInterface;
4+
import io.temporal.workflow.WorkflowMethod;
5+
import java.util.List;
6+
7+
@WorkflowInterface
8+
public interface CallerWorkflow {
9+
@WorkflowMethod
10+
List<String> run();
11+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.temporal.samples.nexus_sync_operations.caller;
2+
3+
import io.temporal.failure.ApplicationFailure;
4+
import io.temporal.samples.nexus_sync_operations.service.GreetingService;
5+
import io.temporal.samples.nexus_sync_operations.service.Language;
6+
import io.temporal.workflow.NexusOperationOptions;
7+
import io.temporal.workflow.NexusServiceOptions;
8+
import io.temporal.workflow.Workflow;
9+
import java.time.Duration;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public class CallerWorkflowImpl implements CallerWorkflow {
14+
15+
// The endpoint is configured at the worker level in CallerWorker; only operation options are
16+
// set here.
17+
GreetingService greetingService =
18+
Workflow.newNexusServiceStub(
19+
GreetingService.class,
20+
NexusServiceOptions.newBuilder()
21+
.setOperationOptions(
22+
NexusOperationOptions.newBuilder()
23+
.setScheduleToCloseTimeout(Duration.ofSeconds(10))
24+
.build())
25+
.build());
26+
27+
@Override
28+
public List<String> run() {
29+
List<String> log = new ArrayList<>();
30+
31+
// 👉 Call a Nexus operation backed by a query against the entity workflow.
32+
GreetingService.GetLanguagesOutput languagesOutput =
33+
greetingService.getLanguages(new GreetingService.GetLanguagesInput(false));
34+
log.add("supported languages: " + languagesOutput.getLanguages());
35+
36+
// 👉 Call a Nexus operation backed by an update against the entity workflow.
37+
Language previousLanguage =
38+
greetingService.setLanguage(new GreetingService.SetLanguageInput(Language.ARABIC));
39+
40+
// 👉 Call a Nexus operation backed by a query to confirm the language change.
41+
Language currentLanguage = greetingService.getLanguage(new GreetingService.GetLanguageInput());
42+
if (currentLanguage != Language.ARABIC) {
43+
throw ApplicationFailure.newFailure(
44+
"expected language ARABIC, got " + currentLanguage, "AssertionError");
45+
}
46+
47+
log.add("language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name());
48+
49+
// 👉 Call a Nexus operation backed by a signal against the entity workflow.
50+
greetingService.approve(new GreetingService.ApproveInput("caller"));
51+
log.add("workflow approved");
52+
53+
return log;
54+
}
55+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.temporal.samples.nexus_sync_operations.handler;
2+
3+
import io.temporal.activity.ActivityInterface;
4+
import io.temporal.activity.ActivityMethod;
5+
import io.temporal.samples.nexus_sync_operations.service.Language;
6+
7+
@ActivityInterface
8+
public interface GreetingActivity {
9+
// Simulates a call to a remote greeting service. Returns null if the language is not supported.
10+
@ActivityMethod
11+
String callGreetingService(Language language);
12+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.temporal.samples.nexus_sync_operations.handler;
2+
3+
import io.temporal.samples.nexus_sync_operations.service.Language;
4+
import java.util.EnumMap;
5+
import java.util.Map;
6+
7+
public class GreetingActivityImpl implements GreetingActivity {
8+
9+
private static final Map<Language, String> GREETINGS = new EnumMap<>(Language.class);
10+
11+
static {
12+
GREETINGS.put(Language.ARABIC, "مرحبا بالعالم");
13+
GREETINGS.put(Language.CHINESE, "你好,世界");
14+
GREETINGS.put(Language.ENGLISH, "Hello, world");
15+
GREETINGS.put(Language.FRENCH, "Bonjour, monde");
16+
GREETINGS.put(Language.HINDI, "नमस्ते दुनिया");
17+
GREETINGS.put(Language.PORTUGUESE, "Olá mundo");
18+
GREETINGS.put(Language.SPANISH, "Hola mundo");
19+
}
20+
21+
@Override
22+
public String callGreetingService(Language language) {
23+
return GREETINGS.get(language);
24+
}
25+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.temporal.samples.nexus_sync_operations.handler;
2+
3+
import io.nexusrpc.handler.OperationHandler;
4+
import io.nexusrpc.handler.OperationImpl;
5+
import io.nexusrpc.handler.ServiceImpl;
6+
import io.temporal.nexus.Nexus;
7+
import io.temporal.samples.nexus_sync_operations.service.GreetingService;
8+
import io.temporal.samples.nexus_sync_operations.service.Language;
9+
10+
/**
11+
* Nexus operation handler implementation. Each operation is backed by the long-running
12+
* GreetingWorkflow entity. The operations are synchronous (sync_operation) because queries and
13+
* updates against a running workflow complete quickly.
14+
*/
15+
@ServiceImpl(service = GreetingService.class)
16+
public class GreetingServiceImpl {
17+
18+
private final String workflowId;
19+
20+
public GreetingServiceImpl(String workflowId) {
21+
this.workflowId = workflowId;
22+
}
23+
24+
private GreetingWorkflow getWorkflowStub() {
25+
return Nexus.getOperationContext()
26+
.getWorkflowClient()
27+
.newWorkflowStub(GreetingWorkflow.class, workflowId);
28+
}
29+
30+
// 👉 Backed by a query against the long-running entity workflow.
31+
@OperationImpl
32+
public OperationHandler<GreetingService.GetLanguagesInput, GreetingService.GetLanguagesOutput>
33+
getLanguages() {
34+
return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguages(input));
35+
}
36+
37+
// 👉 Backed by a query against the long-running entity workflow.
38+
@OperationImpl
39+
public OperationHandler<GreetingService.GetLanguageInput, Language> getLanguage() {
40+
return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguage());
41+
}
42+
43+
// 👉 Backed by an update against the long-running entity workflow. Although updates can run for
44+
// an arbitrarily long time, when exposed via a sync Nexus operation the update should complete
45+
// quickly (sync operations must finish in under 10s).
46+
@OperationImpl
47+
public OperationHandler<GreetingService.SetLanguageInput, Language> setLanguage() {
48+
return OperationHandler.sync(
49+
(ctx, details, input) -> getWorkflowStub().setLanguageUsingActivity(input));
50+
}
51+
52+
// 👉 Backed by a signal against the long-running entity workflow.
53+
@OperationImpl
54+
public OperationHandler<GreetingService.ApproveInput, GreetingService.ApproveOutput> approve() {
55+
return OperationHandler.sync(
56+
(ctx, details, input) -> {
57+
getWorkflowStub().approve(new GreetingWorkflow.ApproveInput(input.getName()));
58+
return new GreetingService.ApproveOutput();
59+
});
60+
}
61+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.temporal.samples.nexus_sync_operations.handler;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import io.temporal.samples.nexus_sync_operations.service.GreetingService;
6+
import io.temporal.samples.nexus_sync_operations.service.Language;
7+
import io.temporal.workflow.QueryMethod;
8+
import io.temporal.workflow.SignalMethod;
9+
import io.temporal.workflow.UpdateMethod;
10+
import io.temporal.workflow.UpdateValidatorMethod;
11+
import io.temporal.workflow.WorkflowInterface;
12+
import io.temporal.workflow.WorkflowMethod;
13+
14+
/**
15+
* A long-running "entity" workflow that backs the GreetingService Nexus operations. The workflow
16+
* exposes queries, an update, and a signal. These are private implementation details of the Nexus
17+
* service: the caller only interacts via Nexus operations.
18+
*/
19+
@WorkflowInterface
20+
public interface GreetingWorkflow {
21+
22+
class ApproveInput {
23+
private final String name;
24+
25+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
26+
public ApproveInput(@JsonProperty("name") String name) {
27+
this.name = name;
28+
}
29+
30+
@JsonProperty("name")
31+
public String getName() {
32+
return name;
33+
}
34+
}
35+
36+
@WorkflowMethod
37+
String run();
38+
39+
// Returns the languages currently supported by the workflow.
40+
@QueryMethod
41+
GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLanguagesInput input);
42+
43+
// Returns the currently active language.
44+
@QueryMethod
45+
Language getLanguage();
46+
47+
// Approves the workflow, allowing it to complete.
48+
@SignalMethod
49+
void approve(ApproveInput input);
50+
51+
// Changes the active language synchronously (only supports languages already in the greetings
52+
// map).
53+
@UpdateMethod
54+
Language setLanguage(GreetingService.SetLanguageInput input);
55+
56+
@UpdateValidatorMethod(updateName = "setLanguage")
57+
void validateSetLanguage(GreetingService.SetLanguageInput input);
58+
59+
// Changes the active language, calling an activity to fetch a greeting for new languages.
60+
@UpdateMethod
61+
Language setLanguageUsingActivity(GreetingService.SetLanguageInput input);
62+
}

0 commit comments

Comments
 (0)