From 87c691bb11de208c1c361613cf104356453664ad Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 13:30:38 -0700 Subject: [PATCH 01/10] Status checkin --- .../samples/nexus_sync_operations/README.md | 76 ++++++++ .../caller/CallerStarter.java | 30 +++ .../caller/CallerWorker.java | 42 +++++ .../caller/CallerWorkflow.java | 11 ++ .../caller/CallerWorkflowImpl.java | 55 ++++++ .../handler/GreetingActivity.java | 12 ++ .../handler/GreetingActivityImpl.java | 25 +++ .../handler/GreetingServiceImpl.java | 61 ++++++ .../handler/GreetingWorkflow.java | 62 ++++++ .../handler/GreetingWorkflowImpl.java | 109 +++++++++++ .../handler/HandlerWorker.java | 51 +++++ .../service/GreetingService.java | 100 ++++++++++ .../service/Language.java | 11 ++ .../temporal/samples/nexusmessaging/README.MD | 102 ++++++++++ .../nexusmessaging/caller/CallerStarter.java | 32 ++++ .../nexusmessaging/caller/CallerWorker.java | 32 ++++ .../MessageCallerStartHandlerWorkflow.java | 10 + ...MessageCallerStartHandlerWorkflowImpl.java | 69 +++++++ .../caller/MessageCallerWorkflow.java | 10 + .../caller/MessageCallerWorkflowImpl.java | 42 +++++ .../nexusmessaging/handler/HandlerWorker.java | 62 ++++++ .../handler/MessageHandlerRemoteWorkflow.java | 29 +++ .../MessageHandlerRemoteWorkflowImpl.java | 51 +++++ .../handler/MessageHandlerWorkflow.java | 29 +++ .../handler/MessageHandlerWorkflowImpl.java | 47 +++++ .../handler/SampleNexusServiceImpl.java | 129 +++++++++++++ .../nexusmessaging/options/ClientOptions.java | 137 ++++++++++++++ .../service/SampleNexusService.java | 177 ++++++++++++++++++ .../nexusmessaging/service/description.md | 6 + yarn.lock | 4 + 30 files changed, 1613 insertions(+) create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/README.MD create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java create mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md create mode 100644 yarn.lock diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md b/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md new file mode 100644 index 00000000..5d76d1fe --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md @@ -0,0 +1,76 @@ +This sample shows how to create a Nexus service that is backed by a long-running workflow and +exposes operations that execute updates and queries against that workflow. The long-running +workflow, and the updates/queries, are private implementation details of the Nexus service: the +caller does not know how the operations are implemented. + +This is a Java port of the +[nexus_sync_operations Python sample](https://github.com/temporalio/samples-python/tree/main/nexus_sync_operations). + +### Sample directory structure + +- [`service/GreetingService.java`](./service/GreetingService.java) — shared Nexus service definition with input/output types +- [`service/Language.java`](./service/Language.java) — shared language enum +- [`handler/`](./handler/) — Nexus operation handlers, the long-running entity workflow they back, an activity, and a handler worker +- [`caller/`](./caller/) — a caller workflow that executes Nexus operations, together with a worker and starter + +### Instructions + +Start a Temporal server: + +```bash +temporal server start-dev +``` + +Create the caller and handler namespaces and the Nexus endpoint: + +```bash +temporal operator namespace create --namespace nexus-sync-operations-handler-namespace +temporal operator namespace create --namespace nexus-sync-operations-caller-namespace + +temporal operator nexus endpoint create \ + --name nexus-sync-operations-nexus-endpoint \ + --target-namespace nexus-sync-operations-handler-namespace \ + --target-task-queue nexus-sync-operations-handler-task-queue +``` + +In one terminal, run the handler worker (starts the long-running entity workflow and polls for +Nexus, workflow, and activity tasks): + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.handler.HandlerWorker +``` + +In a second terminal, run the caller worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerWorker +``` + +In a third terminal, start the caller workflow: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerStarter +``` + +You should see output like: + +``` +supported languages: [CHINESE, ENGLISH] +language changed: ENGLISH -> ARABIC +``` + +### How it works + +The handler starts a single long-running `GreetingWorkflow` entity workflow when the worker boots. +This workflow holds the current language and a map of greetings, and exposes: + +- `getLanguages` — a `@QueryMethod` listing supported languages +- `getLanguage` — a `@QueryMethod` returning the current language +- `setLanguage` — an `@UpdateMethod` (sync) for switching between already-loaded languages +- `setLanguageUsingActivity` — an `@UpdateMethod` (async) that calls an activity to fetch a + greeting for a new language before switching +- `approve` — a `@SignalMethod` that allows the workflow to complete + +The three `GreetingService` Nexus operations delegate to these workflow handlers via the Temporal +client inside each `OperationHandler.sync` implementation. The caller workflow sees only the Nexus +operations; the entity workflow is a private implementation detail. diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java new file mode 100644 index 00000000..48c3758d --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java @@ -0,0 +1,30 @@ +package io.temporal.samples.nexus_sync_operations.caller; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import java.util.List; +import java.util.UUID; + +public class CallerStarter { + + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, + WorkflowClientOptions.newBuilder().setNamespace(CallerWorker.NAMESPACE).build()); + + CallerWorkflow workflow = + client.newWorkflowStub( + CallerWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId("nexus-sync-operations-caller-" + UUID.randomUUID()) + .setTaskQueue(CallerWorker.TASK_QUEUE) + .build()); + + List log = workflow.run(); + log.forEach(System.out::println); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java new file mode 100644 index 00000000..284bf9bd --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java @@ -0,0 +1,42 @@ +package io.temporal.samples.nexus_sync_operations.caller; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.NexusServiceOptions; +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallerWorker { + private static final Logger logger = LoggerFactory.getLogger(CallerWorker.class); + + public static final String NAMESPACE = "nexus-sync-operations-caller-namespace"; + public static final String TASK_QUEUE = "nexus-sync-operations-caller-task-queue"; + static final String NEXUS_ENDPOINT = "nexus-sync-operations-nexus-endpoint"; + + public static void main(String[] args) throws InterruptedException { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + worker.registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setNexusServiceOptions( + Collections.singletonMap( + "GreetingService", + NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build())) + .build(), + CallerWorkflowImpl.class); + + factory.start(); + logger.info("Caller worker started, ctrl+c to exit"); + Thread.currentThread().join(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java new file mode 100644 index 00000000..1061f09a --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java @@ -0,0 +1,11 @@ +package io.temporal.samples.nexus_sync_operations.caller; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; +import java.util.List; + +@WorkflowInterface +public interface CallerWorkflow { + @WorkflowMethod + List run(); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java new file mode 100644 index 00000000..82e96c05 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java @@ -0,0 +1,55 @@ +package io.temporal.samples.nexus_sync_operations.caller; + +import io.temporal.failure.ApplicationFailure; +import io.temporal.samples.nexus_sync_operations.service.GreetingService; +import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +public class CallerWorkflowImpl implements CallerWorkflow { + + // The endpoint is configured at the worker level in CallerWorker; only operation options are + // set here. + GreetingService greetingService = + Workflow.newNexusServiceStub( + GreetingService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public List run() { + List log = new ArrayList<>(); + + // 👉 Call a Nexus operation backed by a query against the entity workflow. + GreetingService.GetLanguagesOutput languagesOutput = + greetingService.getLanguages(new GreetingService.GetLanguagesInput(false)); + log.add("supported languages: " + languagesOutput.getLanguages()); + + // 👉 Call a Nexus operation backed by an update against the entity workflow. + Language previousLanguage = + greetingService.setLanguage(new GreetingService.SetLanguageInput(Language.ARABIC)); + + // 👉 Call a Nexus operation backed by a query to confirm the language change. + Language currentLanguage = greetingService.getLanguage(new GreetingService.GetLanguageInput()); + if (currentLanguage != Language.ARABIC) { + throw ApplicationFailure.newFailure( + "expected language ARABIC, got " + currentLanguage, "AssertionError"); + } + + log.add("language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name()); + + // 👉 Call a Nexus operation backed by a signal against the entity workflow. + greetingService.approve(new GreetingService.ApproveInput("caller")); + log.add("workflow approved"); + + return log; + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java new file mode 100644 index 00000000..ed150780 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java @@ -0,0 +1,12 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import io.temporal.samples.nexus_sync_operations.service.Language; + +@ActivityInterface +public interface GreetingActivity { + // Simulates a call to a remote greeting service. Returns null if the language is not supported. + @ActivityMethod + String callGreetingService(Language language); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java new file mode 100644 index 00000000..b7ab336f --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java @@ -0,0 +1,25 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.temporal.samples.nexus_sync_operations.service.Language; +import java.util.EnumMap; +import java.util.Map; + +public class GreetingActivityImpl implements GreetingActivity { + + private static final Map GREETINGS = new EnumMap<>(Language.class); + + static { + GREETINGS.put(Language.ARABIC, "مرحبا بالعالم"); + GREETINGS.put(Language.CHINESE, "你好,世界"); + GREETINGS.put(Language.ENGLISH, "Hello, world"); + GREETINGS.put(Language.FRENCH, "Bonjour, monde"); + GREETINGS.put(Language.HINDI, "नमस्ते दुनिया"); + GREETINGS.put(Language.PORTUGUESE, "Olá mundo"); + GREETINGS.put(Language.SPANISH, "Hola mundo"); + } + + @Override + public String callGreetingService(Language language) { + return GREETINGS.get(language); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java new file mode 100644 index 00000000..726ae5b2 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java @@ -0,0 +1,61 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.nexusrpc.handler.OperationHandler; +import io.nexusrpc.handler.OperationImpl; +import io.nexusrpc.handler.ServiceImpl; +import io.temporal.nexus.Nexus; +import io.temporal.samples.nexus_sync_operations.service.GreetingService; +import io.temporal.samples.nexus_sync_operations.service.Language; + +/** + * Nexus operation handler implementation. Each operation is backed by the long-running + * GreetingWorkflow entity. The operations are synchronous (sync_operation) because queries and + * updates against a running workflow complete quickly. + */ +@ServiceImpl(service = GreetingService.class) +public class GreetingServiceImpl { + + private final String workflowId; + + public GreetingServiceImpl(String workflowId) { + this.workflowId = workflowId; + } + + private GreetingWorkflow getWorkflowStub() { + return Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(GreetingWorkflow.class, workflowId); + } + + // 👉 Backed by a query against the long-running entity workflow. + @OperationImpl + public OperationHandler + getLanguages() { + return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguages(input)); + } + + // 👉 Backed by a query against the long-running entity workflow. + @OperationImpl + public OperationHandler getLanguage() { + return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguage()); + } + + // 👉 Backed by an update against the long-running entity workflow. Although updates can run for + // an arbitrarily long time, when exposed via a sync Nexus operation the update should complete + // quickly (sync operations must finish in under 10s). + @OperationImpl + public OperationHandler setLanguage() { + return OperationHandler.sync( + (ctx, details, input) -> getWorkflowStub().setLanguageUsingActivity(input)); + } + + // 👉 Backed by a signal against the long-running entity workflow. + @OperationImpl + public OperationHandler approve() { + return OperationHandler.sync( + (ctx, details, input) -> { + getWorkflowStub().approve(new GreetingWorkflow.ApproveInput(input.getName())); + return new GreetingService.ApproveOutput(); + }); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java new file mode 100644 index 00000000..05b46828 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java @@ -0,0 +1,62 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.temporal.samples.nexus_sync_operations.service.GreetingService; +import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.UpdateMethod; +import io.temporal.workflow.UpdateValidatorMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +/** + * A long-running "entity" workflow that backs the GreetingService Nexus operations. The workflow + * exposes queries, an update, and a signal. These are private implementation details of the Nexus + * service: the caller only interacts via Nexus operations. + */ +@WorkflowInterface +public interface GreetingWorkflow { + + class ApproveInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public ApproveInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + @WorkflowMethod + String run(); + + // Returns the languages currently supported by the workflow. + @QueryMethod + GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLanguagesInput input); + + // Returns the currently active language. + @QueryMethod + Language getLanguage(); + + // Approves the workflow, allowing it to complete. + @SignalMethod + void approve(ApproveInput input); + + // Changes the active language synchronously (only supports languages already in the greetings + // map). + @UpdateMethod + Language setLanguage(GreetingService.SetLanguageInput input); + + @UpdateValidatorMethod(updateName = "setLanguage") + void validateSetLanguage(GreetingService.SetLanguageInput input); + + // Changes the active language, calling an activity to fetch a greeting for new languages. + @UpdateMethod + Language setLanguageUsingActivity(GreetingService.SetLanguageInput input); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java new file mode 100644 index 00000000..1f1e6af8 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java @@ -0,0 +1,109 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.temporal.activity.ActivityOptions; +import io.temporal.failure.ApplicationFailure; +import io.temporal.samples.nexus_sync_operations.service.GreetingService; +import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.WorkflowLock; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GreetingWorkflowImpl implements GreetingWorkflow { + + private boolean approvedForRelease = false; + private final Map greetings = new EnumMap<>(Language.class); + private Language language = Language.ENGLISH; + + private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); + + // Used to serialize concurrent setLanguageUsingActivity calls so that only one activity runs at + // a time per update handler execution. + private final WorkflowLock lock = Workflow.newWorkflowLock(); + + private final GreetingActivity greetingActivity = + Workflow.newActivityStub( + GreetingActivity.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); + + public GreetingWorkflowImpl() { + greetings.put(Language.CHINESE, "你好,世界"); + greetings.put(Language.ENGLISH, "Hello, world"); + } + + @Override + public String run() { + // Wait until approved and all in-flight update handlers have finished. + Workflow.await(() -> approvedForRelease && Workflow.isEveryHandlerFinished()); + return greetings.get(language); + } + + @Override + public GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLanguagesInput input) { + List result; + if (input.isIncludeUnsupported()) { + result = new ArrayList<>(Arrays.asList(Language.values())); + } else { + result = new ArrayList<>(greetings.keySet()); + } + Collections.sort(result); + return new GreetingService.GetLanguagesOutput(result); + } + + @Override + public Language getLanguage() { + return language; + } + + @Override + public void approve(ApproveInput input) { + logger.info("Approval signal received"); + approvedForRelease = true; + } + + @Override + public Language setLanguage(GreetingService.SetLanguageInput input) { + logger.info("setLanguage update received"); + Language previous = language; + language = input.getLanguage(); + return previous; + } + + @Override + public void validateSetLanguage(GreetingService.SetLanguageInput input) { + logger.info("validateSetLanguage called"); + if (!greetings.containsKey(input.getLanguage())) { + throw new IllegalArgumentException(input.getLanguage().name() + " is not supported"); + } + } + + @Override + public Language setLanguageUsingActivity(GreetingService.SetLanguageInput input) { + if (!greetings.containsKey(input.getLanguage())) { + // Use a lock so that if this handler is called concurrently, each call executes its activity + // only after the previous one has completed. This ensures updates are processed in order. + lock.lock(); + try { + String greeting = greetingActivity.callGreetingService(input.getLanguage()); + if (greeting == null) { + throw ApplicationFailure.newFailure( + "Greeting service does not support " + input.getLanguage().name(), + "UnsupportedLanguage"); + } + greetings.put(input.getLanguage(), greeting); + } finally { + lock.unlock(); + } + } + Language previous = language; + language = input.getLanguage(); + return previous; + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java new file mode 100644 index 00000000..05851047 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java @@ -0,0 +1,51 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowExecutionAlreadyStarted; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HandlerWorker { + private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); + + public static final String NAMESPACE = "nexus-sync-operations-handler-namespace"; + public static final String TASK_QUEUE = "nexus-sync-operations-handler-task-queue"; + static final String WORKFLOW_ID = "nexus-sync-operations-greeting-workflow"; + + public static void main(String[] args) throws InterruptedException { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + + // Start the long-running entity workflow that backs the Nexus service, if not already running. + GreetingWorkflow greetingWorkflow = + client.newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(TASK_QUEUE) + .build()); + try { + WorkflowClient.start(greetingWorkflow::run); + logger.info("Started greeting workflow: {}", WORKFLOW_ID); + } catch (WorkflowExecutionAlreadyStarted e) { + logger.info("Greeting workflow already running: {}", WORKFLOW_ID); + } + + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); + worker.registerActivitiesImplementations(new GreetingActivityImpl()); + worker.registerNexusServiceImplementation(new GreetingServiceImpl(WORKFLOW_ID)); + + factory.start(); + logger.info("Handler worker started, ctrl+c to exit"); + Thread.currentThread().join(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java new file mode 100644 index 00000000..407d309d --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java @@ -0,0 +1,100 @@ +package io.temporal.samples.nexus_sync_operations.service; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.nexusrpc.Operation; +import io.nexusrpc.Service; +import java.util.List; + +/** + * Nexus service definition. Shared between the handler and caller. The caller uses this to create a + * type-safe Nexus client stub; the handler implements the operations. + */ +@Service +public interface GreetingService { + + class GetLanguagesInput { + private final boolean includeUnsupported; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguagesInput(@JsonProperty("includeUnsupported") boolean includeUnsupported) { + this.includeUnsupported = includeUnsupported; + } + + @JsonProperty("includeUnsupported") + public boolean isIncludeUnsupported() { + return includeUnsupported; + } + } + + class GetLanguagesOutput { + private final List languages; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguagesOutput(@JsonProperty("languages") List languages) { + this.languages = languages; + } + + @JsonProperty("languages") + public List getLanguages() { + return languages; + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + class GetLanguageInput { + @JsonCreator + public GetLanguageInput() {} + } + + class ApproveInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public ApproveInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + class ApproveOutput { + @JsonCreator + public ApproveOutput() {} + } + + class SetLanguageInput { + private final Language language; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SetLanguageInput(@JsonProperty("language") Language language) { + this.language = language; + } + + @JsonProperty("language") + public Language getLanguage() { + return language; + } + } + + // Returns the languages supported by the greeting workflow. + @Operation + GetLanguagesOutput getLanguages(GetLanguagesInput input); + + // Returns the currently active language. + @Operation + Language getLanguage(GetLanguageInput input); + + // Changes the active language, returning the previous one. + @Operation + Language setLanguage(SetLanguageInput input); + + // Approves the workflow, allowing it to complete. + @Operation + ApproveOutput approve(ApproveInput input); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java new file mode 100644 index 00000000..5bc9f0dd --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java @@ -0,0 +1,11 @@ +package io.temporal.samples.nexus_sync_operations.service; + +public enum Language { + ARABIC, + CHINESE, + ENGLISH, + FRENCH, + HINDI, + PORTUGUESE, + SPANISH +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD b/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD new file mode 100644 index 00000000..a8fe380e --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD @@ -0,0 +1,102 @@ +# Nexus + +Temporal Nexus is a new feature of the Temporal platform designed to connect durable executions across team, namespace, +region, and cloud boundaries. It promotes a more modular architecture for sharing a subset of your team’s capabilities +via well-defined service API contracts for other teams to use, that abstract underlying Temporal primitives, like +Workflows, or execute arbitrary code. + +Learn more at [temporal.io/nexus](https://temporal.io/nexus). + +This sample shows how to use Temporal for authoring a Nexus service and call it from a workflow. + +### Sample directory structure + +- [service](./service) - shared service definition +- [caller](./caller) - caller workflows, worker, and starter +- [handler](./handler) - handler workflow, operations, and worker +- [options](./options) - command line argument parsing utility + +## Getting started locally + +### Get `temporal` CLI to enable local development + +1. Follow the instructions on the [docs + site](https://learn.temporal.io/getting_started/go/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + to install Temporal CLI. + +> NOTE: The recommended version is at least v1.3.0. + +### Spin up environment + +#### Start temporal server + +> HTTP port is required for Nexus communications + +``` +temporal server start-dev +``` + +### Initialize environment + +In a separate terminal window + +#### Create caller and target namespaces + +``` +temporal operator namespace create --namespace my-target-namespace +temporal operator namespace create --namespace my-caller-namespace +``` + +#### Create Nexus endpoint + +``` +temporal operator nexus endpoint create \ + --name my-nexus-endpoint-name \ + --target-namespace my-target-namespace \ + --target-task-queue my-handler-task-queue \ + --description-file ./core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md +``` + +## Getting started with a self-hosted service or Temporal Cloud + +Nexus is currently available as +[Public Preview](https://docs.temporal.io/evaluate/development-production-features/release-stages). + +Self hosted users can [try Nexus +out](https://github.com/temporalio/temporal/blob/main/docs/architecture/nexus.md#trying-nexus-out) in single cluster +deployments with server version 1.25.0. + +### Make Nexus calls across namespace boundaries + +> Instructions apply for local development, for Temporal Cloud or a self-hosted setups, supply the relevant [CLI +> flags](./options/ClientOptions.java) to properly set up the connection. + +In separate terminal windows: + +### Nexus handler worker + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.handler.HandlerWorker \ + --args="-target-host localhost:7233 -namespace my-target-namespace" +``` + +### Nexus caller worker + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.caller.CallerWorker \ + --args="-target-host localhost:7233 -namespace my-caller-namespace" +``` + +### Start caller workflow + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.caller.CallerStarter \ + --args="-target-host localhost:7233 -namespace my-caller-namespace" +``` + +### Output + +which should result in: +``` +[main] INFO i.t.s.nexusmessaging.caller.CallerStarter - Workflow result: Nexus Echo 👋 +``` diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java new file mode 100644 index 00000000..498ba524 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java @@ -0,0 +1,32 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.samples.nexusmessaging.options.ClientOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallerStarter { + private static final Logger logger = LoggerFactory.getLogger(CallerStarter.class); + + public static void main(String[] args) { + WorkflowClient client = ClientOptions.getWorkflowClient(args); + + WorkflowOptions workflowOptions = + WorkflowOptions.newBuilder().setTaskQueue(CallerWorker.DEFAULT_TASK_QUEUE_NAME).build(); + + MessageCallerStartHandlerWorkflow messageWorkflow = + client.newWorkflowStub(MessageCallerStartHandlerWorkflow.class, workflowOptions); + + // MessageCallerWorkflow messageWorkflow = + // client.newWorkflowStub(MessageCallerWorkflow.class, workflowOptions); + WorkflowExecution execution = WorkflowClient.start(messageWorkflow::sentMessage); + logger.info( + "Started readMessage workflowId: {} runId: {}", + execution.getWorkflowId(), + execution.getRunId()); + String returnVal = messageWorkflow.sentMessage(); + logger.info("Workflow readMessage done - retval is {}", returnVal); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java new file mode 100644 index 00000000..182c96f3 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java @@ -0,0 +1,32 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.nexusmessaging.options.ClientOptions; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.NexusServiceOptions; +import java.util.Collections; + +public class CallerWorker { + public static final String DEFAULT_TASK_QUEUE_NAME = "my-caller-workflow-task-queue"; + + public static void main(String[] args) { + WorkflowClient client = ClientOptions.getWorkflowClient(args); + + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); + worker.registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setNexusServiceOptions( + Collections.singletonMap( + "SampleNexusService", + NexusServiceOptions.newBuilder().setEndpoint("my-nexus-endpoint-name").build())) + .build(), + // MessageCallerWorkflowImpl.class, + MessageCallerStartHandlerWorkflowImpl.class); + + factory.start(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java new file mode 100644 index 00000000..ba04df24 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java @@ -0,0 +1,10 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface MessageCallerStartHandlerWorkflow { + @WorkflowMethod + String sentMessage(); +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java new file mode 100644 index 00000000..f44bf4ec --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java @@ -0,0 +1,69 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.NexusOperationHandle; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageCallerStartHandlerWorkflowImpl implements MessageCallerStartHandlerWorkflow { + + private static final Logger logger = + LoggerFactory.getLogger(MessageCallerStartHandlerWorkflowImpl.class); + + private final String workflowId = "Remote Start Workflow"; + + SampleNexusService sampleNexusService = + Workflow.newNexusServiceStub( + SampleNexusService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public String sentMessage() { + /* + SampleNexusService.QueryWorkflowOutput queryOutput = + sampleNexusService.queryWorkflow( + new SampleNexusService.QueryWorkflowInput("query string going in")); + logger.info("Query output: {}", queryOutput.getMessage()); + + SampleNexusService.UpdateWorkflowOutput updateOutput = + sampleNexusService.updateWorkflow( + new SampleNexusService.UpdateWorkflowInput("update input")); + logger.info("Update output: {}", updateOutput.getResult()); + + sampleNexusService.signalWorkflow(new SampleNexusService.SignalWorkflowInput("signal input")); + logger.info("Signal sent via Nexus"); + + return "Query: " + queryOutput.getMessage() + ", Update: " + updateOutput.getResult(); + + */ + + NexusOperationHandle handle = + Workflow.startNexusOperation( + sampleNexusService::runFromRemote, + new SampleNexusService.RunFromRemoteInput(workflowId)); + // Optionally wait for the operation to be started. NexusOperationExecution will contain the + // operation token in case this operation is asynchronous. + handle.getExecution().get(); + + SampleNexusService.QueryWorkflowOutput queryWorkflowOutput = + sampleNexusService.queryWorkflowRemoteStart( + new SampleNexusService.QueryWorkflowRemoteStartInput( + "query string going in", workflowId)); + logger.info("Caller has query output of {}", queryWorkflowOutput); + + sampleNexusService.signalWorkflowRemoteStart( + new SampleNexusService.SignalWorkflowRemoteStartInput("signal input", workflowId)); + logger.info("Signal sent via Nexus"); + + return handle.getResult().get().getMessage(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java new file mode 100644 index 00000000..1a9a9b71 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java @@ -0,0 +1,10 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface MessageCallerWorkflow { + @WorkflowMethod + String sentMessage(); +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java new file mode 100644 index 00000000..55a6cf66 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java @@ -0,0 +1,42 @@ +package io.temporal.samples.nexusmessaging.caller; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageCallerWorkflowImpl implements MessageCallerWorkflow { + + private static final Logger logger = LoggerFactory.getLogger(MessageCallerWorkflowImpl.class); + + SampleNexusService sampleNexusService = + Workflow.newNexusServiceStub( + SampleNexusService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public String sentMessage() { + SampleNexusService.QueryWorkflowOutput queryOutput = + sampleNexusService.queryWorkflow( + new SampleNexusService.QueryWorkflowInput("query string going in")); + logger.info("Query output: {}", queryOutput.getMessage()); + + SampleNexusService.UpdateWorkflowOutput updateOutput = + sampleNexusService.updateWorkflow( + new SampleNexusService.UpdateWorkflowInput("update input")); + logger.info("Update output: {}", updateOutput.getResult()); + + sampleNexusService.signalWorkflow(new SampleNexusService.SignalWorkflowInput("signal input")); + logger.info("Signal sent via Nexus"); + + return "Query: " + queryOutput.getMessage() + ", Update: " + updateOutput.getResult(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java new file mode 100644 index 00000000..8af0c721 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java @@ -0,0 +1,62 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowExecutionAlreadyStarted; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HandlerWorker { + + public static final String NAMESPACE = "my-target-namespace"; + public static final String DEFAULT_TASK_QUEUE_NAME = "my-handler-task-queue"; + static final String WORKFLOW_ID = "my-handler-workflow"; + + private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); + + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + + // Start the long-running entity workflow that backs the Nexus service, if not already running. + MessageHandlerWorkflow messageHandlerWorkflow = + client.newWorkflowStub( + MessageHandlerWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(WORKFLOW_ID) + .setTaskQueue(DEFAULT_TASK_QUEUE_NAME) + .build()); + try { + WorkflowClient.start(messageHandlerWorkflow::run); + logger.info("Started message handler workflow: {}", WORKFLOW_ID); + } catch (WorkflowExecutionAlreadyStarted e) { + logger.info("Message handler workflow already running: {}", WORKFLOW_ID); + } + + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); + worker.registerWorkflowImplementationTypes( + MessageHandlerWorkflowImpl.class, MessageHandlerRemoteWorkflowImpl.class); + // worker.registerActivitiesImplementations(new MessageHandlerWorkflowImpl()); + worker.registerNexusServiceImplementation(new SampleNexusServiceImpl(WORKFLOW_ID)); + + factory.start(); + logger.info("Handler worker started, ctrl+c to exit"); + + // WorkflowClient client = ClientOptions.getWorkflowClient(args); + // + // WorkerFactory factory = WorkerFactory.newInstance(client); + // + // Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); + // worker.registerWorkflowImplementationTypes(MessageHandlerWorkflowImpl.class); + // worker.registerNexusServiceImplementation(new SampleNexusServiceImpl()); + // + // factory.start(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java new file mode 100644 index 00000000..dad19f81 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java @@ -0,0 +1,29 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.UpdateMethod; +import io.temporal.workflow.UpdateValidatorMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface MessageHandlerRemoteWorkflow { + + @WorkflowMethod + SampleNexusService.RunFromRemoteOutput runFromRemote(SampleNexusService.RunFromRemoteInput input); + + @QueryMethod + SampleNexusService.QueryWorkflowOutput queryWorkflow(SampleNexusService.QueryWorkflowInput name); + + @SignalMethod + void signalWorkflow(SampleNexusService.SignalWorkflowInput name); + + @UpdateMethod + SampleNexusService.UpdateWorkflowOutput updateWorkflow( + SampleNexusService.UpdateWorkflowInput name); + + @UpdateValidatorMethod(updateName = "updateWorkflow") + void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name); +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java new file mode 100644 index 00000000..e6e2c5a0 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java @@ -0,0 +1,51 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.Workflow; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageHandlerRemoteWorkflowImpl implements MessageHandlerRemoteWorkflow { + private static final Logger logger = + LoggerFactory.getLogger(MessageHandlerRemoteWorkflowImpl.class); + private boolean keepRunning = true; + + @Override + public SampleNexusService.RunFromRemoteOutput runFromRemote( + SampleNexusService.RunFromRemoteInput input) { + Workflow.await(() -> !keepRunning); + + String logMessage = "runFromRemote was told to stop, and did."; + logger.info(logMessage); + return new SampleNexusService.RunFromRemoteOutput(logMessage); + } + + @Override + public SampleNexusService.QueryWorkflowOutput queryWorkflow( + SampleNexusService.QueryWorkflowInput name) { + logger.info("Query '{}' was received", name.getName()); + return new SampleNexusService.QueryWorkflowOutput("Query received"); + } + + @Override + public void signalWorkflow(SampleNexusService.SignalWorkflowInput name) { + logger.info("Signal was received"); + keepRunning = false; + } + + @Override + public SampleNexusService.UpdateWorkflowOutput updateWorkflow( + SampleNexusService.UpdateWorkflowInput name) { + logger.info("Update {} was received", name.getName()); + return new SampleNexusService.UpdateWorkflowOutput(10); + } + + @Override + public void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name) { + if (name.getName().equals("invalid")) { + logger.info("Update {} was rejected", name.getName()); + throw new IllegalArgumentException("Invalid update name!"); + } + logger.info("Update {} was validated", name.getName()); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java new file mode 100644 index 00000000..897e4a51 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java @@ -0,0 +1,29 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.UpdateMethod; +import io.temporal.workflow.UpdateValidatorMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +@WorkflowInterface +public interface MessageHandlerWorkflow { + + @WorkflowMethod + void run(); + + @QueryMethod + SampleNexusService.QueryWorkflowOutput queryWorkflow(SampleNexusService.QueryWorkflowInput name); + + @SignalMethod + void signalWorkflow(SampleNexusService.SignalWorkflowInput name); + + @UpdateMethod + SampleNexusService.UpdateWorkflowOutput updateWorkflow( + SampleNexusService.UpdateWorkflowInput name); + + @UpdateValidatorMethod(updateName = "updateWorkflow") + void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name); +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java new file mode 100644 index 00000000..68129be0 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java @@ -0,0 +1,47 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.temporal.samples.nexusmessaging.service.SampleNexusService; +import io.temporal.workflow.Workflow; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageHandlerWorkflowImpl implements MessageHandlerWorkflow { + private static final Logger logger = LoggerFactory.getLogger(MessageHandlerWorkflowImpl.class); + private boolean keepRunning = true; + + @Override + public void run() { + // Long-running entity workflow: stays alive to receive signals, queries, and updates via Nexus. + Workflow.await(() -> !keepRunning); + logger.info("Handler workflow stopped via signal"); + } + + @Override + public SampleNexusService.QueryWorkflowOutput queryWorkflow( + SampleNexusService.QueryWorkflowInput name) { + logger.info("Query '{}' was received", name.getName()); + return new SampleNexusService.QueryWorkflowOutput("Query received"); + } + + @Override + public void signalWorkflow(SampleNexusService.SignalWorkflowInput name) { + logger.info("Signal was received"); + keepRunning = false; + } + + @Override + public SampleNexusService.UpdateWorkflowOutput updateWorkflow( + SampleNexusService.UpdateWorkflowInput name) { + logger.info("Update {} was received", name.getName()); + return new SampleNexusService.UpdateWorkflowOutput(10); + } + + @Override + public void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name) { + if (name.getName().equals("invalid")) { + logger.info("Update {} was rejected", name.getName()); + throw new IllegalArgumentException("Invalid update name!"); + } + logger.info("Update {} was validated", name.getName()); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java new file mode 100644 index 00000000..675bf7fe --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java @@ -0,0 +1,129 @@ +package io.temporal.samples.nexusmessaging.handler; + +import io.nexusrpc.handler.OperationHandler; +import io.nexusrpc.handler.OperationImpl; +import io.nexusrpc.handler.ServiceImpl; +import io.temporal.client.WorkflowOptions; +import io.temporal.nexus.Nexus; +import io.temporal.nexus.WorkflowRunOperation; +import io.temporal.samples.nexusmessaging.service.SampleNexusService; + +@ServiceImpl(service = SampleNexusService.class) +public class SampleNexusServiceImpl { + + private final String workflowId; + + public SampleNexusServiceImpl(String workflowId) { + this.workflowId = workflowId; + } + + private MessageHandlerWorkflow getWorkflowStub() { + return Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(MessageHandlerWorkflow.class, workflowId); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.RunFromRemoteInput, SampleNexusService.RunFromRemoteOutput> + runFromRemote() { + return WorkflowRunOperation.fromWorkflowMethod( + (ctx, details, input) -> + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub( + MessageHandlerRemoteWorkflow.class, + WorkflowOptions.newBuilder().setWorkflowId(input.getWorkflowId()).build()) + ::runFromRemote); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.SignalWorkflowInput, SampleNexusService.SignalWorkflowOutput> + signalWorkflow() { + return OperationHandler.sync( + (ctx, details, input) -> { + getWorkflowStub().signalWorkflow(input); + return new SampleNexusService.SignalWorkflowOutput(); + }); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.QueryWorkflowInput, SampleNexusService.QueryWorkflowOutput> + queryWorkflow() { + return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().queryWorkflow(input)); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.UpdateWorkflowInput, SampleNexusService.UpdateWorkflowOutput> + updateWorkflow() { + return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().updateWorkflow(input)); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.QueryWorkflowRemoteStartInput, SampleNexusService.QueryWorkflowOutput> + queryWorkflowRemoteStart() { + return OperationHandler.sync( + (ctx, details, input) -> { + MessageHandlerRemoteWorkflow stub = + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(MessageHandlerRemoteWorkflow.class, input.getWorkflowId()); + return stub.queryWorkflow(new SampleNexusService.QueryWorkflowInput(input.getName())); + }); + } + + @OperationImpl + public OperationHandler< + SampleNexusService.SignalWorkflowRemoteStartInput, + SampleNexusService.SignalWorkflowOutput> + signalWorkflowRemoteStart() { + return OperationHandler.sync( + (ctx, details, input) -> { + MessageHandlerRemoteWorkflow stub = + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(MessageHandlerRemoteWorkflow.class, input.getWorkflowId()); + stub.signalWorkflow(new SampleNexusService.SignalWorkflowInput(input.getName())); + return new SampleNexusService.SignalWorkflowOutput(); + }); + } + + /* + + @OperationImpl + public OperationHandler< + SampleNexusService.SignalWorkflowInput, SampleNexusService.SignalWorkflowOutput> + signalWorkflow() { + return OperationHandler.sync( + (ctx, details, input) -> { + MessageHandlerWorkflow stub = + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(MessageHandlerWorkflow.class, input.getWorkflowId()); + stub.signalWorkflow(input.getName()); + return new SampleNexusService.SignalWorkflowOutput(); + }); + } + + + + @OperationImpl + public OperationHandler< + SampleNexusService.UpdateWorkflowInput, SampleNexusService.UpdateWorkflowOutput> + updateWorkflow() { + return OperationHandler.sync( + (ctx, details, input) -> { + MessageHandlerWorkflow stub = + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(MessageHandlerWorkflow.class, input.getWorkflowId()); + int result = stub.updateWorkflow(input.getName()); + return new SampleNexusService.UpdateWorkflowOutput(result); + }); + } + */ +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java b/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java new file mode 100644 index 00000000..60e88c0f --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java @@ -0,0 +1,137 @@ +package io.temporal.samples.nexusmessaging.options; + +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.serviceclient.WorkflowServiceStubsOptions; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import javax.net.ssl.SSLException; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +public class ClientOptions { + + public static WorkflowClient getWorkflowClient(String[] args) { + return getWorkflowClient(args, WorkflowClientOptions.newBuilder()); + } + + public static WorkflowClient getWorkflowClient( + String[] args, WorkflowClientOptions.Builder clientOptions) { + Options options = new Options(); + Option targetHostOption = new Option("target-host", true, "Host:port for the Temporal service"); + targetHostOption.setRequired(false); + options.addOption(targetHostOption); + + Option namespaceOption = new Option("namespace", true, "Namespace to connect to"); + namespaceOption.setRequired(false); + options.addOption(namespaceOption); + + Option serverRootCaOption = + new Option("server-root-ca-cert", true, "Optional path to root server CA cert"); + serverRootCaOption.setRequired(false); + options.addOption(serverRootCaOption); + + Option clientCertOption = + new Option( + "client-cert", true, "Optional path to client cert, mutually exclusive with API key"); + clientCertOption.setRequired(false); + options.addOption(clientCertOption); + + Option clientKeyOption = + new Option( + "client-key", true, "Optional path to client key, mutually exclusive with API key"); + clientKeyOption.setRequired(false); + options.addOption(clientKeyOption); + + Option apiKeyOption = + new Option("api-key", true, "Optional API key, mutually exclusive with cert/key"); + apiKeyOption.setRequired(false); + options.addOption(apiKeyOption); + + Option serverNameOption = + new Option( + "server-name", true, "Server name to use for verifying the server's certificate"); + serverNameOption.setRequired(false); + options.addOption(serverNameOption); + + Option insercureSkipVerifyOption = + new Option( + "insecure-skip-verify", + false, + "Skip verification of the server's certificate and host name"); + insercureSkipVerifyOption.setRequired(false); + options.addOption(insercureSkipVerifyOption); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("utility-name", options); + + System.exit(1); + } + + String targetHost = cmd.getOptionValue("target-host", "localhost:7233"); + String namespace = cmd.getOptionValue("namespace", "default"); + String serverRootCaCert = cmd.getOptionValue("server-root-ca-cert", ""); + String clientCert = cmd.getOptionValue("client-cert", ""); + String clientKey = cmd.getOptionValue("client-key", ""); + String serverName = cmd.getOptionValue("server-name", ""); + boolean insecureSkipVerify = cmd.hasOption("insecure-skip-verify"); + String apiKey = cmd.getOptionValue("api-key", ""); + + // API key and client cert/key are mutually exclusive + if (!apiKey.isEmpty() && (!clientCert.isEmpty() || !clientKey.isEmpty())) { + throw new IllegalArgumentException("API key and client cert/key are mutually exclusive"); + } + WorkflowServiceStubsOptions.Builder serviceStubOptionsBuilder = + WorkflowServiceStubsOptions.newBuilder().setTarget(targetHost); + // Configure TLS if client cert and key are provided + if (!clientCert.isEmpty() || !clientKey.isEmpty()) { + if (clientCert.isEmpty() || clientKey.isEmpty()) { + throw new IllegalArgumentException("Both client-cert and client-key must be provided"); + } + try { + SslContextBuilder sslContext = + SslContextBuilder.forClient() + .keyManager(new FileInputStream(clientCert), new FileInputStream(clientKey)); + if (serverRootCaCert != null && !serverRootCaCert.isEmpty()) { + sslContext.trustManager(new FileInputStream(serverRootCaCert)); + } + if (insecureSkipVerify) { + sslContext.trustManager(InsecureTrustManagerFactory.INSTANCE); + } + serviceStubOptionsBuilder.setSslContext(GrpcSslContexts.configure(sslContext).build()); + } catch (SSLException e) { + throw new RuntimeException(e); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + if (serverName != null && !serverName.isEmpty()) { + serviceStubOptionsBuilder.setChannelInitializer(c -> c.overrideAuthority(serverName)); + } + } + // Configure API key if provided + if (!apiKey.isEmpty()) { + serviceStubOptionsBuilder.setEnableHttps(true); + serviceStubOptionsBuilder.addApiKey(() -> apiKey); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(serviceStubOptionsBuilder.build()); + return WorkflowClient.newInstance(service, clientOptions.setNamespace(namespace).build()); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java b/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java new file mode 100644 index 00000000..edb72864 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java @@ -0,0 +1,177 @@ +package io.temporal.samples.nexusmessaging.service; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.nexusrpc.Operation; +import io.nexusrpc.Service; + +@Service +public interface SampleNexusService { + + class SignalWorkflowInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SignalWorkflowInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + class SignalWorkflowOutput { + @JsonCreator + public SignalWorkflowOutput() {} + } + + class QueryWorkflowInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public QueryWorkflowInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + class QueryWorkflowRemoteStartInput { + private final String name; + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public QueryWorkflowRemoteStartInput( + @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { + this.name = name; + this.workflowId = workflowId; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + class QueryWorkflowOutput { + private final String message; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public QueryWorkflowOutput(@JsonProperty("message") String message) { + this.message = message; + } + + @JsonProperty("message") + public String getMessage() { + return message; + } + } + + class UpdateWorkflowInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public UpdateWorkflowInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + class UpdateWorkflowOutput { + private final int result; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public UpdateWorkflowOutput(@JsonProperty("result") int result) { + this.result = result; + } + + @JsonProperty("result") + public int getResult() { + return result; + } + } + + class RunFromRemoteInput { + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public RunFromRemoteInput(@JsonProperty("workflowId") String workflowId) { + this.workflowId = workflowId; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + class RunFromRemoteOutput { + private final String message; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public RunFromRemoteOutput(@JsonProperty("message") String message) { + this.message = message; + } + + @JsonProperty("message") + public String getMessage() { + return message; + } + } + + @Operation + RunFromRemoteOutput runFromRemote(RunFromRemoteInput input); + + class SignalWorkflowRemoteStartInput { + private final String name; + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SignalWorkflowRemoteStartInput( + @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { + this.name = name; + this.workflowId = workflowId; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + @Operation + SignalWorkflowOutput signalWorkflow(SignalWorkflowInput input); + + @Operation + SignalWorkflowOutput signalWorkflowRemoteStart(SignalWorkflowRemoteStartInput input); + + @Operation + UpdateWorkflowOutput updateWorkflow(UpdateWorkflowInput input); + + @Operation + QueryWorkflowOutput queryWorkflow(QueryWorkflowInput input); + + @Operation + QueryWorkflowOutput queryWorkflowRemoteStart(QueryWorkflowRemoteStartInput input); +} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md b/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md new file mode 100644 index 00000000..0d607d9a --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md @@ -0,0 +1,6 @@ +## Service: [SampleNexusService](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java) + - operation: `signalWorkflow` + - operation: `queryWorkflow` + - operation: `updateWorkflow` + +See https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java for Input / Output types. diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + From 12ae22fdef59983fedfbb26cbe7d9b36808e99e6 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 14:17:21 -0700 Subject: [PATCH 02/10] Updating --- .../samples/nexus_sync_operations/README.md | 121 ++++++++++++---- .../caller/CallerWorker.java | 2 +- .../caller/CallerWorkflowImpl.java | 17 +-- .../caller_remote/CallerRemoteStarter.java | 30 ++++ .../caller_remote/CallerRemoteWorker.java | 42 ++++++ .../caller_remote/CallerRemoteWorkflow.java | 11 ++ .../CallerRemoteWorkflowImpl.java | 74 ++++++++++ .../handler/GreetingWorkflow.java | 17 +-- .../handler/GreetingWorkflowImpl.java | 13 +- .../handler/HandlerWorker.java | 3 +- ...mpl.java => NexusGreetingServiceImpl.java} | 42 ++++-- .../NexusRemoteGreetingServiceImpl.java | 99 +++++++++++++ ...Service.java => NexusGreetingService.java} | 2 +- .../service/NexusRemoteGreetingService.java | 134 ++++++++++++++++++ node_modules/.yarn-integrity | 10 ++ 15 files changed, 549 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java rename core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/{GreetingServiceImpl.java => NexusGreetingServiceImpl.java} (51%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java rename core/src/main/java/io/temporal/samples/nexus_sync_operations/service/{GreetingService.java => NexusGreetingService.java} (98%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java create mode 100644 node_modules/.yarn-integrity diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md b/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md index 5d76d1fe..15dcf21a 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md @@ -1,29 +1,30 @@ -This sample shows how to create a Nexus service that is backed by a long-running workflow and -exposes operations that execute updates and queries against that workflow. The long-running -workflow, and the updates/queries, are private implementation details of the Nexus service: the -caller does not know how the operations are implemented. +This sample shows how to expose a long-running workflow's queries, updates, and signals as Nexus +operations. The caller interacts only with the Nexus service; the workflow is a private +implementation detail. -This is a Java port of the -[nexus_sync_operations Python sample](https://github.com/temporalio/samples-python/tree/main/nexus_sync_operations). +There are **two caller patterns** that share the same handler workflow (`GreetingWorkflow`): -### Sample directory structure +| | `caller/` (entity pattern) | `caller_remote/` (remote-start pattern) | +|---|---|---| +| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a `runFromRemote` Nexus operation | +| **Who knows the workflow ID?** | Only the handler | The caller chooses and passes it in every operation | +| **Nexus service** | `NexusGreetingService` — inputs carry only business data | `NexusRemoteGreetingService` — every input includes a `workflowId` | +| **When to use** | Single shared entity; callers don't need lifecycle control | Caller needs to create and target specific workflow instances | -- [`service/GreetingService.java`](./service/GreetingService.java) — shared Nexus service definition with input/output types -- [`service/Language.java`](./service/Language.java) — shared language enum -- [`handler/`](./handler/) — Nexus operation handlers, the long-running entity workflow they back, an activity, and a handler worker -- [`caller/`](./caller/) — a caller workflow that executes Nexus operations, together with a worker and starter +### Directory structure -### Instructions +- `service/` — shared Nexus service definitions (`NexusGreetingService`, `NexusRemoteGreetingService`) and `Language` enum +- `handler/` — `GreetingWorkflow` and its implementation, `GreetingActivity`, both Nexus service impls (`NexusGreetingServiceImpl`, `NexusRemoteGreetingServiceImpl`), and the handler worker +- `caller/` — entity-pattern caller (workflow, worker, starter) +- `caller_remote/` — remote-start caller (workflow, worker, starter) -Start a Temporal server: +### Running + +Start a Temporal server and create namespaces/endpoint: ```bash temporal server start-dev -``` -Create the caller and handler namespaces and the Nexus endpoint: - -```bash temporal operator namespace create --namespace nexus-sync-operations-handler-namespace temporal operator namespace create --namespace nexus-sync-operations-caller-namespace @@ -33,13 +34,14 @@ temporal operator nexus endpoint create \ --target-task-queue nexus-sync-operations-handler-task-queue ``` -In one terminal, run the handler worker (starts the long-running entity workflow and polls for -Nexus, workflow, and activity tasks): +In one terminal, start the handler worker (shared by both patterns): ```bash ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.handler.HandlerWorker ``` +#### Entity pattern + In a second terminal, run the caller worker: ```bash @@ -52,25 +54,82 @@ In a third terminal, start the caller workflow: ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerStarter ``` -You should see output like: +Expected output: ``` supported languages: [CHINESE, ENGLISH] language changed: ENGLISH -> ARABIC +workflow approved +``` + +#### Remote-start pattern + +In a second terminal, run the remote caller worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteWorker +``` + +In a third terminal, start the remote caller workflow: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteStarter +``` + +Expected output: + +``` +started remote greeting workflow: nexus-sync-operations-remote-greeting-workflow +supported languages: [CHINESE, ENGLISH] +language changed: ENGLISH -> ARABIC +workflow approved +workflow result: مرحبا بالعالم ``` ### How it works -The handler starts a single long-running `GreetingWorkflow` entity workflow when the worker boots. -This workflow holds the current language and a map of greetings, and exposes: +#### The handler (shared by both patterns) + +`GreetingWorkflow` is a long-running "entity" workflow that holds the current language and a map of +greetings. It exposes its state through standard Temporal primitives: + +- `getLanguages` / `getLanguage` — `@QueryMethod`s for reading state +- `setLanguage` — an `@UpdateMethod` for switching between already-loaded languages +- `setLanguageUsingActivity` — an `@UpdateMethod` that calls an activity to fetch a greeting for + a language not yet in the map (uses `WorkflowLock` to serialize concurrent activity calls) +- `approve` — a `@SignalMethod` that lets the workflow complete + +The workflow waits until approved and all in-flight update handlers have finished, then returns the +greeting in the current language. + +Both Nexus service implementations translate incoming Nexus operations into calls against +`GreetingWorkflow` stubs — queries, updates, and signals. The caller never interacts with the +workflow directly. + +#### Entity pattern (`caller/` + `NexusGreetingService`) + +The handler worker starts a single `GreetingWorkflow` on boot with a fixed workflow ID. +`NexusGreetingServiceImpl` holds that workflow ID in its constructor and routes every operation to +it. The caller's inputs contain only business data (language, name), not workflow IDs. + +`CallerWorkflowImpl` creates a `NexusGreetingService` stub and: +1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`) +2. Changes the language to Arabic (`setLanguage` — backed by an `@UpdateMethod` that calls an activity) +3. Confirms the change via a second query (`getLanguage`) +4. Approves the workflow (`approve` — backed by a `@SignalMethod`) + +#### Remote-start pattern (`caller_remote/` + `NexusRemoteGreetingService`) + +No workflow is pre-started. Instead, `NexusRemoteGreetingService` adds a `runFromRemote` operation +that starts a new `GreetingWorkflow` with a caller-chosen workflow ID using +`WorkflowRunOperation`. Every other operation also includes the `workflowId` in its input so that +`NexusRemoteGreetingServiceImpl` can look up the right workflow stub. -- `getLanguages` — a `@QueryMethod` listing supported languages -- `getLanguage` — a `@QueryMethod` returning the current language -- `setLanguage` — an `@UpdateMethod` (sync) for switching between already-loaded languages -- `setLanguageUsingActivity` — an `@UpdateMethod` (async) that calls an activity to fetch a - greeting for a new language before switching -- `approve` — a `@SignalMethod` that allows the workflow to complete +`CallerRemoteWorkflowImpl` creates a `NexusRemoteGreetingService` stub and: +1. Starts a remote `GreetingWorkflow` via `runFromRemote` and waits for it to be running +2. Queries, updates, and approves that workflow — same operations as the entity pattern, but each + input carries the workflow ID +3. Waits for the remote workflow to complete and returns its result (the greeting string) -The three `GreetingService` Nexus operations delegate to these workflow handlers via the Temporal -client inside each `OperationHandler.sync` implementation. The caller workflow sees only the Nexus -operations; the entity workflow is a private implementation detail. +This pattern is useful when the caller needs to control the lifecycle of individual workflow +instances rather than sharing a single entity. diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java index 284bf9bd..083e7b14 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws InterruptedException { WorkflowImplementationOptions.newBuilder() .setNexusServiceOptions( Collections.singletonMap( - "GreetingService", + "NexusGreetingService", NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build())) .build(), CallerWorkflowImpl.class); diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java index 82e96c05..e639f54e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java @@ -1,8 +1,8 @@ package io.temporal.samples.nexus_sync_operations.caller; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_sync_operations.service.GreetingService; import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; import io.temporal.workflow.Workflow; @@ -14,9 +14,9 @@ public class CallerWorkflowImpl implements CallerWorkflow { // The endpoint is configured at the worker level in CallerWorker; only operation options are // set here. - GreetingService greetingService = + NexusGreetingService greetingService = Workflow.newNexusServiceStub( - GreetingService.class, + NexusGreetingService.class, NexusServiceOptions.newBuilder() .setOperationOptions( NexusOperationOptions.newBuilder() @@ -29,16 +29,17 @@ public List run() { List log = new ArrayList<>(); // 👉 Call a Nexus operation backed by a query against the entity workflow. - GreetingService.GetLanguagesOutput languagesOutput = - greetingService.getLanguages(new GreetingService.GetLanguagesInput(false)); + NexusGreetingService.GetLanguagesOutput languagesOutput = + greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false)); log.add("supported languages: " + languagesOutput.getLanguages()); // 👉 Call a Nexus operation backed by an update against the entity workflow. Language previousLanguage = - greetingService.setLanguage(new GreetingService.SetLanguageInput(Language.ARABIC)); + greetingService.setLanguage(new NexusGreetingService.SetLanguageInput(Language.ARABIC)); // 👉 Call a Nexus operation backed by a query to confirm the language change. - Language currentLanguage = greetingService.getLanguage(new GreetingService.GetLanguageInput()); + Language currentLanguage = + greetingService.getLanguage(new NexusGreetingService.GetLanguageInput()); if (currentLanguage != Language.ARABIC) { throw ApplicationFailure.newFailure( "expected language ARABIC, got " + currentLanguage, "AssertionError"); @@ -47,7 +48,7 @@ public List run() { log.add("language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name()); // 👉 Call a Nexus operation backed by a signal against the entity workflow. - greetingService.approve(new GreetingService.ApproveInput("caller")); + greetingService.approve(new NexusGreetingService.ApproveInput("caller")); log.add("workflow approved"); return log; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java new file mode 100644 index 00000000..4ca946ba --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java @@ -0,0 +1,30 @@ +package io.temporal.samples.nexus_sync_operations.caller_remote; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import java.util.List; +import java.util.UUID; + +public class CallerRemoteStarter { + + public static void main(String[] args) { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, + WorkflowClientOptions.newBuilder().setNamespace(CallerRemoteWorker.NAMESPACE).build()); + + CallerRemoteWorkflow workflow = + client.newWorkflowStub( + CallerRemoteWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId("nexus-sync-operations-remote-caller-" + UUID.randomUUID()) + .setTaskQueue(CallerRemoteWorker.TASK_QUEUE) + .build()); + + List log = workflow.run(); + log.forEach(System.out::println); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java new file mode 100644 index 00000000..32073476 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java @@ -0,0 +1,42 @@ +package io.temporal.samples.nexus_sync_operations.caller_remote; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.NexusServiceOptions; +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallerRemoteWorker { + private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorker.class); + + public static final String NAMESPACE = "nexus-sync-operations-caller-namespace"; + public static final String TASK_QUEUE = "nexus-sync-operations-caller-remote-task-queue"; + static final String NEXUS_ENDPOINT = "nexus-sync-operations-nexus-endpoint"; + + public static void main(String[] args) throws InterruptedException { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + worker.registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setNexusServiceOptions( + Collections.singletonMap( + "NexusRemoteGreetingService", + NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build())) + .build(), + CallerRemoteWorkflowImpl.class); + + factory.start(); + logger.info("Caller remote worker started, ctrl+c to exit"); + Thread.currentThread().join(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java new file mode 100644 index 00000000..b6e49bf7 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java @@ -0,0 +1,11 @@ +package io.temporal.samples.nexus_sync_operations.caller_remote; + +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; +import java.util.List; + +@WorkflowInterface +public interface CallerRemoteWorkflow { + @WorkflowMethod + List run(); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java new file mode 100644 index 00000000..08f29bd8 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java @@ -0,0 +1,74 @@ +package io.temporal.samples.nexus_sync_operations.caller_remote; + +import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_sync_operations.service.NexusRemoteGreetingService; +import io.temporal.workflow.NexusOperationHandle; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { + + private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class); + + private static final String REMOTE_WORKFLOW_ID = "nexus-sync-operations-remote-greeting-workflow"; + + NexusRemoteGreetingService greetingRemoteService = + Workflow.newNexusServiceStub( + NexusRemoteGreetingService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public List run() { + List log = new ArrayList<>(); + + // Start a new GreetingWorkflow on the handler side via Nexus. + NexusOperationHandle handle = + Workflow.startNexusOperation( + greetingRemoteService::runFromRemote, + new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_ID)); + // Wait for the operation to be started (workflow is now running on the handler). + handle.getExecution().get(); + log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_ID); + + // Query the remote workflow for supported languages. + NexusGreetingService.GetLanguagesOutput languagesOutput = + greetingRemoteService.getLanguages( + new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ID)); + log.add("supported languages: " + languagesOutput.getLanguages()); + + // Update the language on the remote workflow. + Language previousLanguage = + greetingRemoteService.setLanguage( + new NexusRemoteGreetingService.SetLanguageInput(Language.ARABIC, REMOTE_WORKFLOW_ID)); + logger.info("Language changed from {}", previousLanguage); + + // Confirm the change by querying. + Language currentLanguage = + greetingRemoteService.getLanguage( + new NexusRemoteGreetingService.GetLanguageInput(REMOTE_WORKFLOW_ID)); + log.add("language changed: " + previousLanguage.name() + " -> " + currentLanguage.name()); + + // Approve the remote workflow so it can complete. + greetingRemoteService.approve( + new NexusRemoteGreetingService.ApproveInput("remote-caller", REMOTE_WORKFLOW_ID)); + log.add("workflow approved"); + + // Wait for the remote workflow to finish and return its result. + String result = handle.getResult().get(); + log.add("workflow result: " + result); + + return log; + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java index 05b46828..8604c47d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java @@ -2,8 +2,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.temporal.samples.nexus_sync_operations.service.GreetingService; import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; import io.temporal.workflow.QueryMethod; import io.temporal.workflow.SignalMethod; import io.temporal.workflow.UpdateMethod; @@ -12,9 +12,9 @@ import io.temporal.workflow.WorkflowMethod; /** - * A long-running "entity" workflow that backs the GreetingService Nexus operations. The workflow - * exposes queries, an update, and a signal. These are private implementation details of the Nexus - * service: the caller only interacts via Nexus operations. + * A long-running "entity" workflow that backs the NexusGreetingService Nexus operations. The + * workflow exposes queries, an update, and a signal. These are private implementation details of + * the Nexus service: the caller only interacts via Nexus operations. */ @WorkflowInterface public interface GreetingWorkflow { @@ -38,7 +38,8 @@ public String getName() { // Returns the languages currently supported by the workflow. @QueryMethod - GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLanguagesInput input); + NexusGreetingService.GetLanguagesOutput getLanguages( + NexusGreetingService.GetLanguagesInput input); // Returns the currently active language. @QueryMethod @@ -51,12 +52,12 @@ public String getName() { // Changes the active language synchronously (only supports languages already in the greetings // map). @UpdateMethod - Language setLanguage(GreetingService.SetLanguageInput input); + Language setLanguage(NexusGreetingService.SetLanguageInput input); @UpdateValidatorMethod(updateName = "setLanguage") - void validateSetLanguage(GreetingService.SetLanguageInput input); + void validateSetLanguage(NexusGreetingService.SetLanguageInput input); // Changes the active language, calling an activity to fetch a greeting for new languages. @UpdateMethod - Language setLanguageUsingActivity(GreetingService.SetLanguageInput input); + Language setLanguageUsingActivity(NexusGreetingService.SetLanguageInput input); } diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java index 1f1e6af8..22d66c45 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java @@ -2,8 +2,8 @@ import io.temporal.activity.ActivityOptions; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_sync_operations.service.GreetingService; import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; import io.temporal.workflow.Workflow; import io.temporal.workflow.WorkflowLock; import java.time.Duration; @@ -46,7 +46,8 @@ public String run() { } @Override - public GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLanguagesInput input) { + public NexusGreetingService.GetLanguagesOutput getLanguages( + NexusGreetingService.GetLanguagesInput input) { List result; if (input.isIncludeUnsupported()) { result = new ArrayList<>(Arrays.asList(Language.values())); @@ -54,7 +55,7 @@ public GreetingService.GetLanguagesOutput getLanguages(GreetingService.GetLangua result = new ArrayList<>(greetings.keySet()); } Collections.sort(result); - return new GreetingService.GetLanguagesOutput(result); + return new NexusGreetingService.GetLanguagesOutput(result); } @Override @@ -69,7 +70,7 @@ public void approve(ApproveInput input) { } @Override - public Language setLanguage(GreetingService.SetLanguageInput input) { + public Language setLanguage(NexusGreetingService.SetLanguageInput input) { logger.info("setLanguage update received"); Language previous = language; language = input.getLanguage(); @@ -77,7 +78,7 @@ public Language setLanguage(GreetingService.SetLanguageInput input) { } @Override - public void validateSetLanguage(GreetingService.SetLanguageInput input) { + public void validateSetLanguage(NexusGreetingService.SetLanguageInput input) { logger.info("validateSetLanguage called"); if (!greetings.containsKey(input.getLanguage())) { throw new IllegalArgumentException(input.getLanguage().name() + " is not supported"); @@ -85,7 +86,7 @@ public void validateSetLanguage(GreetingService.SetLanguageInput input) { } @Override - public Language setLanguageUsingActivity(GreetingService.SetLanguageInput input) { + public Language setLanguageUsingActivity(NexusGreetingService.SetLanguageInput input) { if (!greetings.containsKey(input.getLanguage())) { // Use a lock so that if this handler is called concurrently, each call executes its activity // only after the previous one has completed. This ensures updates are processed in order. diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java index 05851047..85dbc6f1 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java @@ -42,7 +42,8 @@ public static void main(String[] args) throws InterruptedException { Worker worker = factory.newWorker(TASK_QUEUE); worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); worker.registerActivitiesImplementations(new GreetingActivityImpl()); - worker.registerNexusServiceImplementation(new GreetingServiceImpl(WORKFLOW_ID)); + worker.registerNexusServiceImplementation(new NexusGreetingServiceImpl(WORKFLOW_ID)); + worker.registerNexusServiceImplementation(new NexusRemoteGreetingServiceImpl()); factory.start(); logger.info("Handler worker started, ctrl+c to exit"); diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java similarity index 51% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java index 726ae5b2..f88839f3 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java @@ -4,20 +4,24 @@ import io.nexusrpc.handler.OperationImpl; import io.nexusrpc.handler.ServiceImpl; import io.temporal.nexus.Nexus; -import io.temporal.samples.nexus_sync_operations.service.GreetingService; import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Nexus operation handler implementation. Each operation is backed by the long-running * GreetingWorkflow entity. The operations are synchronous (sync_operation) because queries and * updates against a running workflow complete quickly. */ -@ServiceImpl(service = GreetingService.class) -public class GreetingServiceImpl { +@ServiceImpl(service = NexusGreetingService.class) +public class NexusGreetingServiceImpl { + + private static final Logger logger = LoggerFactory.getLogger(NexusGreetingServiceImpl.class); private final String workflowId; - public GreetingServiceImpl(String workflowId) { + public NexusGreetingServiceImpl(String workflowId) { this.workflowId = workflowId; } @@ -29,33 +33,47 @@ private GreetingWorkflow getWorkflowStub() { // 👉 Backed by a query against the long-running entity workflow. @OperationImpl - public OperationHandler + public OperationHandler< + NexusGreetingService.GetLanguagesInput, NexusGreetingService.GetLanguagesOutput> getLanguages() { - return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguages(input)); + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Query for GetLanguages was received"); + return getWorkflowStub().getLanguages(input); + }); } // 👉 Backed by a query against the long-running entity workflow. @OperationImpl - public OperationHandler getLanguage() { - return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().getLanguage()); + public OperationHandler getLanguage() { + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Query for GetLanguage was received"); + return getWorkflowStub().getLanguage(); + }); } // 👉 Backed by an update against the long-running entity workflow. Although updates can run for // an arbitrarily long time, when exposed via a sync Nexus operation the update should complete // quickly (sync operations must finish in under 10s). @OperationImpl - public OperationHandler setLanguage() { + public OperationHandler setLanguage() { return OperationHandler.sync( - (ctx, details, input) -> getWorkflowStub().setLanguageUsingActivity(input)); + (ctx, details, input) -> { + logger.info("Update for SetLanguage was received"); + return getWorkflowStub().setLanguageUsingActivity(input); + }); } // 👉 Backed by a signal against the long-running entity workflow. @OperationImpl - public OperationHandler approve() { + public OperationHandler + approve() { return OperationHandler.sync( (ctx, details, input) -> { + logger.info("Signal for Approve was received"); getWorkflowStub().approve(new GreetingWorkflow.ApproveInput(input.getName())); - return new GreetingService.ApproveOutput(); + return new NexusGreetingService.ApproveOutput(); }); } } diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java new file mode 100644 index 00000000..0358f58d --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java @@ -0,0 +1,99 @@ +package io.temporal.samples.nexus_sync_operations.handler; + +import io.nexusrpc.handler.OperationHandler; +import io.nexusrpc.handler.OperationImpl; +import io.nexusrpc.handler.ServiceImpl; +import io.temporal.client.WorkflowOptions; +import io.temporal.nexus.Nexus; +import io.temporal.nexus.WorkflowHandle; +import io.temporal.nexus.WorkflowRunOperation; +import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_sync_operations.service.NexusRemoteGreetingService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Nexus operation handler for the remote-start pattern. Unlike {@link NexusGreetingServiceImpl}, + * this implementation does not hold a fixed workflow ID. Instead, each operation receives the + * target workflow ID in its input, and {@code runFromRemote} starts a brand-new GreetingWorkflow. + */ +@ServiceImpl(service = NexusRemoteGreetingService.class) +public class NexusRemoteGreetingServiceImpl { + + private static final Logger logger = + LoggerFactory.getLogger(NexusRemoteGreetingServiceImpl.class); + + private GreetingWorkflow getWorkflowStub(String workflowId) { + return Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub(GreetingWorkflow.class, workflowId); + } + + // Starts a new GreetingWorkflow with the caller-specified workflow ID. This is an async + // Nexus operation backed by WorkflowRunOperation. + @OperationImpl + public OperationHandler runFromRemote() { + return WorkflowRunOperation.fromWorkflowHandle( + (ctx, details, input) -> { + logger.info("RunFromRemote was received for workflow {}", input.getWorkflowId()); + return WorkflowHandle.fromWorkflowMethod( + Nexus.getOperationContext() + .getWorkflowClient() + .newWorkflowStub( + GreetingWorkflow.class, + WorkflowOptions.newBuilder() + .setWorkflowId(input.getWorkflowId()) + .setTaskQueue(HandlerWorker.TASK_QUEUE) + .build()) + ::run); + }); + } + + @OperationImpl + public OperationHandler< + NexusRemoteGreetingService.GetLanguagesInput, NexusGreetingService.GetLanguagesOutput> + getLanguages() { + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Query for GetLanguages was received for workflow {}", input.getWorkflowId()); + return getWorkflowStub(input.getWorkflowId()) + .getLanguages( + new NexusGreetingService.GetLanguagesInput(input.isIncludeUnsupported())); + }); + } + + @OperationImpl + public OperationHandler getLanguage() { + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Query for GetLanguage was received for workflow {}", input.getWorkflowId()); + return getWorkflowStub(input.getWorkflowId()).getLanguage(); + }); + } + + // Uses setLanguageUsingActivity so that new languages are fetched via an activity. + @OperationImpl + public OperationHandler setLanguage() { + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Update for SetLanguage was received for workflow {}", input.getWorkflowId()); + return getWorkflowStub(input.getWorkflowId()) + .setLanguageUsingActivity( + new NexusGreetingService.SetLanguageInput(input.getLanguage())); + }); + } + + @OperationImpl + public OperationHandler< + NexusRemoteGreetingService.ApproveInput, NexusGreetingService.ApproveOutput> + approve() { + return OperationHandler.sync( + (ctx, details, input) -> { + logger.info("Signal for Approve was received for workflow {}", input.getWorkflowId()); + getWorkflowStub(input.getWorkflowId()) + .approve(new GreetingWorkflow.ApproveInput(input.getName())); + return new NexusGreetingService.ApproveOutput(); + }); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java similarity index 98% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java rename to core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java index 407d309d..69f1d853 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/GreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java @@ -12,7 +12,7 @@ * type-safe Nexus client stub; the handler implements the operations. */ @Service -public interface GreetingService { +public interface NexusGreetingService { class GetLanguagesInput { private final boolean includeUnsupported; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java new file mode 100644 index 00000000..4841ae0a --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java @@ -0,0 +1,134 @@ +package io.temporal.samples.nexus_sync_operations.service; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.nexusrpc.Operation; +import io.nexusrpc.Service; + +/** + * Nexus service definition for the remote-start pattern. Unlike {@link NexusGreetingService}, every + * operation includes a {@code workflowId} so the caller controls which workflow instance is + * targeted. This also exposes a {@code runFromRemote} operation that starts a new GreetingWorkflow. + */ +@Service +public interface NexusRemoteGreetingService { + + class RunFromRemoteInput { + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public RunFromRemoteInput(@JsonProperty("workflowId") String workflowId) { + this.workflowId = workflowId; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + class GetLanguagesInput { + private final boolean includeUnsupported; + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguagesInput( + @JsonProperty("includeUnsupported") boolean includeUnsupported, + @JsonProperty("workflowId") String workflowId) { + this.includeUnsupported = includeUnsupported; + this.workflowId = workflowId; + } + + @JsonProperty("includeUnsupported") + public boolean isIncludeUnsupported() { + return includeUnsupported; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + class GetLanguageInput { + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguageInput(@JsonProperty("workflowId") String workflowId) { + this.workflowId = workflowId; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + class SetLanguageInput { + private final Language language; + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SetLanguageInput( + @JsonProperty("language") Language language, + @JsonProperty("workflowId") String workflowId) { + this.language = language; + this.workflowId = workflowId; + } + + @JsonProperty("language") + public Language getLanguage() { + return language; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + class ApproveInput { + private final String name; + private final String workflowId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public ApproveInput( + @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { + this.name = name; + this.workflowId = workflowId; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("workflowId") + public String getWorkflowId() { + return workflowId; + } + } + + // Starts a new GreetingWorkflow with the given workflow ID. This is an asynchronous Nexus + // operation: the caller receives a handle and can wait for the workflow to complete. + @Operation + String runFromRemote(RunFromRemoteInput input); + + // Returns the languages supported by the specified workflow. + @Operation + NexusGreetingService.GetLanguagesOutput getLanguages(GetLanguagesInput input); + + // Returns the currently active language of the specified workflow. + @Operation + Language getLanguage(GetLanguageInput input); + + // Changes the active language on the specified workflow, returning the previous one. + @Operation + Language setLanguage(SetLanguageInput input); + + // Approves the specified workflow, allowing it to complete. + @Operation + NexusGreetingService.ApproveOutput approve(ApproveInput input); +} diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity new file mode 100644 index 00000000..044a5ddd --- /dev/null +++ b/node_modules/.yarn-integrity @@ -0,0 +1,10 @@ +{ + "systemParams": "darwin-arm64-115", + "modulesFolders": [], + "flags": [], + "linkedModules": [], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} \ No newline at end of file From 85bec3e928e87a4752bdfce77714f685515859f2 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 14:18:17 -0700 Subject: [PATCH 03/10] Moving packages --- .../README.md | 0 .../caller/CallerStarter.java | 2 +- .../caller/CallerWorker.java | 2 +- .../caller/CallerWorkflow.java | 2 +- .../caller/CallerWorkflowImpl.java | 6 +- .../caller_remote/CallerRemoteStarter.java | 2 +- .../caller_remote/CallerRemoteWorker.java | 2 +- .../caller_remote/CallerRemoteWorkflow.java | 2 +- .../CallerRemoteWorkflowImpl.java | 8 +- .../handler/GreetingActivity.java | 4 +- .../handler/GreetingActivityImpl.java | 4 +- .../handler/GreetingWorkflow.java | 6 +- .../handler/GreetingWorkflowImpl.java | 6 +- .../handler/HandlerWorker.java | 2 +- .../handler/NexusGreetingServiceImpl.java | 6 +- .../NexusRemoteGreetingServiceImpl.java | 8 +- .../service/Language.java | 2 +- .../service/NexusGreetingService.java | 2 +- .../service/NexusRemoteGreetingService.java | 2 +- .../temporal/samples/nexusmessaging/README.MD | 102 ---------- .../nexusmessaging/caller/CallerStarter.java | 32 ---- .../nexusmessaging/caller/CallerWorker.java | 32 ---- .../MessageCallerStartHandlerWorkflow.java | 10 - ...MessageCallerStartHandlerWorkflowImpl.java | 69 ------- .../caller/MessageCallerWorkflow.java | 10 - .../caller/MessageCallerWorkflowImpl.java | 42 ----- .../nexusmessaging/handler/HandlerWorker.java | 62 ------ .../handler/MessageHandlerRemoteWorkflow.java | 29 --- .../MessageHandlerRemoteWorkflowImpl.java | 51 ----- .../handler/MessageHandlerWorkflow.java | 29 --- .../handler/MessageHandlerWorkflowImpl.java | 47 ----- .../handler/SampleNexusServiceImpl.java | 129 ------------- .../nexusmessaging/options/ClientOptions.java | 137 -------------- .../service/SampleNexusService.java | 177 ------------------ .../nexusmessaging/service/description.md | 6 - 35 files changed, 34 insertions(+), 998 deletions(-) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/README.md (100%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller/CallerStarter.java (94%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller/CallerWorker.java (96%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller/CallerWorkflow.java (78%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller/CallerWorkflowImpl.java (91%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller_remote/CallerRemoteStarter.java (93%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller_remote/CallerRemoteWorker.java (96%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller_remote/CallerRemoteWorkflow.java (76%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/caller_remote/CallerRemoteWorkflowImpl.java (91%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/GreetingActivity.java (71%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/GreetingActivityImpl.java (85%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/GreetingWorkflow.java (90%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/GreetingWorkflowImpl.java (94%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/HandlerWorker.java (97%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/NexusGreetingServiceImpl.java (93%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/handler/NexusRemoteGreetingServiceImpl.java (93%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/service/Language.java (63%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/service/NexusGreetingService.java (97%) rename core/src/main/java/io/temporal/samples/{nexus_sync_operations => nexus_messaging}/service/NexusRemoteGreetingService.java (98%) delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/README.MD delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java delete mode 100644 core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md similarity index 100% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/README.md rename to core/src/main/java/io/temporal/samples/nexus_messaging/README.md diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java similarity index 94% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java index 48c3758d..bb4163da 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller; +package io.temporal.samples.nexus_messaging.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java index 083e7b14..1ac68d76 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller; +package io.temporal.samples.nexus_messaging.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java similarity index 78% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java index 1061f09a..6c830d7d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller; +package io.temporal.samples.nexus_messaging.caller; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java similarity index 91% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java index e639f54e..5a37caf0 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_sync_operations.caller; +package io.temporal.samples.nexus_messaging.caller; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; import io.temporal.workflow.Workflow; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java index 4ca946ba..c255f288 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller_remote; +package io.temporal.samples.nexus_messaging.caller_remote; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java index 32073476..1e89c59b 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller_remote; +package io.temporal.samples.nexus_messaging.caller_remote; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java similarity index 76% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java index b6e49bf7..bd72d4ac 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.caller_remote; +package io.temporal.samples.nexus_messaging.caller_remote; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java similarity index 91% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java index 08f29bd8..8477c10e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/caller_remote/CallerRemoteWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_sync_operations.caller_remote; +package io.temporal.samples.nexus_messaging.caller_remote; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; -import io.temporal.samples.nexus_sync_operations.service.NexusRemoteGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; import io.temporal.workflow.NexusOperationHandle; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java similarity index 71% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java index ed150780..83f97d1f 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivity.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; -import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_messaging.service.Language; @ActivityInterface public interface GreetingActivity { diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java similarity index 85% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java index b7ab336f..e7927eba 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingActivityImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java @@ -1,6 +1,6 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; -import io.temporal.samples.nexus_sync_operations.service.Language; +import io.temporal.samples.nexus_messaging.service.Language; import java.util.EnumMap; import java.util.Map; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java similarity index 90% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java index 8604c47d..786309f3 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.QueryMethod; import io.temporal.workflow.SignalMethod; import io.temporal.workflow.UpdateMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java similarity index 94% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java index 22d66c45..c7140e60 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import io.temporal.activity.ActivityOptions; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.Workflow; import io.temporal.workflow.WorkflowLock; import java.time.Duration; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java index 85dbc6f1..ea219b0c 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java index f88839f3..afe9075e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java @@ -1,11 +1,11 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import io.nexusrpc.handler.OperationHandler; import io.nexusrpc.handler.OperationImpl; import io.nexusrpc.handler.ServiceImpl; import io.temporal.nexus.Nexus; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java index 0358f58d..1deecf60 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/handler/NexusRemoteGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.handler; +package io.temporal.samples.nexus_messaging.handler; import io.nexusrpc.handler.OperationHandler; import io.nexusrpc.handler.OperationImpl; @@ -7,9 +7,9 @@ import io.temporal.nexus.Nexus; import io.temporal.nexus.WorkflowHandle; import io.temporal.nexus.WorkflowRunOperation; -import io.temporal.samples.nexus_sync_operations.service.Language; -import io.temporal.samples.nexus_sync_operations.service.NexusGreetingService; -import io.temporal.samples.nexus_sync_operations.service.NexusRemoteGreetingService; +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java b/core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java similarity index 63% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java index 5bc9f0dd..3c81c7c5 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/Language.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.service; +package io.temporal.samples.nexus_messaging.service; public enum Language { ARABIC, diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java index 69f1d853..841efb76 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.service; +package io.temporal.samples.nexus_messaging.service; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java similarity index 98% rename from core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java index 4841ae0a..60447a61 100644 --- a/core/src/main/java/io/temporal/samples/nexus_sync_operations/service/NexusRemoteGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_sync_operations.service; +package io.temporal.samples.nexus_messaging.service; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD b/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD deleted file mode 100644 index a8fe380e..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/README.MD +++ /dev/null @@ -1,102 +0,0 @@ -# Nexus - -Temporal Nexus is a new feature of the Temporal platform designed to connect durable executions across team, namespace, -region, and cloud boundaries. It promotes a more modular architecture for sharing a subset of your team’s capabilities -via well-defined service API contracts for other teams to use, that abstract underlying Temporal primitives, like -Workflows, or execute arbitrary code. - -Learn more at [temporal.io/nexus](https://temporal.io/nexus). - -This sample shows how to use Temporal for authoring a Nexus service and call it from a workflow. - -### Sample directory structure - -- [service](./service) - shared service definition -- [caller](./caller) - caller workflows, worker, and starter -- [handler](./handler) - handler workflow, operations, and worker -- [options](./options) - command line argument parsing utility - -## Getting started locally - -### Get `temporal` CLI to enable local development - -1. Follow the instructions on the [docs - site](https://learn.temporal.io/getting_started/go/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) - to install Temporal CLI. - -> NOTE: The recommended version is at least v1.3.0. - -### Spin up environment - -#### Start temporal server - -> HTTP port is required for Nexus communications - -``` -temporal server start-dev -``` - -### Initialize environment - -In a separate terminal window - -#### Create caller and target namespaces - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -#### Create Nexus endpoint - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue \ - --description-file ./core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md -``` - -## Getting started with a self-hosted service or Temporal Cloud - -Nexus is currently available as -[Public Preview](https://docs.temporal.io/evaluate/development-production-features/release-stages). - -Self hosted users can [try Nexus -out](https://github.com/temporalio/temporal/blob/main/docs/architecture/nexus.md#trying-nexus-out) in single cluster -deployments with server version 1.25.0. - -### Make Nexus calls across namespace boundaries - -> Instructions apply for local development, for Temporal Cloud or a self-hosted setups, supply the relevant [CLI -> flags](./options/ClientOptions.java) to properly set up the connection. - -In separate terminal windows: - -### Nexus handler worker - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.handler.HandlerWorker \ - --args="-target-host localhost:7233 -namespace my-target-namespace" -``` - -### Nexus caller worker - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.caller.CallerWorker \ - --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -### Start caller workflow - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexusmessaging.caller.CallerStarter \ - --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -### Output - -which should result in: -``` -[main] INFO i.t.s.nexusmessaging.caller.CallerStarter - Workflow result: Nexus Echo 👋 -``` diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java deleted file mode 100644 index 498ba524..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerStarter.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.api.common.v1.WorkflowExecution; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowOptions; -import io.temporal.samples.nexusmessaging.options.ClientOptions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CallerStarter { - private static final Logger logger = LoggerFactory.getLogger(CallerStarter.class); - - public static void main(String[] args) { - WorkflowClient client = ClientOptions.getWorkflowClient(args); - - WorkflowOptions workflowOptions = - WorkflowOptions.newBuilder().setTaskQueue(CallerWorker.DEFAULT_TASK_QUEUE_NAME).build(); - - MessageCallerStartHandlerWorkflow messageWorkflow = - client.newWorkflowStub(MessageCallerStartHandlerWorkflow.class, workflowOptions); - - // MessageCallerWorkflow messageWorkflow = - // client.newWorkflowStub(MessageCallerWorkflow.class, workflowOptions); - WorkflowExecution execution = WorkflowClient.start(messageWorkflow::sentMessage); - logger.info( - "Started readMessage workflowId: {} runId: {}", - execution.getWorkflowId(), - execution.getRunId()); - String returnVal = messageWorkflow.sentMessage(); - logger.info("Workflow readMessage done - retval is {}", returnVal); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java deleted file mode 100644 index 182c96f3..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/CallerWorker.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.client.WorkflowClient; -import io.temporal.samples.nexusmessaging.options.ClientOptions; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; -import io.temporal.worker.WorkflowImplementationOptions; -import io.temporal.workflow.NexusServiceOptions; -import java.util.Collections; - -public class CallerWorker { - public static final String DEFAULT_TASK_QUEUE_NAME = "my-caller-workflow-task-queue"; - - public static void main(String[] args) { - WorkflowClient client = ClientOptions.getWorkflowClient(args); - - WorkerFactory factory = WorkerFactory.newInstance(client); - - Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); - worker.registerWorkflowImplementationTypes( - WorkflowImplementationOptions.newBuilder() - .setNexusServiceOptions( - Collections.singletonMap( - "SampleNexusService", - NexusServiceOptions.newBuilder().setEndpoint("my-nexus-endpoint-name").build())) - .build(), - // MessageCallerWorkflowImpl.class, - MessageCallerStartHandlerWorkflowImpl.class); - - factory.start(); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java deleted file mode 100644 index ba04df24..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflow.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface MessageCallerStartHandlerWorkflow { - @WorkflowMethod - String sentMessage(); -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java deleted file mode 100644 index f44bf4ec..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerStartHandlerWorkflowImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.NexusOperationHandle; -import io.temporal.workflow.NexusOperationOptions; -import io.temporal.workflow.NexusServiceOptions; -import io.temporal.workflow.Workflow; -import java.time.Duration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageCallerStartHandlerWorkflowImpl implements MessageCallerStartHandlerWorkflow { - - private static final Logger logger = - LoggerFactory.getLogger(MessageCallerStartHandlerWorkflowImpl.class); - - private final String workflowId = "Remote Start Workflow"; - - SampleNexusService sampleNexusService = - Workflow.newNexusServiceStub( - SampleNexusService.class, - NexusServiceOptions.newBuilder() - .setOperationOptions( - NexusOperationOptions.newBuilder() - .setScheduleToCloseTimeout(Duration.ofSeconds(10)) - .build()) - .build()); - - @Override - public String sentMessage() { - /* - SampleNexusService.QueryWorkflowOutput queryOutput = - sampleNexusService.queryWorkflow( - new SampleNexusService.QueryWorkflowInput("query string going in")); - logger.info("Query output: {}", queryOutput.getMessage()); - - SampleNexusService.UpdateWorkflowOutput updateOutput = - sampleNexusService.updateWorkflow( - new SampleNexusService.UpdateWorkflowInput("update input")); - logger.info("Update output: {}", updateOutput.getResult()); - - sampleNexusService.signalWorkflow(new SampleNexusService.SignalWorkflowInput("signal input")); - logger.info("Signal sent via Nexus"); - - return "Query: " + queryOutput.getMessage() + ", Update: " + updateOutput.getResult(); - - */ - - NexusOperationHandle handle = - Workflow.startNexusOperation( - sampleNexusService::runFromRemote, - new SampleNexusService.RunFromRemoteInput(workflowId)); - // Optionally wait for the operation to be started. NexusOperationExecution will contain the - // operation token in case this operation is asynchronous. - handle.getExecution().get(); - - SampleNexusService.QueryWorkflowOutput queryWorkflowOutput = - sampleNexusService.queryWorkflowRemoteStart( - new SampleNexusService.QueryWorkflowRemoteStartInput( - "query string going in", workflowId)); - logger.info("Caller has query output of {}", queryWorkflowOutput); - - sampleNexusService.signalWorkflowRemoteStart( - new SampleNexusService.SignalWorkflowRemoteStartInput("signal input", workflowId)); - logger.info("Signal sent via Nexus"); - - return handle.getResult().get().getMessage(); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java deleted file mode 100644 index 1a9a9b71..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflow.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface MessageCallerWorkflow { - @WorkflowMethod - String sentMessage(); -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java deleted file mode 100644 index 55a6cf66..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/caller/MessageCallerWorkflowImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.temporal.samples.nexusmessaging.caller; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.NexusOperationOptions; -import io.temporal.workflow.NexusServiceOptions; -import io.temporal.workflow.Workflow; -import java.time.Duration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageCallerWorkflowImpl implements MessageCallerWorkflow { - - private static final Logger logger = LoggerFactory.getLogger(MessageCallerWorkflowImpl.class); - - SampleNexusService sampleNexusService = - Workflow.newNexusServiceStub( - SampleNexusService.class, - NexusServiceOptions.newBuilder() - .setOperationOptions( - NexusOperationOptions.newBuilder() - .setScheduleToCloseTimeout(Duration.ofSeconds(10)) - .build()) - .build()); - - @Override - public String sentMessage() { - SampleNexusService.QueryWorkflowOutput queryOutput = - sampleNexusService.queryWorkflow( - new SampleNexusService.QueryWorkflowInput("query string going in")); - logger.info("Query output: {}", queryOutput.getMessage()); - - SampleNexusService.UpdateWorkflowOutput updateOutput = - sampleNexusService.updateWorkflow( - new SampleNexusService.UpdateWorkflowInput("update input")); - logger.info("Update output: {}", updateOutput.getResult()); - - sampleNexusService.signalWorkflow(new SampleNexusService.SignalWorkflowInput("signal input")); - logger.info("Signal sent via Nexus"); - - return "Query: " + queryOutput.getMessage() + ", Update: " + updateOutput.getResult(); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java deleted file mode 100644 index 8af0c721..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/HandlerWorker.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; -import io.temporal.client.WorkflowExecutionAlreadyStarted; -import io.temporal.client.WorkflowOptions; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HandlerWorker { - - public static final String NAMESPACE = "my-target-namespace"; - public static final String DEFAULT_TASK_QUEUE_NAME = "my-handler-task-queue"; - static final String WORKFLOW_ID = "my-handler-workflow"; - - private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); - - public static void main(String[] args) { - WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); - WorkflowClient client = - WorkflowClient.newInstance( - service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); - - // Start the long-running entity workflow that backs the Nexus service, if not already running. - MessageHandlerWorkflow messageHandlerWorkflow = - client.newWorkflowStub( - MessageHandlerWorkflow.class, - WorkflowOptions.newBuilder() - .setWorkflowId(WORKFLOW_ID) - .setTaskQueue(DEFAULT_TASK_QUEUE_NAME) - .build()); - try { - WorkflowClient.start(messageHandlerWorkflow::run); - logger.info("Started message handler workflow: {}", WORKFLOW_ID); - } catch (WorkflowExecutionAlreadyStarted e) { - logger.info("Message handler workflow already running: {}", WORKFLOW_ID); - } - - WorkerFactory factory = WorkerFactory.newInstance(client); - Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); - worker.registerWorkflowImplementationTypes( - MessageHandlerWorkflowImpl.class, MessageHandlerRemoteWorkflowImpl.class); - // worker.registerActivitiesImplementations(new MessageHandlerWorkflowImpl()); - worker.registerNexusServiceImplementation(new SampleNexusServiceImpl(WORKFLOW_ID)); - - factory.start(); - logger.info("Handler worker started, ctrl+c to exit"); - - // WorkflowClient client = ClientOptions.getWorkflowClient(args); - // - // WorkerFactory factory = WorkerFactory.newInstance(client); - // - // Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); - // worker.registerWorkflowImplementationTypes(MessageHandlerWorkflowImpl.class); - // worker.registerNexusServiceImplementation(new SampleNexusServiceImpl()); - // - // factory.start(); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java deleted file mode 100644 index dad19f81..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflow.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.QueryMethod; -import io.temporal.workflow.SignalMethod; -import io.temporal.workflow.UpdateMethod; -import io.temporal.workflow.UpdateValidatorMethod; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface MessageHandlerRemoteWorkflow { - - @WorkflowMethod - SampleNexusService.RunFromRemoteOutput runFromRemote(SampleNexusService.RunFromRemoteInput input); - - @QueryMethod - SampleNexusService.QueryWorkflowOutput queryWorkflow(SampleNexusService.QueryWorkflowInput name); - - @SignalMethod - void signalWorkflow(SampleNexusService.SignalWorkflowInput name); - - @UpdateMethod - SampleNexusService.UpdateWorkflowOutput updateWorkflow( - SampleNexusService.UpdateWorkflowInput name); - - @UpdateValidatorMethod(updateName = "updateWorkflow") - void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name); -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java deleted file mode 100644 index e6e2c5a0..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerRemoteWorkflowImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.Workflow; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageHandlerRemoteWorkflowImpl implements MessageHandlerRemoteWorkflow { - private static final Logger logger = - LoggerFactory.getLogger(MessageHandlerRemoteWorkflowImpl.class); - private boolean keepRunning = true; - - @Override - public SampleNexusService.RunFromRemoteOutput runFromRemote( - SampleNexusService.RunFromRemoteInput input) { - Workflow.await(() -> !keepRunning); - - String logMessage = "runFromRemote was told to stop, and did."; - logger.info(logMessage); - return new SampleNexusService.RunFromRemoteOutput(logMessage); - } - - @Override - public SampleNexusService.QueryWorkflowOutput queryWorkflow( - SampleNexusService.QueryWorkflowInput name) { - logger.info("Query '{}' was received", name.getName()); - return new SampleNexusService.QueryWorkflowOutput("Query received"); - } - - @Override - public void signalWorkflow(SampleNexusService.SignalWorkflowInput name) { - logger.info("Signal was received"); - keepRunning = false; - } - - @Override - public SampleNexusService.UpdateWorkflowOutput updateWorkflow( - SampleNexusService.UpdateWorkflowInput name) { - logger.info("Update {} was received", name.getName()); - return new SampleNexusService.UpdateWorkflowOutput(10); - } - - @Override - public void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name) { - if (name.getName().equals("invalid")) { - logger.info("Update {} was rejected", name.getName()); - throw new IllegalArgumentException("Invalid update name!"); - } - logger.info("Update {} was validated", name.getName()); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java deleted file mode 100644 index 897e4a51..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflow.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.QueryMethod; -import io.temporal.workflow.SignalMethod; -import io.temporal.workflow.UpdateMethod; -import io.temporal.workflow.UpdateValidatorMethod; -import io.temporal.workflow.WorkflowInterface; -import io.temporal.workflow.WorkflowMethod; - -@WorkflowInterface -public interface MessageHandlerWorkflow { - - @WorkflowMethod - void run(); - - @QueryMethod - SampleNexusService.QueryWorkflowOutput queryWorkflow(SampleNexusService.QueryWorkflowInput name); - - @SignalMethod - void signalWorkflow(SampleNexusService.SignalWorkflowInput name); - - @UpdateMethod - SampleNexusService.UpdateWorkflowOutput updateWorkflow( - SampleNexusService.UpdateWorkflowInput name); - - @UpdateValidatorMethod(updateName = "updateWorkflow") - void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name); -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java deleted file mode 100644 index 68129be0..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/MessageHandlerWorkflowImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.temporal.samples.nexusmessaging.service.SampleNexusService; -import io.temporal.workflow.Workflow; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MessageHandlerWorkflowImpl implements MessageHandlerWorkflow { - private static final Logger logger = LoggerFactory.getLogger(MessageHandlerWorkflowImpl.class); - private boolean keepRunning = true; - - @Override - public void run() { - // Long-running entity workflow: stays alive to receive signals, queries, and updates via Nexus. - Workflow.await(() -> !keepRunning); - logger.info("Handler workflow stopped via signal"); - } - - @Override - public SampleNexusService.QueryWorkflowOutput queryWorkflow( - SampleNexusService.QueryWorkflowInput name) { - logger.info("Query '{}' was received", name.getName()); - return new SampleNexusService.QueryWorkflowOutput("Query received"); - } - - @Override - public void signalWorkflow(SampleNexusService.SignalWorkflowInput name) { - logger.info("Signal was received"); - keepRunning = false; - } - - @Override - public SampleNexusService.UpdateWorkflowOutput updateWorkflow( - SampleNexusService.UpdateWorkflowInput name) { - logger.info("Update {} was received", name.getName()); - return new SampleNexusService.UpdateWorkflowOutput(10); - } - - @Override - public void setLanguageValidator(SampleNexusService.UpdateWorkflowInput name) { - if (name.getName().equals("invalid")) { - logger.info("Update {} was rejected", name.getName()); - throw new IllegalArgumentException("Invalid update name!"); - } - logger.info("Update {} was validated", name.getName()); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java deleted file mode 100644 index 675bf7fe..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/handler/SampleNexusServiceImpl.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.temporal.samples.nexusmessaging.handler; - -import io.nexusrpc.handler.OperationHandler; -import io.nexusrpc.handler.OperationImpl; -import io.nexusrpc.handler.ServiceImpl; -import io.temporal.client.WorkflowOptions; -import io.temporal.nexus.Nexus; -import io.temporal.nexus.WorkflowRunOperation; -import io.temporal.samples.nexusmessaging.service.SampleNexusService; - -@ServiceImpl(service = SampleNexusService.class) -public class SampleNexusServiceImpl { - - private final String workflowId; - - public SampleNexusServiceImpl(String workflowId) { - this.workflowId = workflowId; - } - - private MessageHandlerWorkflow getWorkflowStub() { - return Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(MessageHandlerWorkflow.class, workflowId); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.RunFromRemoteInput, SampleNexusService.RunFromRemoteOutput> - runFromRemote() { - return WorkflowRunOperation.fromWorkflowMethod( - (ctx, details, input) -> - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub( - MessageHandlerRemoteWorkflow.class, - WorkflowOptions.newBuilder().setWorkflowId(input.getWorkflowId()).build()) - ::runFromRemote); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.SignalWorkflowInput, SampleNexusService.SignalWorkflowOutput> - signalWorkflow() { - return OperationHandler.sync( - (ctx, details, input) -> { - getWorkflowStub().signalWorkflow(input); - return new SampleNexusService.SignalWorkflowOutput(); - }); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.QueryWorkflowInput, SampleNexusService.QueryWorkflowOutput> - queryWorkflow() { - return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().queryWorkflow(input)); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.UpdateWorkflowInput, SampleNexusService.UpdateWorkflowOutput> - updateWorkflow() { - return OperationHandler.sync((ctx, details, input) -> getWorkflowStub().updateWorkflow(input)); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.QueryWorkflowRemoteStartInput, SampleNexusService.QueryWorkflowOutput> - queryWorkflowRemoteStart() { - return OperationHandler.sync( - (ctx, details, input) -> { - MessageHandlerRemoteWorkflow stub = - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(MessageHandlerRemoteWorkflow.class, input.getWorkflowId()); - return stub.queryWorkflow(new SampleNexusService.QueryWorkflowInput(input.getName())); - }); - } - - @OperationImpl - public OperationHandler< - SampleNexusService.SignalWorkflowRemoteStartInput, - SampleNexusService.SignalWorkflowOutput> - signalWorkflowRemoteStart() { - return OperationHandler.sync( - (ctx, details, input) -> { - MessageHandlerRemoteWorkflow stub = - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(MessageHandlerRemoteWorkflow.class, input.getWorkflowId()); - stub.signalWorkflow(new SampleNexusService.SignalWorkflowInput(input.getName())); - return new SampleNexusService.SignalWorkflowOutput(); - }); - } - - /* - - @OperationImpl - public OperationHandler< - SampleNexusService.SignalWorkflowInput, SampleNexusService.SignalWorkflowOutput> - signalWorkflow() { - return OperationHandler.sync( - (ctx, details, input) -> { - MessageHandlerWorkflow stub = - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(MessageHandlerWorkflow.class, input.getWorkflowId()); - stub.signalWorkflow(input.getName()); - return new SampleNexusService.SignalWorkflowOutput(); - }); - } - - - - @OperationImpl - public OperationHandler< - SampleNexusService.UpdateWorkflowInput, SampleNexusService.UpdateWorkflowOutput> - updateWorkflow() { - return OperationHandler.sync( - (ctx, details, input) -> { - MessageHandlerWorkflow stub = - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(MessageHandlerWorkflow.class, input.getWorkflowId()); - int result = stub.updateWorkflow(input.getName()); - return new SampleNexusService.UpdateWorkflowOutput(result); - }); - } - */ -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java b/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java deleted file mode 100644 index 60e88c0f..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/options/ClientOptions.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.temporal.samples.nexusmessaging.options; - -import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; -import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.serviceclient.WorkflowServiceStubsOptions; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import javax.net.ssl.SSLException; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; - -public class ClientOptions { - - public static WorkflowClient getWorkflowClient(String[] args) { - return getWorkflowClient(args, WorkflowClientOptions.newBuilder()); - } - - public static WorkflowClient getWorkflowClient( - String[] args, WorkflowClientOptions.Builder clientOptions) { - Options options = new Options(); - Option targetHostOption = new Option("target-host", true, "Host:port for the Temporal service"); - targetHostOption.setRequired(false); - options.addOption(targetHostOption); - - Option namespaceOption = new Option("namespace", true, "Namespace to connect to"); - namespaceOption.setRequired(false); - options.addOption(namespaceOption); - - Option serverRootCaOption = - new Option("server-root-ca-cert", true, "Optional path to root server CA cert"); - serverRootCaOption.setRequired(false); - options.addOption(serverRootCaOption); - - Option clientCertOption = - new Option( - "client-cert", true, "Optional path to client cert, mutually exclusive with API key"); - clientCertOption.setRequired(false); - options.addOption(clientCertOption); - - Option clientKeyOption = - new Option( - "client-key", true, "Optional path to client key, mutually exclusive with API key"); - clientKeyOption.setRequired(false); - options.addOption(clientKeyOption); - - Option apiKeyOption = - new Option("api-key", true, "Optional API key, mutually exclusive with cert/key"); - apiKeyOption.setRequired(false); - options.addOption(apiKeyOption); - - Option serverNameOption = - new Option( - "server-name", true, "Server name to use for verifying the server's certificate"); - serverNameOption.setRequired(false); - options.addOption(serverNameOption); - - Option insercureSkipVerifyOption = - new Option( - "insecure-skip-verify", - false, - "Skip verification of the server's certificate and host name"); - insercureSkipVerifyOption.setRequired(false); - options.addOption(insercureSkipVerifyOption); - - CommandLineParser parser = new DefaultParser(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine cmd = null; - - try { - cmd = parser.parse(options, args); - } catch (ParseException e) { - System.out.println(e.getMessage()); - formatter.printHelp("utility-name", options); - - System.exit(1); - } - - String targetHost = cmd.getOptionValue("target-host", "localhost:7233"); - String namespace = cmd.getOptionValue("namespace", "default"); - String serverRootCaCert = cmd.getOptionValue("server-root-ca-cert", ""); - String clientCert = cmd.getOptionValue("client-cert", ""); - String clientKey = cmd.getOptionValue("client-key", ""); - String serverName = cmd.getOptionValue("server-name", ""); - boolean insecureSkipVerify = cmd.hasOption("insecure-skip-verify"); - String apiKey = cmd.getOptionValue("api-key", ""); - - // API key and client cert/key are mutually exclusive - if (!apiKey.isEmpty() && (!clientCert.isEmpty() || !clientKey.isEmpty())) { - throw new IllegalArgumentException("API key and client cert/key are mutually exclusive"); - } - WorkflowServiceStubsOptions.Builder serviceStubOptionsBuilder = - WorkflowServiceStubsOptions.newBuilder().setTarget(targetHost); - // Configure TLS if client cert and key are provided - if (!clientCert.isEmpty() || !clientKey.isEmpty()) { - if (clientCert.isEmpty() || clientKey.isEmpty()) { - throw new IllegalArgumentException("Both client-cert and client-key must be provided"); - } - try { - SslContextBuilder sslContext = - SslContextBuilder.forClient() - .keyManager(new FileInputStream(clientCert), new FileInputStream(clientKey)); - if (serverRootCaCert != null && !serverRootCaCert.isEmpty()) { - sslContext.trustManager(new FileInputStream(serverRootCaCert)); - } - if (insecureSkipVerify) { - sslContext.trustManager(InsecureTrustManagerFactory.INSTANCE); - } - serviceStubOptionsBuilder.setSslContext(GrpcSslContexts.configure(sslContext).build()); - } catch (SSLException e) { - throw new RuntimeException(e); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - if (serverName != null && !serverName.isEmpty()) { - serviceStubOptionsBuilder.setChannelInitializer(c -> c.overrideAuthority(serverName)); - } - } - // Configure API key if provided - if (!apiKey.isEmpty()) { - serviceStubOptionsBuilder.setEnableHttps(true); - serviceStubOptionsBuilder.addApiKey(() -> apiKey); - } - - WorkflowServiceStubs service = - WorkflowServiceStubs.newServiceStubs(serviceStubOptionsBuilder.build()); - return WorkflowClient.newInstance(service, clientOptions.setNamespace(namespace).build()); - } -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java b/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java deleted file mode 100644 index edb72864..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java +++ /dev/null @@ -1,177 +0,0 @@ -package io.temporal.samples.nexusmessaging.service; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.nexusrpc.Operation; -import io.nexusrpc.Service; - -@Service -public interface SampleNexusService { - - class SignalWorkflowInput { - private final String name; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public SignalWorkflowInput(@JsonProperty("name") String name) { - this.name = name; - } - - @JsonProperty("name") - public String getName() { - return name; - } - } - - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) - class SignalWorkflowOutput { - @JsonCreator - public SignalWorkflowOutput() {} - } - - class QueryWorkflowInput { - private final String name; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public QueryWorkflowInput(@JsonProperty("name") String name) { - this.name = name; - } - - @JsonProperty("name") - public String getName() { - return name; - } - } - - class QueryWorkflowRemoteStartInput { - private final String name; - private final String workflowId; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public QueryWorkflowRemoteStartInput( - @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { - this.name = name; - this.workflowId = workflowId; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; - } - } - - class QueryWorkflowOutput { - private final String message; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public QueryWorkflowOutput(@JsonProperty("message") String message) { - this.message = message; - } - - @JsonProperty("message") - public String getMessage() { - return message; - } - } - - class UpdateWorkflowInput { - private final String name; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public UpdateWorkflowInput(@JsonProperty("name") String name) { - this.name = name; - } - - @JsonProperty("name") - public String getName() { - return name; - } - } - - class UpdateWorkflowOutput { - private final int result; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public UpdateWorkflowOutput(@JsonProperty("result") int result) { - this.result = result; - } - - @JsonProperty("result") - public int getResult() { - return result; - } - } - - class RunFromRemoteInput { - private final String workflowId; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public RunFromRemoteInput(@JsonProperty("workflowId") String workflowId) { - this.workflowId = workflowId; - } - - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; - } - } - - class RunFromRemoteOutput { - private final String message; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public RunFromRemoteOutput(@JsonProperty("message") String message) { - this.message = message; - } - - @JsonProperty("message") - public String getMessage() { - return message; - } - } - - @Operation - RunFromRemoteOutput runFromRemote(RunFromRemoteInput input); - - class SignalWorkflowRemoteStartInput { - private final String name; - private final String workflowId; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public SignalWorkflowRemoteStartInput( - @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { - this.name = name; - this.workflowId = workflowId; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; - } - } - - @Operation - SignalWorkflowOutput signalWorkflow(SignalWorkflowInput input); - - @Operation - SignalWorkflowOutput signalWorkflowRemoteStart(SignalWorkflowRemoteStartInput input); - - @Operation - UpdateWorkflowOutput updateWorkflow(UpdateWorkflowInput input); - - @Operation - QueryWorkflowOutput queryWorkflow(QueryWorkflowInput input); - - @Operation - QueryWorkflowOutput queryWorkflowRemoteStart(QueryWorkflowRemoteStartInput input); -} diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md b/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md deleted file mode 100644 index 0d607d9a..00000000 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/service/description.md +++ /dev/null @@ -1,6 +0,0 @@ -## Service: [SampleNexusService](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java) - - operation: `signalWorkflow` - - operation: `queryWorkflow` - - operation: `updateWorkflow` - -See https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexusmessaging/service/SampleNexusService.java for Input / Output types. From f6f20ab41cce39e9812ed8844c5332ea0f53ccc4 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 14:23:08 -0700 Subject: [PATCH 04/10] Remove yarn.lock and node_modules/.yarn-integrity from tracking --- node_modules/.yarn-integrity | 10 ---------- yarn.lock | 4 ---- 2 files changed, 14 deletions(-) delete mode 100644 node_modules/.yarn-integrity delete mode 100644 yarn.lock diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity deleted file mode 100644 index 044a5ddd..00000000 --- a/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "darwin-arm64-115", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd1..00000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From 47a43e273982f10912dc552d33b13fca23b7fa5a Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 14:52:16 -0700 Subject: [PATCH 05/10] Reviewing --- .../samples/nexus_messaging/README.md | 31 ++++++++++--------- .../nexus_messaging/caller/CallerStarter.java | 2 +- .../nexus_messaging/caller/CallerWorker.java | 7 +++-- .../caller/CallerWorkflowImpl.java | 6 ++++ .../caller_remote/CallerRemoteStarter.java | 2 +- .../caller_remote/CallerRemoteWorker.java | 7 +++-- .../CallerRemoteWorkflowImpl.java | 10 ++++-- .../handler/GreetingWorkflowImpl.java | 2 +- .../handler/HandlerWorker.java | 6 ++-- .../handler/NexusGreetingServiceImpl.java | 8 +++-- .../NexusRemoteGreetingServiceImpl.java | 5 +++ 11 files changed, 54 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md index 15dcf21a..066bc213 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md @@ -20,38 +20,41 @@ There are **two caller patterns** that share the same handler workflow (`Greetin ### Running -Start a Temporal server and create namespaces/endpoint: +Start a Temporal server: ```bash temporal server start-dev +``` +Create the namespaces and Nexus endpoint: -temporal operator namespace create --namespace nexus-sync-operations-handler-namespace -temporal operator namespace create --namespace nexus-sync-operations-caller-namespace +```bash +temporal operator namespace create --namespace nexus-messaging-handler-namespace +temporal operator namespace create --namespace nexus-messaging-caller-namespace temporal operator nexus endpoint create \ - --name nexus-sync-operations-nexus-endpoint \ - --target-namespace nexus-sync-operations-handler-namespace \ - --target-task-queue nexus-sync-operations-handler-task-queue + --name nexus-messaging-nexus-endpoint \ + --target-namespace nexus-messaging-handler-namespace \ + --target-task-queue nexus-messaging-handler-task-queue ``` In one terminal, start the handler worker (shared by both patterns): ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.handler.HandlerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.handler.HandlerWorker ``` #### Entity pattern -In a second terminal, run the caller worker: +In the second terminal, run the caller worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerWorker ``` -In a third terminal, start the caller workflow: +In the third terminal, start the caller workflow: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller.CallerStarter +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerStarter ``` Expected output: @@ -67,19 +70,19 @@ workflow approved In a second terminal, run the remote caller worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorker ``` In a third terminal, start the remote caller workflow: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_sync_operations.caller_remote.CallerRemoteStarter +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteStarter ``` Expected output: ``` -started remote greeting workflow: nexus-sync-operations-remote-greeting-workflow +started remote greeting workflow: nexus-messaging-remote-greeting-workflow supported languages: [CHINESE, ENGLISH] language changed: ENGLISH -> ARABIC workflow approved diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java index bb4163da..95a3d025 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java @@ -20,7 +20,7 @@ public static void main(String[] args) { client.newWorkflowStub( CallerWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId("nexus-sync-operations-caller-" + UUID.randomUUID()) + .setWorkflowId("nexus-messaging-caller-" + UUID.randomUUID()) .setTaskQueue(CallerWorker.TASK_QUEUE) .build()); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java index 1ac68d76..8194503b 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java @@ -14,9 +14,9 @@ public class CallerWorker { private static final Logger logger = LoggerFactory.getLogger(CallerWorker.class); - public static final String NAMESPACE = "nexus-sync-operations-caller-namespace"; - public static final String TASK_QUEUE = "nexus-sync-operations-caller-task-queue"; - static final String NEXUS_ENDPOINT = "nexus-sync-operations-nexus-endpoint"; + public static final String NAMESPACE = "nexus-messaging-caller-namespace"; + public static final String TASK_QUEUE = "nexus-messaging-caller-task-queue"; + static final String NEXUS_ENDPOINT = "nexus-messaging-nexus-endpoint"; public static void main(String[] args) throws InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); @@ -29,6 +29,7 @@ public static void main(String[] args) throws InterruptedException { worker.registerWorkflowImplementationTypes( WorkflowImplementationOptions.newBuilder() .setNexusServiceOptions( + // The key must match the @Service-annotated interface name. Collections.singletonMap( "NexusGreetingService", NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build())) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java index 5a37caf0..ed07f99e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java @@ -1,6 +1,7 @@ package io.temporal.samples.nexus_messaging.caller; import io.temporal.failure.ApplicationFailure; +import io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorkflowImpl; import io.temporal.samples.nexus_messaging.service.Language; import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.NexusOperationOptions; @@ -9,9 +10,13 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CallerWorkflowImpl implements CallerWorkflow { + private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class); + // The endpoint is configured at the worker level in CallerWorker; only operation options are // set here. NexusGreetingService greetingService = @@ -36,6 +41,7 @@ public List run() { // 👉 Call a Nexus operation backed by an update against the entity workflow. Language previousLanguage = greetingService.setLanguage(new NexusGreetingService.SetLanguageInput(Language.ARABIC)); + logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); // 👉 Call a Nexus operation backed by a query to confirm the language change. Language currentLanguage = diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java index c255f288..ad20ab2a 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java @@ -20,7 +20,7 @@ public static void main(String[] args) { client.newWorkflowStub( CallerRemoteWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId("nexus-sync-operations-remote-caller-" + UUID.randomUUID()) + .setWorkflowId("nexus-messaging-remote-caller-" + UUID.randomUUID()) .setTaskQueue(CallerRemoteWorker.TASK_QUEUE) .build()); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java index 1e89c59b..4aa850cc 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java @@ -14,9 +14,9 @@ public class CallerRemoteWorker { private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorker.class); - public static final String NAMESPACE = "nexus-sync-operations-caller-namespace"; - public static final String TASK_QUEUE = "nexus-sync-operations-caller-remote-task-queue"; - static final String NEXUS_ENDPOINT = "nexus-sync-operations-nexus-endpoint"; + public static final String NAMESPACE = "nexus-messaging-caller-namespace"; + public static final String TASK_QUEUE = "nexus-messaging-caller-remote-task-queue"; + static final String NEXUS_ENDPOINT = "nexus-messaging-nexus-endpoint"; public static void main(String[] args) throws InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); @@ -29,6 +29,7 @@ public static void main(String[] args) throws InterruptedException { worker.registerWorkflowImplementationTypes( WorkflowImplementationOptions.newBuilder() .setNexusServiceOptions( + // The key must match the @Service-annotated interface name. Collections.singletonMap( "NexusRemoteGreetingService", NexusServiceOptions.newBuilder().setEndpoint(NEXUS_ENDPOINT).build())) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java index 8477c10e..16ee4d9c 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java @@ -17,7 +17,7 @@ public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class); - private static final String REMOTE_WORKFLOW_ID = "nexus-sync-operations-remote-greeting-workflow"; + private static final String REMOTE_WORKFLOW_ID = "nexus-messaging-remote-greeting-workflow"; NexusRemoteGreetingService greetingRemoteService = Workflow.newNexusServiceStub( @@ -33,7 +33,9 @@ public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { public List run() { List log = new ArrayList<>(); - // Start a new GreetingWorkflow on the handler side via Nexus. + // 👉 Async Nexus operation — starts a workflow on the handler and returns a handle. + // Unlike the sync operations below (getLanguages, setLanguage, etc.), this does not block + // until the workflow completes. It is backed by WorkflowRunOperation on the handler side. NexusOperationHandle handle = Workflow.startNexusOperation( greetingRemoteService::runFromRemote, @@ -43,6 +45,8 @@ public List run() { log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_ID); // Query the remote workflow for supported languages. + // Output types (e.g. GetLanguagesOutput) are defined on NexusGreetingService and shared by + // both service interfaces. NexusGreetingService.GetLanguagesOutput languagesOutput = greetingRemoteService.getLanguages( new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ID)); @@ -52,7 +56,7 @@ public List run() { Language previousLanguage = greetingRemoteService.setLanguage( new NexusRemoteGreetingService.SetLanguageInput(Language.ARABIC, REMOTE_WORKFLOW_ID)); - logger.info("Language changed from {}", previousLanguage); + logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); // Confirm the change by querying. Language currentLanguage = diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java index c7140e60..c5ca5596 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java @@ -22,7 +22,7 @@ public class GreetingWorkflowImpl implements GreetingWorkflow { private final Map greetings = new EnumMap<>(Language.class); private Language language = Language.ENGLISH; - private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); + private static final Logger logger = LoggerFactory.getLogger(GreetingWorkflowImpl.class); // Used to serialize concurrent setLanguageUsingActivity calls so that only one activity runs at // a time per update handler execution. diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java index ea219b0c..e23fff61 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java @@ -13,9 +13,9 @@ public class HandlerWorker { private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); - public static final String NAMESPACE = "nexus-sync-operations-handler-namespace"; - public static final String TASK_QUEUE = "nexus-sync-operations-handler-task-queue"; - static final String WORKFLOW_ID = "nexus-sync-operations-greeting-workflow"; + public static final String NAMESPACE = "nexus-messaging-handler-namespace"; + public static final String TASK_QUEUE = "nexus-messaging-handler-task-queue"; + static final String WORKFLOW_ID = "nexus-messaging-greeting-workflow"; public static void main(String[] args) throws InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java index afe9075e..0926f6ce 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java @@ -53,9 +53,11 @@ public OperationHandler getLang }); } - // 👉 Backed by an update against the long-running entity workflow. Although updates can run for - // an arbitrarily long time, when exposed via a sync Nexus operation the update should complete - // quickly (sync operations must finish in under 10s). + // 👉 Backed by an update against the long-running entity workflow. Routes to + // setLanguageUsingActivity (not setLanguage) so that new languages not already in the greetings + // map can be fetched via an activity. Although updates can run for an arbitrarily long time, when + // exposed via a sync Nexus operation the update should complete quickly (sync operations must + // finish in under 10s). @OperationImpl public OperationHandler setLanguage() { return OperationHandler.sync( diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java index 1deecf60..42867f58 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java @@ -32,6 +32,11 @@ private GreetingWorkflow getWorkflowStub(String workflowId) { // Starts a new GreetingWorkflow with the caller-specified workflow ID. This is an async // Nexus operation backed by WorkflowRunOperation. + // + // fromWorkflowHandle (rather than fromWorkflowMethod) is used here because the Nexus operation + // input (RunFromRemoteInput) differs from the workflow method parameters — run() takes no args. + // The input is consumed to set the workflow ID on the stub; the workflow itself is invoked + // with no arguments via the ::run method reference. @OperationImpl public OperationHandler runFromRemote() { return WorkflowRunOperation.fromWorkflowHandle( From b6c14f4568bab2dceded9728065c62f7aaded1c8 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Thu, 9 Apr 2026 15:34:34 -0700 Subject: [PATCH 06/10] Some tweaks --- .../samples/nexus_messaging/README.md | 10 +- .../caller/CallerWorkflowImpl.java | 17 +- .../CallerRemoteWorkflowImpl.java | 78 ---------- .../CallerRemoteStarter.java | 2 +- .../CallerRemoteWorker.java | 6 +- .../CallerRemoteWorkflow.java | 2 +- .../CallerRemoteWorkflowImpl.java | 145 ++++++++++++++++++ .../handler/GreetingWorkflowImpl.java | 28 +--- 8 files changed, 172 insertions(+), 116 deletions(-) delete mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java rename core/src/main/java/io/temporal/samples/nexus_messaging/{caller_remote => callerondemand}/CallerRemoteStarter.java (94%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{caller_remote => callerondemand}/CallerRemoteWorker.java (90%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{caller_remote => callerondemand}/CallerRemoteWorkflow.java (78%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md index 066bc213..d25424f4 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md @@ -40,7 +40,7 @@ temporal operator nexus endpoint create \ In one terminal, start the handler worker (shared by both patterns): ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.handler.HandlerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.handler.HandlerWorker --args="-target-host localhost:7233 -namespace my-target-namespace" ``` #### Entity pattern @@ -48,13 +48,13 @@ In one terminal, start the handler worker (shared by both patterns): In the second terminal, run the caller worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerWorker --args="-target-host localhost:7233 -namespace my-caller-namespace" ``` In the third terminal, start the caller workflow: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerStarter +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerStarter --args="-target-host localhost:7233 -namespace my-caller-namespace" ``` Expected output: @@ -70,13 +70,13 @@ workflow approved In a second terminal, run the remote caller worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorker --args="-target-host localhost:7233 -namespace my-caller-namespace" ``` In a third terminal, start the remote caller workflow: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteStarter +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteStarter --args="-target-host localhost:7233 -namespace my-caller-namespace" ``` Expected output: diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java index ed07f99e..dbea7843 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java @@ -1,7 +1,7 @@ package io.temporal.samples.nexus_messaging.caller; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorkflowImpl; +import io.temporal.samples.nexus_messaging.callerondemand.CallerRemoteWorkflowImpl; import io.temporal.samples.nexus_messaging.service.Language; import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.NexusOperationOptions; @@ -11,11 +11,10 @@ import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class CallerWorkflowImpl implements CallerWorkflow { - private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class); + private static final Logger logger = Workflow.getLogger(CallerRemoteWorkflowImpl.class); // The endpoint is configured at the worker level in CallerWorker; only operation options are // set here. @@ -36,26 +35,28 @@ public List run() { // 👉 Call a Nexus operation backed by a query against the entity workflow. NexusGreetingService.GetLanguagesOutput languagesOutput = greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false)); - log.add("supported languages: " + languagesOutput.getLanguages()); + log.add("Supported languages: " + languagesOutput.getLanguages()); + logger.info("Supported languages: {}", languagesOutput.getLanguages()); // 👉 Call a Nexus operation backed by an update against the entity workflow. Language previousLanguage = greetingService.setLanguage(new NexusGreetingService.SetLanguageInput(Language.ARABIC)); - logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); // 👉 Call a Nexus operation backed by a query to confirm the language change. Language currentLanguage = greetingService.getLanguage(new NexusGreetingService.GetLanguageInput()); if (currentLanguage != Language.ARABIC) { throw ApplicationFailure.newFailure( - "expected language ARABIC, got " + currentLanguage, "AssertionError"); + "Expected language ARABIC, got " + currentLanguage, "AssertionError"); } - log.add("language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name()); + log.add("Language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name()); + logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); // 👉 Call a Nexus operation backed by a signal against the entity workflow. greetingService.approve(new NexusGreetingService.ApproveInput("caller")); - log.add("workflow approved"); + log.add("Workflow approved"); + logger.info("Workflow approved"); return log; } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java deleted file mode 100644 index 16ee4d9c..00000000 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflowImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.temporal.samples.nexus_messaging.caller_remote; - -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; -import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; -import io.temporal.workflow.NexusOperationHandle; -import io.temporal.workflow.NexusOperationOptions; -import io.temporal.workflow.NexusServiceOptions; -import io.temporal.workflow.Workflow; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { - - private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorkflowImpl.class); - - private static final String REMOTE_WORKFLOW_ID = "nexus-messaging-remote-greeting-workflow"; - - NexusRemoteGreetingService greetingRemoteService = - Workflow.newNexusServiceStub( - NexusRemoteGreetingService.class, - NexusServiceOptions.newBuilder() - .setOperationOptions( - NexusOperationOptions.newBuilder() - .setScheduleToCloseTimeout(Duration.ofSeconds(10)) - .build()) - .build()); - - @Override - public List run() { - List log = new ArrayList<>(); - - // 👉 Async Nexus operation — starts a workflow on the handler and returns a handle. - // Unlike the sync operations below (getLanguages, setLanguage, etc.), this does not block - // until the workflow completes. It is backed by WorkflowRunOperation on the handler side. - NexusOperationHandle handle = - Workflow.startNexusOperation( - greetingRemoteService::runFromRemote, - new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_ID)); - // Wait for the operation to be started (workflow is now running on the handler). - handle.getExecution().get(); - log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_ID); - - // Query the remote workflow for supported languages. - // Output types (e.g. GetLanguagesOutput) are defined on NexusGreetingService and shared by - // both service interfaces. - NexusGreetingService.GetLanguagesOutput languagesOutput = - greetingRemoteService.getLanguages( - new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ID)); - log.add("supported languages: " + languagesOutput.getLanguages()); - - // Update the language on the remote workflow. - Language previousLanguage = - greetingRemoteService.setLanguage( - new NexusRemoteGreetingService.SetLanguageInput(Language.ARABIC, REMOTE_WORKFLOW_ID)); - logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); - - // Confirm the change by querying. - Language currentLanguage = - greetingRemoteService.getLanguage( - new NexusRemoteGreetingService.GetLanguageInput(REMOTE_WORKFLOW_ID)); - log.add("language changed: " + previousLanguage.name() + " -> " + currentLanguage.name()); - - // Approve the remote workflow so it can complete. - greetingRemoteService.approve( - new NexusRemoteGreetingService.ApproveInput("remote-caller", REMOTE_WORKFLOW_ID)); - log.add("workflow approved"); - - // Wait for the remote workflow to finish and return its result. - String result = handle.getResult().get(); - log.add("workflow result: " + result); - - return log; - } -} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java similarity index 94% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java index ad20ab2a..670d6123 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller_remote; +package io.temporal.samples.nexus_messaging.callerondemand; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java similarity index 90% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java index 4aa850cc..49dad341 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller_remote; +package io.temporal.samples.nexus_messaging.callerondemand; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; @@ -7,12 +7,12 @@ import io.temporal.worker.WorkerFactory; import io.temporal.worker.WorkflowImplementationOptions; import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; import java.util.Collections; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class CallerRemoteWorker { - private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorker.class); + private static final Logger logger = Workflow.getLogger(CallerRemoteWorker.class); public static final String NAMESPACE = "nexus-messaging-caller-namespace"; public static final String TASK_QUEUE = "nexus-messaging-caller-remote-task-queue"; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java similarity index 78% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java index bd72d4ac..b707fa3b 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller_remote/CallerRemoteWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller_remote; +package io.temporal.samples.nexus_messaging.callerondemand; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java new file mode 100644 index 00000000..61bdee13 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java @@ -0,0 +1,145 @@ +package io.temporal.samples.nexus_messaging.callerondemand; + +import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; +import io.temporal.workflow.NexusOperationHandle; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; + +public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { + + private static final Logger logger = Workflow.getLogger(CallerRemoteWorkflowImpl.class); + + // private static final String REMOTE_WORKFLOW_ID = "nexus-messaging-remote-greeting-workflow"; + private static final String REMOTE_WORKFLOW_ONE = "UserId One"; + private static final String REMOTE_WORKFLOW_TWO = "UserId Two"; + + NexusRemoteGreetingService greetingRemoteServiceOne = + Workflow.newNexusServiceStub( + NexusRemoteGreetingService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + NexusRemoteGreetingService greetingRemoteServiceTwo = + Workflow.newNexusServiceStub( + NexusRemoteGreetingService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public List run() { + List log = new ArrayList<>(); + + // 👉 Async Nexus operation — starts a workflow on the handler and returns a handle. + // Unlike the sync operations below (getLanguages, setLanguage, etc.), this does not block + // until the workflow completes. It is backed by WorkflowRunOperation on the handler side. + NexusOperationHandle handleOne = + Workflow.startNexusOperation( + greetingRemoteServiceOne::runFromRemote, + new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_ONE)); + // Wait for the operation to be started (workflow is now running on the handler). + handleOne.getExecution().get(); + log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_ONE); + logger.info("started remote greeting workflow {}", REMOTE_WORKFLOW_ONE); + + NexusOperationHandle handleTwo = + Workflow.startNexusOperation( + greetingRemoteServiceOne::runFromRemote, + new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_TWO)); + // Wait for the operation to be started (workflow is now running on the handler). + handleTwo.getExecution().get(); + log.add("started remote greeting workflow: " + REMOTE_WORKFLOW_TWO); + logger.info("started remote greeting workflow {}", REMOTE_WORKFLOW_TWO); + + // Query the remote workflow for supported languages. + // Output types (e.g. GetLanguagesOutput) are defined on NexusGreetingService and shared by + // both service interfaces. + NexusGreetingService.GetLanguagesOutput languagesOutput = + greetingRemoteServiceOne.getLanguages( + new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ONE)); + log.add( + "Supported languages for " + REMOTE_WORKFLOW_ONE + ": " + languagesOutput.getLanguages()); + logger.info( + "supported languages are {} for workflow {}", + languagesOutput.getLanguages(), + REMOTE_WORKFLOW_ONE); + + languagesOutput = + greetingRemoteServiceTwo.getLanguages( + new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_TWO)); + log.add( + "Supported languages for " + REMOTE_WORKFLOW_TWO + ": " + languagesOutput.getLanguages()); + logger.info( + "supported languages are {} for workflow {}", + languagesOutput.getLanguages(), + REMOTE_WORKFLOW_TWO); + + // Update the language on the remote workflow. + Language previousLanguageOne = + greetingRemoteServiceOne.setLanguage( + new NexusRemoteGreetingService.SetLanguageInput(Language.ARABIC, REMOTE_WORKFLOW_ONE)); + + Language previousLanguageTwo = + greetingRemoteServiceTwo.setLanguage( + new NexusRemoteGreetingService.SetLanguageInput(Language.HINDI, REMOTE_WORKFLOW_TWO)); + + // Confirm the change by querying. + Language currentLanguage = + greetingRemoteServiceOne.getLanguage( + new NexusRemoteGreetingService.GetLanguageInput(REMOTE_WORKFLOW_ONE)); + log.add( + REMOTE_WORKFLOW_ONE + + " changed language: " + + previousLanguageOne.name() + + " -> " + + currentLanguage.name()); + logger.info( + "Language changed from {} to {} for workflow {}", + previousLanguageOne, + currentLanguage, + REMOTE_WORKFLOW_ONE); + + currentLanguage = + greetingRemoteServiceTwo.getLanguage( + new NexusRemoteGreetingService.GetLanguageInput(REMOTE_WORKFLOW_TWO)); + log.add( + REMOTE_WORKFLOW_TWO + + " changed language: " + + previousLanguageTwo.name() + + " -> " + + currentLanguage.name()); + logger.info( + "Language changed from {} to {} for workflow {}", + previousLanguageTwo, + currentLanguage, + REMOTE_WORKFLOW_TWO); + + // Approve the remote workflow so it can complete. + greetingRemoteServiceOne.approve( + new NexusRemoteGreetingService.ApproveInput("remote-caller", REMOTE_WORKFLOW_ONE)); + greetingRemoteServiceTwo.approve( + new NexusRemoteGreetingService.ApproveInput("remote-caller", REMOTE_WORKFLOW_TWO)); + log.add("Workflows approved"); + + // Wait for the remote workflow to finish and return its result. + String result = handleOne.getResult().get(); + log.add("Workflow one result: " + result); + + result = handleTwo.getResult().get(); + log.add("Workflow two result: " + result); + return log; + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java index c5ca5596..0021607e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java @@ -5,7 +5,6 @@ import io.temporal.samples.nexus_messaging.service.Language; import io.temporal.samples.nexus_messaging.service.NexusGreetingService; import io.temporal.workflow.Workflow; -import io.temporal.workflow.WorkflowLock; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -14,7 +13,6 @@ import java.util.List; import java.util.Map; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class GreetingWorkflowImpl implements GreetingWorkflow { @@ -22,11 +20,7 @@ public class GreetingWorkflowImpl implements GreetingWorkflow { private final Map greetings = new EnumMap<>(Language.class); private Language language = Language.ENGLISH; - private static final Logger logger = LoggerFactory.getLogger(GreetingWorkflowImpl.class); - - // Used to serialize concurrent setLanguageUsingActivity calls so that only one activity runs at - // a time per update handler execution. - private final WorkflowLock lock = Workflow.newWorkflowLock(); + private static final Logger logger = Workflow.getLogger(GreetingWorkflowImpl.class); private final GreetingActivity greetingActivity = Workflow.newActivityStub( @@ -88,20 +82,14 @@ public void validateSetLanguage(NexusGreetingService.SetLanguageInput input) { @Override public Language setLanguageUsingActivity(NexusGreetingService.SetLanguageInput input) { if (!greetings.containsKey(input.getLanguage())) { - // Use a lock so that if this handler is called concurrently, each call executes its activity - // only after the previous one has completed. This ensures updates are processed in order. - lock.lock(); - try { - String greeting = greetingActivity.callGreetingService(input.getLanguage()); - if (greeting == null) { - throw ApplicationFailure.newFailure( - "Greeting service does not support " + input.getLanguage().name(), - "UnsupportedLanguage"); - } - greetings.put(input.getLanguage(), greeting); - } finally { - lock.unlock(); + + String greeting = greetingActivity.callGreetingService(input.getLanguage()); + if (greeting == null) { + throw ApplicationFailure.newFailure( + "Greeting service does not support " + input.getLanguage().name(), + "UnsupportedLanguage"); } + greetings.put(input.getLanguage(), greeting); } Language previous = language; language = input.getLanguage(); From 17adffb4c2b6be58bc675b256ece7ec5d8546921 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Thu, 9 Apr 2026 17:25:41 -0700 Subject: [PATCH 07/10] Splitting test cases apart --- .../samples/nexus_messaging/README.md | 142 ++---------------- .../nexus_messaging/callerpattern/README.md | 57 +++++++ .../caller/CallerStarter.java | 2 +- .../caller/CallerWorker.java | 2 +- .../caller/CallerWorkflow.java | 2 +- .../caller/CallerWorkflowImpl.java | 9 +- .../handler/GreetingActivity.java | 4 +- .../handler/GreetingActivityImpl.java | 4 +- .../handler/GreetingWorkflow.java | 6 +- .../handler/GreetingWorkflowImpl.java | 7 +- .../handler/HandlerWorker.java | 3 +- .../handler/NexusGreetingServiceImpl.java | 16 +- .../{ => callerpattern}/service/Language.java | 2 +- .../service/NexusGreetingService.java | 2 +- .../nexus_messaging/ondemandpattern/README.md | 66 ++++++++ .../caller}/CallerRemoteStarter.java | 2 +- .../caller}/CallerRemoteWorker.java | 2 +- .../caller}/CallerRemoteWorkflow.java | 2 +- .../caller}/CallerRemoteWorkflowImpl.java | 27 ++-- .../handler/GreetingActivity.java | 12 ++ .../handler/GreetingActivityImpl.java | 25 +++ .../handler/GreetingWorkflow.java | 90 +++++++++++ .../handler/GreetingWorkflowImpl.java | 98 ++++++++++++ .../handler/HandlerWorker.java | 33 ++++ .../NexusRemoteGreetingServiceImpl.java | 25 ++- .../ondemandpattern/service/Language.java | 11 ++ .../service/NexusRemoteGreetingService.java | 33 +++- 27 files changed, 480 insertions(+), 204 deletions(-) create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/caller/CallerStarter.java (93%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/caller/CallerWorker.java (96%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/caller/CallerWorkflow.java (76%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/caller/CallerWorkflowImpl.java (88%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/GreetingActivity.java (68%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/GreetingActivityImpl.java (83%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/GreetingWorkflow.java (89%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/GreetingWorkflowImpl.java (92%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/HandlerWorker.java (93%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/handler/NexusGreetingServiceImpl.java (74%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/service/Language.java (60%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => callerpattern}/service/NexusGreetingService.java (97%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md rename core/src/main/java/io/temporal/samples/nexus_messaging/{callerondemand => ondemandpattern/caller}/CallerRemoteStarter.java (93%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{callerondemand => ondemandpattern/caller}/CallerRemoteWorker.java (96%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{callerondemand => ondemandpattern/caller}/CallerRemoteWorkflow.java (76%) rename core/src/main/java/io/temporal/samples/nexus_messaging/{callerondemand => ondemandpattern/caller}/CallerRemoteWorkflowImpl.java (87%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => ondemandpattern}/handler/NexusRemoteGreetingServiceImpl.java (77%) create mode 100644 core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java rename core/src/main/java/io/temporal/samples/nexus_messaging/{ => ondemandpattern}/service/NexusRemoteGreetingService.java (78%) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md index d25424f4..fd047d66 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md @@ -1,138 +1,16 @@ This sample shows how to expose a long-running workflow's queries, updates, and signals as Nexus -operations. The caller interacts only with the Nexus service; the workflow is a private -implementation detail. +operations. There are two self-contained examples, each in its own directory: -There are **two caller patterns** that share the same handler workflow (`GreetingWorkflow`): - -| | `caller/` (entity pattern) | `caller_remote/` (remote-start pattern) | +| | `callerpattern/` | `ondemandpattern/` | |---|---|---| -| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a `runFromRemote` Nexus operation | +| **Pattern** | Signal an existing workflow | Create and run workflows on demand | +| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a Nexus operation | | **Who knows the workflow ID?** | Only the handler | The caller chooses and passes it in every operation | -| **Nexus service** | `NexusGreetingService` — inputs carry only business data | `NexusRemoteGreetingService` — every input includes a `workflowId` | -| **When to use** | Single shared entity; callers don't need lifecycle control | Caller needs to create and target specific workflow instances | - -### Directory structure - -- `service/` — shared Nexus service definitions (`NexusGreetingService`, `NexusRemoteGreetingService`) and `Language` enum -- `handler/` — `GreetingWorkflow` and its implementation, `GreetingActivity`, both Nexus service impls (`NexusGreetingServiceImpl`, `NexusRemoteGreetingServiceImpl`), and the handler worker -- `caller/` — entity-pattern caller (workflow, worker, starter) -- `caller_remote/` — remote-start caller (workflow, worker, starter) - -### Running - -Start a Temporal server: - -```bash -temporal server start-dev -``` -Create the namespaces and Nexus endpoint: - -```bash -temporal operator namespace create --namespace nexus-messaging-handler-namespace -temporal operator namespace create --namespace nexus-messaging-caller-namespace - -temporal operator nexus endpoint create \ - --name nexus-messaging-nexus-endpoint \ - --target-namespace nexus-messaging-handler-namespace \ - --target-task-queue nexus-messaging-handler-task-queue -``` - -In one terminal, start the handler worker (shared by both patterns): - -```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.handler.HandlerWorker --args="-target-host localhost:7233 -namespace my-target-namespace" -``` - -#### Entity pattern - -In the second terminal, run the caller worker: - -```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerWorker --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -In the third terminal, start the caller workflow: - -```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller.CallerStarter --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -Expected output: - -``` -supported languages: [CHINESE, ENGLISH] -language changed: ENGLISH -> ARABIC -workflow approved -``` - -#### Remote-start pattern - -In a second terminal, run the remote caller worker: - -```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteWorker --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -In a third terminal, start the remote caller workflow: - -```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.caller_remote.CallerRemoteStarter --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -Expected output: - -``` -started remote greeting workflow: nexus-messaging-remote-greeting-workflow -supported languages: [CHINESE, ENGLISH] -language changed: ENGLISH -> ARABIC -workflow approved -workflow result: مرحبا بالعالم -``` - -### How it works - -#### The handler (shared by both patterns) - -`GreetingWorkflow` is a long-running "entity" workflow that holds the current language and a map of -greetings. It exposes its state through standard Temporal primitives: - -- `getLanguages` / `getLanguage` — `@QueryMethod`s for reading state -- `setLanguage` — an `@UpdateMethod` for switching between already-loaded languages -- `setLanguageUsingActivity` — an `@UpdateMethod` that calls an activity to fetch a greeting for - a language not yet in the map (uses `WorkflowLock` to serialize concurrent activity calls) -- `approve` — a `@SignalMethod` that lets the workflow complete - -The workflow waits until approved and all in-flight update handlers have finished, then returns the -greeting in the current language. - -Both Nexus service implementations translate incoming Nexus operations into calls against -`GreetingWorkflow` stubs — queries, updates, and signals. The caller never interacts with the -workflow directly. - -#### Entity pattern (`caller/` + `NexusGreetingService`) - -The handler worker starts a single `GreetingWorkflow` on boot with a fixed workflow ID. -`NexusGreetingServiceImpl` holds that workflow ID in its constructor and routes every operation to -it. The caller's inputs contain only business data (language, name), not workflow IDs. - -`CallerWorkflowImpl` creates a `NexusGreetingService` stub and: -1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`) -2. Changes the language to Arabic (`setLanguage` — backed by an `@UpdateMethod` that calls an activity) -3. Confirms the change via a second query (`getLanguage`) -4. Approves the workflow (`approve` — backed by a `@SignalMethod`) - -#### Remote-start pattern (`caller_remote/` + `NexusRemoteGreetingService`) - -No workflow is pre-started. Instead, `NexusRemoteGreetingService` adds a `runFromRemote` operation -that starts a new `GreetingWorkflow` with a caller-chosen workflow ID using -`WorkflowRunOperation`. Every other operation also includes the `workflowId` in its input so that -`NexusRemoteGreetingServiceImpl` can look up the right workflow stub. +| **Nexus service** | `NexusGreetingService` | `NexusRemoteGreetingService` | -`CallerRemoteWorkflowImpl` creates a `NexusRemoteGreetingService` stub and: -1. Starts a remote `GreetingWorkflow` via `runFromRemote` and waits for it to be running -2. Queries, updates, and approves that workflow — same operations as the entity pattern, but each - input carries the workflow ID -3. Waits for the remote workflow to complete and returns its result (the greeting string) +Each directory is fully self-contained with its own `service/`, `handler/`, and caller code. The +`GreetingWorkflow` and `GreetingWorkflowImpl` classes are **identical** between the two — only the +Nexus service interface and its implementation differ. This highlights that the same workflow can be +exposed through Nexus in different ways depending on whether the caller needs lifecycle control. -This pattern is useful when the caller needs to control the lifecycle of individual workflow -instances rather than sharing a single entity. +See each directory's README for running instructions. diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md new file mode 100644 index 00000000..9f876393 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md @@ -0,0 +1,57 @@ +## Entity pattern + +The handler worker starts a single `GreetingWorkflow` on boot with a fixed workflow ID. +`NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. The caller's +inputs contain only business data — no workflow IDs. + +The caller workflow: +1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`) +2. Changes the language to Arabic (`setLanguage` — backed by an `@UpdateMethod` that calls an activity) +3. Confirms the change via a second query (`getLanguage`) +4. Approves the workflow (`approve` — backed by a `@SignalMethod`) + +### Running + +Start a Temporal server: + +```bash +temporal server start-dev +``` + +Create the namespaces and Nexus endpoint: + +```bash +temporal operator namespace create --namespace nexus-messaging-handler-namespace +temporal operator namespace create --namespace nexus-messaging-caller-namespace + +temporal operator nexus endpoint create \ + --name nexus-messaging-nexus-endpoint \ + --target-namespace nexus-messaging-handler-namespace \ + --target-task-queue nexus-messaging-handler-task-queue +``` + +In one terminal, start the handler worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.callerpattern.handler.HandlerWorker +``` + +In a second terminal, start the caller worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.callerpattern.caller.CallerWorker +``` + +In a third terminal, start the caller workflow: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.callerpattern.caller.CallerStarter +``` + +Expected output: + +``` +Supported languages: [CHINESE, ENGLISH] +Language changed: ENGLISH -> ARABIC +Workflow approved +``` diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java index 95a3d025..7fd8b5aa 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller; +package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java index 8194503b..50cda0f4 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller; +package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java similarity index 76% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java index 6c830d7d..4bb91ce7 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.caller; +package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java similarity index 88% rename from core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java index dbea7843..3bfe1eaa 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java @@ -1,9 +1,8 @@ -package io.temporal.samples.nexus_messaging.caller; +package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_messaging.callerondemand.CallerRemoteWorkflowImpl; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; import io.temporal.workflow.Workflow; @@ -14,7 +13,7 @@ public class CallerWorkflowImpl implements CallerWorkflow { - private static final Logger logger = Workflow.getLogger(CallerRemoteWorkflowImpl.class); + private static final Logger logger = Workflow.getLogger(CallerWorkflowImpl.class); // The endpoint is configured at the worker level in CallerWorker; only operation options are // set here. diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivity.java similarity index 68% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivity.java index 83f97d1f..a7bfcd75 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivity.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivity.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; -import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; @ActivityInterface public interface GreetingActivity { diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java similarity index 83% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java index e7927eba..2a4b9492 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingActivityImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java @@ -1,6 +1,6 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; -import io.temporal.samples.nexus_messaging.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; import java.util.EnumMap; import java.util.Map; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java similarity index 89% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java index 786309f3..8cab7cf6 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; import io.temporal.workflow.QueryMethod; import io.temporal.workflow.SignalMethod; import io.temporal.workflow.UpdateMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java similarity index 92% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java index 0021607e..5ebe7e67 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; import io.temporal.activity.ActivityOptions; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; import io.temporal.workflow.Workflow; import java.time.Duration; import java.util.ArrayList; @@ -82,7 +82,6 @@ public void validateSetLanguage(NexusGreetingService.SetLanguageInput input) { @Override public Language setLanguageUsingActivity(NexusGreetingService.SetLanguageInput input) { if (!greetings.containsKey(input.getLanguage())) { - String greeting = greetingActivity.callGreetingService(input.getLanguage()); if (greeting == null) { throw ApplicationFailure.newFailure( diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java index e23fff61..1f0a8aac 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; @@ -43,7 +43,6 @@ public static void main(String[] args) throws InterruptedException { worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); worker.registerActivitiesImplementations(new GreetingActivityImpl()); worker.registerNexusServiceImplementation(new NexusGreetingServiceImpl(WORKFLOW_ID)); - worker.registerNexusServiceImplementation(new NexusRemoteGreetingServiceImpl()); factory.start(); logger.info("Handler worker started, ctrl+c to exit"); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java similarity index 74% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java index 0926f6ce..1f9bc87b 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java @@ -1,11 +1,11 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.callerpattern.handler; import io.nexusrpc.handler.OperationHandler; import io.nexusrpc.handler.OperationImpl; import io.nexusrpc.handler.ServiceImpl; import io.temporal.nexus.Nexus; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; +import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +31,6 @@ private GreetingWorkflow getWorkflowStub() { .newWorkflowStub(GreetingWorkflow.class, workflowId); } - // 👉 Backed by a query against the long-running entity workflow. @OperationImpl public OperationHandler< NexusGreetingService.GetLanguagesInput, NexusGreetingService.GetLanguagesOutput> @@ -43,7 +42,6 @@ private GreetingWorkflow getWorkflowStub() { }); } - // 👉 Backed by a query against the long-running entity workflow. @OperationImpl public OperationHandler getLanguage() { return OperationHandler.sync( @@ -53,11 +51,8 @@ public OperationHandler getLang }); } - // 👉 Backed by an update against the long-running entity workflow. Routes to - // setLanguageUsingActivity (not setLanguage) so that new languages not already in the greetings - // map can be fetched via an activity. Although updates can run for an arbitrarily long time, when - // exposed via a sync Nexus operation the update should complete quickly (sync operations must - // finish in under 10s). + // Routes to setLanguageUsingActivity (not setLanguage) so that new languages not already in the + // greetings map can be fetched via an activity. @OperationImpl public OperationHandler setLanguage() { return OperationHandler.sync( @@ -67,7 +62,6 @@ public OperationHandler setLang }); } - // 👉 Backed by a signal against the long-running entity workflow. @OperationImpl public OperationHandler approve() { diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/Language.java similarity index 60% rename from core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/Language.java index 3c81c7c5..9ca70913 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/service/Language.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/Language.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.service; +package io.temporal.samples.nexus_messaging.callerpattern.service; public enum Language { ARABIC, diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java index 841efb76..70966b67 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.service; +package io.temporal.samples.nexus_messaging.callerpattern.service; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md new file mode 100644 index 00000000..fbf3fde7 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md @@ -0,0 +1,66 @@ +## On-demand pattern + +No workflow is pre-started. The caller creates and controls workflow instances through Nexus +operations. `NexusRemoteGreetingService` adds a `runFromRemote` operation that starts a new +`GreetingWorkflow`, and every other operation includes a `workflowId` so the handler knows which +instance to target. + +The caller workflow: +1. Starts two remote `GreetingWorkflow` instances via `runFromRemote` (backed by `WorkflowRunOperation`) +2. Queries each for supported languages +3. Changes the language on each (Arabic and Hindi) +4. Confirms the changes via queries +5. Approves both workflows +6. Waits for each to complete and returns their results + +### Running + +Start a Temporal server: + +```bash +temporal server start-dev +``` + +Create the namespaces and Nexus endpoint: + +```bash +temporal operator namespace create --namespace nexus-messaging-handler-namespace +temporal operator namespace create --namespace nexus-messaging-caller-namespace + +temporal operator nexus endpoint create \ + --name nexus-messaging-nexus-endpoint \ + --target-namespace nexus-messaging-handler-namespace \ + --target-task-queue nexus-messaging-handler-task-queue +``` + +In one terminal, start the handler worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.ondemandpattern.handler.HandlerWorker +``` + +In a second terminal, start the caller worker: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.ondemandpattern.caller.CallerRemoteWorker +``` + +In a third terminal, start the caller workflow: + +```bash +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.ondemandpattern.caller.CallerRemoteStarter +``` + +Expected output: + +``` +started remote greeting workflow: UserId One +started remote greeting workflow: UserId Two +Supported languages for UserId One: [CHINESE, ENGLISH] +Supported languages for UserId Two: [CHINESE, ENGLISH] +UserId One changed language: ENGLISH -> ARABIC +UserId Two changed language: ENGLISH -> HINDI +Workflows approved +Workflow one result: مرحبا بالعالم +Workflow two result: नमस्ते दुनिया +``` diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java index 670d6123..35be6ff0 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerondemand; +package io.temporal.samples.nexus_messaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java index 49dad341..832a2f15 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerondemand; +package io.temporal.samples.nexus_messaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java similarity index 76% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java index b707fa3b..5f3485f2 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerondemand; +package io.temporal.samples.nexus_messaging.ondemandpattern.caller; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java similarity index 87% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java index 61bdee13..25997bcb 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerondemand/CallerRemoteWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java @@ -1,8 +1,7 @@ -package io.temporal.samples.nexus_messaging.callerondemand; +package io.temporal.samples.nexus_messaging.ondemandpattern.caller; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; -import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; import io.temporal.workflow.NexusOperationHandle; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; @@ -65,9 +64,7 @@ public List run() { logger.info("started remote greeting workflow {}", REMOTE_WORKFLOW_TWO); // Query the remote workflow for supported languages. - // Output types (e.g. GetLanguagesOutput) are defined on NexusGreetingService and shared by - // both service interfaces. - NexusGreetingService.GetLanguagesOutput languagesOutput = + NexusRemoteGreetingService.GetLanguagesOutput languagesOutput = greetingRemoteServiceOne.getLanguages( new NexusRemoteGreetingService.GetLanguagesInput(false, REMOTE_WORKFLOW_ONE)); log.add( @@ -107,10 +104,10 @@ public List run() { + " -> " + currentLanguage.name()); logger.info( - "Language changed from {} to {} for workflow {}", - previousLanguageOne, - currentLanguage, - REMOTE_WORKFLOW_ONE); + "Language changed from {} to {} for workflow {}", + previousLanguageOne, + currentLanguage, + REMOTE_WORKFLOW_ONE); currentLanguage = greetingRemoteServiceTwo.getLanguage( @@ -122,10 +119,10 @@ public List run() { + " -> " + currentLanguage.name()); logger.info( - "Language changed from {} to {} for workflow {}", - previousLanguageTwo, - currentLanguage, - REMOTE_WORKFLOW_TWO); + "Language changed from {} to {} for workflow {}", + previousLanguageTwo, + currentLanguage, + REMOTE_WORKFLOW_TWO); // Approve the remote workflow so it can complete. greetingRemoteServiceOne.approve( diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java new file mode 100644 index 00000000..aa69c643 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java @@ -0,0 +1,12 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; + +@ActivityInterface +public interface GreetingActivity { + // Simulates a call to a remote greeting service. Returns null if the language is not supported. + @ActivityMethod + String callGreetingService(Language language); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java new file mode 100644 index 00000000..354e8dfa --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java @@ -0,0 +1,25 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; + +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import java.util.EnumMap; +import java.util.Map; + +public class GreetingActivityImpl implements GreetingActivity { + + private static final Map GREETINGS = new EnumMap<>(Language.class); + + static { + GREETINGS.put(Language.ARABIC, "مرحبا بالعالم"); + GREETINGS.put(Language.CHINESE, "你好,世界"); + GREETINGS.put(Language.ENGLISH, "Hello, world"); + GREETINGS.put(Language.FRENCH, "Bonjour, monde"); + GREETINGS.put(Language.HINDI, "नमस्ते दुनिया"); + GREETINGS.put(Language.PORTUGUESE, "Olá mundo"); + GREETINGS.put(Language.SPANISH, "Hola mundo"); + } + + @Override + public String callGreetingService(Language language) { + return GREETINGS.get(language); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java new file mode 100644 index 00000000..1c37564a --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java @@ -0,0 +1,90 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; +import io.temporal.workflow.QueryMethod; +import io.temporal.workflow.SignalMethod; +import io.temporal.workflow.UpdateMethod; +import io.temporal.workflow.UpdateValidatorMethod; +import io.temporal.workflow.WorkflowInterface; +import io.temporal.workflow.WorkflowMethod; + +/** + * A long-running "entity" workflow that backs the NexusRemoteGreetingService Nexus operations. The + * workflow exposes queries, an update, and a signal. These are private implementation details of + * the Nexus service: the caller only interacts via Nexus operations. + */ +@WorkflowInterface +public interface GreetingWorkflow { + + class ApproveInput { + private final String name; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public ApproveInput(@JsonProperty("name") String name) { + this.name = name; + } + + @JsonProperty("name") + public String getName() { + return name; + } + } + + class GetLanguagesInput { + private final boolean includeUnsupported; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguagesInput(@JsonProperty("includeUnsupported") boolean includeUnsupported) { + this.includeUnsupported = includeUnsupported; + } + + @JsonProperty("includeUnsupported") + public boolean isIncludeUnsupported() { + return includeUnsupported; + } + } + + class SetLanguageInput { + private final Language language; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SetLanguageInput(@JsonProperty("language") Language language) { + this.language = language; + } + + @JsonProperty("language") + public Language getLanguage() { + return language; + } + } + + @WorkflowMethod + String run(); + + // Returns the languages currently supported by the workflow. + @QueryMethod + NexusRemoteGreetingService.GetLanguagesOutput getLanguages(GetLanguagesInput input); + + // Returns the currently active language. + @QueryMethod + Language getLanguage(); + + // Approves the workflow, allowing it to complete. + @SignalMethod + void approve(ApproveInput input); + + // Changes the active language synchronously (only supports languages already in the greetings + // map). + @UpdateMethod + Language setLanguage(SetLanguageInput input); + + @UpdateValidatorMethod(updateName = "setLanguage") + void validateSetLanguage(SetLanguageInput input); + + // Changes the active language, calling an activity to fetch a greeting for new languages. + @UpdateMethod + Language setLanguageUsingActivity(SetLanguageInput input); +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java new file mode 100644 index 00000000..499069de --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java @@ -0,0 +1,98 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; + +import io.temporal.activity.ActivityOptions; +import io.temporal.failure.ApplicationFailure; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; +import io.temporal.workflow.Workflow; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; + +public class GreetingWorkflowImpl implements GreetingWorkflow { + + private static final Logger logger1 = Workflow.getLogger(GreetingWorkflowImpl.class); + private boolean approvedForRelease = false; + private final Map greetings = new EnumMap<>(Language.class); + private Language language = Language.ENGLISH; + + private static final Logger logger = logger1; + + private final GreetingActivity greetingActivity = + Workflow.newActivityStub( + GreetingActivity.class, + ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); + + public GreetingWorkflowImpl() { + greetings.put(Language.CHINESE, "你好,世界"); + greetings.put(Language.ENGLISH, "Hello, world"); + } + + @Override + public String run() { + // Wait until approved and all in-flight update handlers have finished. + Workflow.await(() -> approvedForRelease && Workflow.isEveryHandlerFinished()); + return greetings.get(language); + } + + @Override + public NexusRemoteGreetingService.GetLanguagesOutput getLanguages( + GreetingWorkflow.GetLanguagesInput input) { + List result; + if (input.isIncludeUnsupported()) { + result = new ArrayList<>(Arrays.asList(Language.values())); + } else { + result = new ArrayList<>(greetings.keySet()); + } + Collections.sort(result); + return new NexusRemoteGreetingService.GetLanguagesOutput(result); + } + + @Override + public Language getLanguage() { + return language; + } + + @Override + public void approve(ApproveInput input) { + logger.info("Approval signal received"); + approvedForRelease = true; + } + + @Override + public Language setLanguage(GreetingWorkflow.SetLanguageInput input) { + logger.info("setLanguage update received"); + Language previous = language; + language = input.getLanguage(); + return previous; + } + + @Override + public void validateSetLanguage(GreetingWorkflow.SetLanguageInput input) { + logger.info("validateSetLanguage called"); + if (!greetings.containsKey(input.getLanguage())) { + throw new IllegalArgumentException(input.getLanguage().name() + " is not supported"); + } + } + + @Override + public Language setLanguageUsingActivity(GreetingWorkflow.SetLanguageInput input) { + if (!greetings.containsKey(input.getLanguage())) { + String greeting = greetingActivity.callGreetingService(input.getLanguage()); + if (greeting == null) { + throw ApplicationFailure.newFailure( + "Greeting service does not support " + input.getLanguage().name(), + "UnsupportedLanguage"); + } + greetings.put(input.getLanguage(), greeting); + } + Language previous = language; + language = input.getLanguage(); + return previous; + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java new file mode 100644 index 00000000..5cc1f645 --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java @@ -0,0 +1,33 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; + +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowClientOptions; +import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HandlerWorker { + private static final Logger logger = LoggerFactory.getLogger(HandlerWorker.class); + + public static final String NAMESPACE = "nexus-messaging-handler-namespace"; + public static final String TASK_QUEUE = "nexus-messaging-handler-task-queue"; + + public static void main(String[] args) throws InterruptedException { + WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); + WorkflowClient client = + WorkflowClient.newInstance( + service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + + WorkerFactory factory = WorkerFactory.newInstance(client); + Worker worker = factory.newWorker(TASK_QUEUE); + worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); + worker.registerActivitiesImplementations(new GreetingActivityImpl()); + worker.registerNexusServiceImplementation(new NexusRemoteGreetingServiceImpl()); + + factory.start(); + logger.info("Handler worker started, ctrl+c to exit"); + Thread.currentThread().join(); + } +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java similarity index 77% rename from core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java index 42867f58..69d0192e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/handler/NexusRemoteGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.handler; +package io.temporal.samples.nexus_messaging.ondemandpattern.handler; import io.nexusrpc.handler.OperationHandler; import io.nexusrpc.handler.OperationImpl; @@ -7,16 +7,14 @@ import io.temporal.nexus.Nexus; import io.temporal.nexus.WorkflowHandle; import io.temporal.nexus.WorkflowRunOperation; -import io.temporal.samples.nexus_messaging.service.Language; -import io.temporal.samples.nexus_messaging.service.NexusGreetingService; -import io.temporal.samples.nexus_messaging.service.NexusRemoteGreetingService; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Nexus operation handler for the remote-start pattern. Unlike {@link NexusGreetingServiceImpl}, - * this implementation does not hold a fixed workflow ID. Instead, each operation receives the - * target workflow ID in its input, and {@code runFromRemote} starts a brand-new GreetingWorkflow. + * Nexus operation handler for the on-demand pattern. Each operation receives the target workflow ID + * in its input, and {@code runFromRemote} starts a brand-new GreetingWorkflow. */ @ServiceImpl(service = NexusRemoteGreetingService.class) public class NexusRemoteGreetingServiceImpl { @@ -57,14 +55,14 @@ public OperationHandler r @OperationImpl public OperationHandler< - NexusRemoteGreetingService.GetLanguagesInput, NexusGreetingService.GetLanguagesOutput> + NexusRemoteGreetingService.GetLanguagesInput, + NexusRemoteGreetingService.GetLanguagesOutput> getLanguages() { return OperationHandler.sync( (ctx, details, input) -> { logger.info("Query for GetLanguages was received for workflow {}", input.getWorkflowId()); return getWorkflowStub(input.getWorkflowId()) - .getLanguages( - new NexusGreetingService.GetLanguagesInput(input.isIncludeUnsupported())); + .getLanguages(new GreetingWorkflow.GetLanguagesInput(input.isIncludeUnsupported())); }); } @@ -84,21 +82,20 @@ public OperationHandler s (ctx, details, input) -> { logger.info("Update for SetLanguage was received for workflow {}", input.getWorkflowId()); return getWorkflowStub(input.getWorkflowId()) - .setLanguageUsingActivity( - new NexusGreetingService.SetLanguageInput(input.getLanguage())); + .setLanguageUsingActivity(new GreetingWorkflow.SetLanguageInput(input.getLanguage())); }); } @OperationImpl public OperationHandler< - NexusRemoteGreetingService.ApproveInput, NexusGreetingService.ApproveOutput> + NexusRemoteGreetingService.ApproveInput, NexusRemoteGreetingService.ApproveOutput> approve() { return OperationHandler.sync( (ctx, details, input) -> { logger.info("Signal for Approve was received for workflow {}", input.getWorkflowId()); getWorkflowStub(input.getWorkflowId()) .approve(new GreetingWorkflow.ApproveInput(input.getName())); - return new NexusGreetingService.ApproveOutput(); + return new NexusRemoteGreetingService.ApproveOutput(); }); } } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java new file mode 100644 index 00000000..bb23915f --- /dev/null +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java @@ -0,0 +1,11 @@ +package io.temporal.samples.nexus_messaging.ondemandpattern.service; + +public enum Language { + ARABIC, + CHINESE, + ENGLISH, + FRENCH, + HINDI, + PORTUGUESE, + SPANISH +} diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java similarity index 78% rename from core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java rename to core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java index 60447a61..42f9ecf6 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/service/NexusRemoteGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java @@ -1,15 +1,16 @@ -package io.temporal.samples.nexus_messaging.service; +package io.temporal.samples.nexus_messaging.ondemandpattern.service; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import io.nexusrpc.Operation; import io.nexusrpc.Service; +import java.util.List; /** - * Nexus service definition for the remote-start pattern. Unlike {@link NexusGreetingService}, every - * operation includes a {@code workflowId} so the caller controls which workflow instance is - * targeted. This also exposes a {@code runFromRemote} operation that starts a new GreetingWorkflow. + * Nexus service definition for the on-demand pattern. Every operation includes a {@code workflowId} + * so the caller controls which workflow instance is targeted. This also exposes a {@code + * runFromRemote} operation that starts a new GreetingWorkflow. */ @Service public interface NexusRemoteGreetingService { @@ -111,6 +112,26 @@ public String getWorkflowId() { } } + class GetLanguagesOutput { + private final List languages; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguagesOutput(@JsonProperty("languages") List languages) { + this.languages = languages; + } + + @JsonProperty("languages") + public List getLanguages() { + return languages; + } + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + class ApproveOutput { + @JsonCreator + public ApproveOutput() {} + } + // Starts a new GreetingWorkflow with the given workflow ID. This is an asynchronous Nexus // operation: the caller receives a handle and can wait for the workflow to complete. @Operation @@ -118,7 +139,7 @@ public String getWorkflowId() { // Returns the languages supported by the specified workflow. @Operation - NexusGreetingService.GetLanguagesOutput getLanguages(GetLanguagesInput input); + GetLanguagesOutput getLanguages(GetLanguagesInput input); // Returns the currently active language of the specified workflow. @Operation @@ -130,5 +151,5 @@ public String getWorkflowId() { // Approves the specified workflow, allowing it to complete. @Operation - NexusGreetingService.ApproveOutput approve(ApproveInput input); + ApproveOutput approve(ApproveInput input); } From 4bfdba969571e87a48e16f93134b68bd1874f9b0 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Thu, 9 Apr 2026 17:56:02 -0700 Subject: [PATCH 08/10] Tinkering --- .../nexus_messaging/callerpattern/README.md | 2 +- .../callerpattern/caller/CallerStarter.java | 2 +- .../callerpattern/caller/CallerWorkflow.java | 2 +- .../caller/CallerWorkflowImpl.java | 11 ++--- .../handler/GreetingWorkflow.java | 18 +------- .../handler/GreetingWorkflowImpl.java | 14 ++++-- .../callerpattern/handler/HandlerWorker.java | 13 +++--- .../handler/NexusGreetingServiceImpl.java | 30 ++++++------- .../service/NexusGreetingService.java | 44 ++++++++++++++++--- 9 files changed, 81 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md index 9f876393..4e3a78bc 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md @@ -1,6 +1,6 @@ ## Entity pattern -The handler worker starts a single `GreetingWorkflow` on boot with a fixed workflow ID. +The handler worker starts a single `GreetingWorkflow` with a fixed workflow ID. `NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. The caller's inputs contain only business data — no workflow IDs. diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java index 7fd8b5aa..d771f545 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java @@ -24,7 +24,7 @@ public static void main(String[] args) { .setTaskQueue(CallerWorker.TASK_QUEUE) .build()); - List log = workflow.run(); + List log = workflow.run("user-1"); log.forEach(System.out::println); } } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java index 4bb91ce7..1f6fa1f8 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java @@ -7,5 +7,5 @@ @WorkflowInterface public interface CallerWorkflow { @WorkflowMethod - List run(); + List run(String userId); } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java index 3bfe1eaa..903ae2be 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java @@ -28,22 +28,23 @@ public class CallerWorkflowImpl implements CallerWorkflow { .build()); @Override - public List run() { + public List run(String userId) { List log = new ArrayList<>(); // 👉 Call a Nexus operation backed by a query against the entity workflow. NexusGreetingService.GetLanguagesOutput languagesOutput = - greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false)); + greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false, userId)); log.add("Supported languages: " + languagesOutput.getLanguages()); logger.info("Supported languages: {}", languagesOutput.getLanguages()); // 👉 Call a Nexus operation backed by an update against the entity workflow. Language previousLanguage = - greetingService.setLanguage(new NexusGreetingService.SetLanguageInput(Language.ARABIC)); + greetingService.setLanguage( + new NexusGreetingService.SetLanguageInput(Language.ARABIC, userId)); // 👉 Call a Nexus operation backed by a query to confirm the language change. Language currentLanguage = - greetingService.getLanguage(new NexusGreetingService.GetLanguageInput()); + greetingService.getLanguage(new NexusGreetingService.GetLanguageInput(userId)); if (currentLanguage != Language.ARABIC) { throw ApplicationFailure.newFailure( "Expected language ARABIC, got " + currentLanguage, "AssertionError"); @@ -53,7 +54,7 @@ public List run() { logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); // 👉 Call a Nexus operation backed by a signal against the entity workflow. - greetingService.approve(new NexusGreetingService.ApproveInput("caller")); + greetingService.approve(new NexusGreetingService.ApproveInput("caller", userId)); log.add("Workflow approved"); logger.info("Workflow approved"); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java index 8cab7cf6..6b12720f 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java @@ -1,7 +1,5 @@ package io.temporal.samples.nexus_messaging.callerpattern.handler; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; import io.temporal.samples.nexus_messaging.callerpattern.service.Language; import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; import io.temporal.workflow.QueryMethod; @@ -19,20 +17,6 @@ @WorkflowInterface public interface GreetingWorkflow { - class ApproveInput { - private final String name; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public ApproveInput(@JsonProperty("name") String name) { - this.name = name; - } - - @JsonProperty("name") - public String getName() { - return name; - } - } - @WorkflowMethod String run(); @@ -47,7 +31,7 @@ NexusGreetingService.GetLanguagesOutput getLanguages( // Approves the workflow, allowing it to complete. @SignalMethod - void approve(ApproveInput input); + void approve(NexusGreetingService.ApproveInput input); // Changes the active language synchronously (only supports languages already in the greetings // map). diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java index 5ebe7e67..a6ab634e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java @@ -58,14 +58,18 @@ public Language getLanguage() { } @Override - public void approve(ApproveInput input) { - logger.info("Approval signal received"); + public void approve(NexusGreetingService.ApproveInput input) { + logger.info( + "Approval signal received for workflow {}", + NexusGreetingServiceImpl.getWorkflowId(input.getUserId())); approvedForRelease = true; } @Override public Language setLanguage(NexusGreetingService.SetLanguageInput input) { - logger.info("setLanguage update received"); + logger.info( + "setLanguage update received for workflow {}", + NexusGreetingServiceImpl.getWorkflowId(input.getUserId())); Language previous = language; language = input.getLanguage(); return previous; @@ -73,7 +77,9 @@ public Language setLanguage(NexusGreetingService.SetLanguageInput input) { @Override public void validateSetLanguage(NexusGreetingService.SetLanguageInput input) { - logger.info("validateSetLanguage called"); + logger.info( + "validateSetLanguage called for workflow {}", + NexusGreetingServiceImpl.getWorkflowId(input.getUserId())); if (!greetings.containsKey(input.getLanguage())) { throw new IllegalArgumentException(input.getLanguage().name() + " is not supported"); } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java index 1f0a8aac..5f90042e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java @@ -15,7 +15,7 @@ public class HandlerWorker { public static final String NAMESPACE = "nexus-messaging-handler-namespace"; public static final String TASK_QUEUE = "nexus-messaging-handler-task-queue"; - static final String WORKFLOW_ID = "nexus-messaging-greeting-workflow"; + static final String USER_ID = "user-1"; public static void main(String[] args) throws InterruptedException { WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); @@ -24,25 +24,28 @@ public static void main(String[] args) throws InterruptedException { service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); // Start the long-running entity workflow that backs the Nexus service, if not already running. + // The workflow ID is derived from the user ID using the same prefix as + // NexusGreetingServiceImpl. + String workflowId = NexusGreetingServiceImpl.getWorkflowId(USER_ID); GreetingWorkflow greetingWorkflow = client.newWorkflowStub( GreetingWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId(WORKFLOW_ID) + .setWorkflowId(workflowId) .setTaskQueue(TASK_QUEUE) .build()); try { WorkflowClient.start(greetingWorkflow::run); - logger.info("Started greeting workflow: {}", WORKFLOW_ID); + logger.info("Started greeting workflow: {}", workflowId); } catch (WorkflowExecutionAlreadyStarted e) { - logger.info("Greeting workflow already running: {}", WORKFLOW_ID); + logger.info("Greeting workflow already running: {}", workflowId); } WorkerFactory factory = WorkerFactory.newInstance(client); Worker worker = factory.newWorker(TASK_QUEUE); worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class); worker.registerActivitiesImplementations(new GreetingActivityImpl()); - worker.registerNexusServiceImplementation(new NexusGreetingServiceImpl(WORKFLOW_ID)); + worker.registerNexusServiceImplementation(new NexusGreetingServiceImpl()); factory.start(); logger.info("Handler worker started, ctrl+c to exit"); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java index 1f9bc87b..90bba6d9 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java @@ -10,8 +10,8 @@ import org.slf4j.LoggerFactory; /** - * Nexus operation handler implementation. Each operation is backed by the long-running - * GreetingWorkflow entity. The operations are synchronous (sync_operation) because queries and + * Nexus operation handler implementation. Each operation receives a userId, which is mapped to a + * workflow ID using {@link #WORKFLOW_ID_PREFIX}. The operations are synchronous because queries and * updates against a running workflow complete quickly. */ @ServiceImpl(service = NexusGreetingService.class) @@ -19,16 +19,16 @@ public class NexusGreetingServiceImpl { private static final Logger logger = LoggerFactory.getLogger(NexusGreetingServiceImpl.class); - private final String workflowId; + static final String WORKFLOW_ID_PREFIX = "GreetingWorkflow_for_"; - public NexusGreetingServiceImpl(String workflowId) { - this.workflowId = workflowId; + public static String getWorkflowId(String userId) { + return WORKFLOW_ID_PREFIX + userId; } - private GreetingWorkflow getWorkflowStub() { + private GreetingWorkflow getWorkflowStub(String userId) { return Nexus.getOperationContext() .getWorkflowClient() - .newWorkflowStub(GreetingWorkflow.class, workflowId); + .newWorkflowStub(GreetingWorkflow.class, getWorkflowId(userId)); } @OperationImpl @@ -37,8 +37,8 @@ private GreetingWorkflow getWorkflowStub() { getLanguages() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Query for GetLanguages was received"); - return getWorkflowStub().getLanguages(input); + logger.info("Query for GetLanguages was received for user {}", input.getUserId()); + return getWorkflowStub(input.getUserId()).getLanguages(input); }); } @@ -46,8 +46,8 @@ private GreetingWorkflow getWorkflowStub() { public OperationHandler getLanguage() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Query for GetLanguage was received"); - return getWorkflowStub().getLanguage(); + logger.info("Query for GetLanguage was received for user {}", input.getUserId()); + return getWorkflowStub(input.getUserId()).getLanguage(); }); } @@ -57,8 +57,8 @@ public OperationHandler getLang public OperationHandler setLanguage() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Update for SetLanguage was received"); - return getWorkflowStub().setLanguageUsingActivity(input); + logger.info("Update for SetLanguage was received for user {}", input.getUserId()); + return getWorkflowStub(input.getUserId()).setLanguageUsingActivity(input); }); } @@ -67,8 +67,8 @@ public OperationHandler setLang approve() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Signal for Approve was received"); - getWorkflowStub().approve(new GreetingWorkflow.ApproveInput(input.getName())); + logger.info("Signal for Approve was received for user {}", input.getUserId()); + getWorkflowStub(input.getUserId()).approve(input); return new NexusGreetingService.ApproveOutput(); }); } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java index 70966b67..f42149c4 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java @@ -16,16 +16,25 @@ public interface NexusGreetingService { class GetLanguagesInput { private final boolean includeUnsupported; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public GetLanguagesInput(@JsonProperty("includeUnsupported") boolean includeUnsupported) { + public GetLanguagesInput( + @JsonProperty("includeUnsupported") boolean includeUnsupported, + @JsonProperty("userId") String userId) { this.includeUnsupported = includeUnsupported; + this.userId = userId; } @JsonProperty("includeUnsupported") public boolean isIncludeUnsupported() { return includeUnsupported; } + + @JsonProperty("userId") + public String getUserId() { + return userId; + } } class GetLanguagesOutput { @@ -42,24 +51,39 @@ public List getLanguages() { } } - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class GetLanguageInput { - @JsonCreator - public GetLanguageInput() {} + private final String userId; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GetLanguageInput(@JsonProperty("userId") String userId) { + this.userId = userId; + } + + @JsonProperty("userId") + public String getUserId() { + return userId; + } } class ApproveInput { private final String name; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public ApproveInput(@JsonProperty("name") String name) { + public ApproveInput(@JsonProperty("name") String name, @JsonProperty("userId") String userId) { this.name = name; + this.userId = userId; } @JsonProperty("name") public String getName() { return name; } + + @JsonProperty("userId") + public String getUserId() { + return userId; + } } @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) @@ -70,16 +94,24 @@ public ApproveOutput() {} class SetLanguageInput { private final Language language; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public SetLanguageInput(@JsonProperty("language") Language language) { + public SetLanguageInput( + @JsonProperty("language") Language language, @JsonProperty("userId") String userId) { this.language = language; + this.userId = userId; } @JsonProperty("language") public Language getLanguage() { return language; } + + @JsonProperty("userId") + public String getUserId() { + return userId; + } } // Returns the languages supported by the greeting workflow. From 050f0c4f9fa1c561fa977dc7a2c43007f8669b69 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Fri, 10 Apr 2026 12:55:57 -0700 Subject: [PATCH 09/10] Reviewing and some updates --- .../temporal/samples/nexus_messaging/README.md | 16 ++++++++-------- .../nexus_messaging/callerpattern/README.md | 2 ++ .../callerpattern/caller/CallerStarter.java | 2 ++ .../caller/CallerWorkflowImpl.java | 18 ++++++++++++++---- .../callerpattern/handler/HandlerWorker.java | 8 ++++++-- .../handler/NexusGreetingServiceImpl.java | 4 ++++ .../caller/CallerRemoteWorkflowImpl.java | 17 +++++++++++++++-- .../NexusRemoteGreetingServiceImpl.java | 5 ----- 8 files changed, 51 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md index fd047d66..0ff69765 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/README.md @@ -1,15 +1,15 @@ This sample shows how to expose a long-running workflow's queries, updates, and signals as Nexus operations. There are two self-contained examples, each in its own directory: -| | `callerpattern/` | `ondemandpattern/` | -|---|---|---| -| **Pattern** | Signal an existing workflow | Create and run workflows on demand | -| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a Nexus operation | -| **Who knows the workflow ID?** | Only the handler | The caller chooses and passes it in every operation | -| **Nexus service** | `NexusGreetingService` | `NexusRemoteGreetingService` | +| | `callerpattern/` | `ondemandpattern/` | +|---|---|--------------------------------------------------------------| +| **Pattern** | Signal an existing workflow | Create and run workflows on demand, and send signals to them | +| **Who creates the workflow?** | The handler worker starts it on boot | The caller starts it via a Nexus operation | +| **Who knows the workflow ID?** | Only the handler | The caller chooses and passes it in every operation | +| **Nexus service** | `NexusGreetingService` | `NexusRemoteGreetingService` | -Each directory is fully self-contained with its own `service/`, `handler/`, and caller code. The -`GreetingWorkflow` and `GreetingWorkflowImpl` classes are **identical** between the two — only the +Each directory is fully self-contained for clarity. The +`GreetingWorkflow`, `GreetingWorkflowImpl`, `GreetingActivity` and `GreetingActivityImpl` classes are **identical** between the two — only the Nexus service interface and its implementation differ. This highlights that the same workflow can be exposed through Nexus in different ways depending on whether the caller needs lifecycle control. diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md index 4e3a78bc..e7b5134e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md @@ -4,6 +4,8 @@ The handler worker starts a single `GreetingWorkflow` with a fixed workflow ID. `NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. The caller's inputs contain only business data — no workflow IDs. +Refer to the on demand pattern examples and you will see how to expand this example to not just a single fixed workflow ID - the NexusGreetingServiceImpl simply needs to be able to map something from the inputs to a Workflow ID. For example if the workflow ID is a user ID with a prefix, then if the Nexus inputs include a User ID, then Nexus can simple prepend the same string to get the desired workflow ID. + The caller workflow: 1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`) 2. Changes the language to Arabic (`setLanguage` — backed by an `@UpdateMethod` that calls an activity) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java index d771f545..e947eb6c 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java @@ -24,6 +24,8 @@ public static void main(String[] args) { .setTaskQueue(CallerWorker.TASK_QUEUE) .build()); + // Launch the worker, passing in an identifier which the Nexus service will use + // to find the matching workflow (See NexusGreetingServiceImpl::getWorkflowId) List log = workflow.run("user-1"); log.forEach(System.out::println); } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java index 903ae2be..31958a26 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java @@ -29,20 +29,30 @@ public class CallerWorkflowImpl implements CallerWorkflow { @Override public List run(String userId) { + + // Messages in the log array are passed back to the caller who will then log them to report what + // is happening. + // The same message is also logged for demo purposes, so that things are visible in the caller + // workflow output. List log = new ArrayList<>(); - // 👉 Call a Nexus operation backed by a query against the entity workflow. + // Call a Nexus operation backed by a query against the entity workflow. + // The workflow must already be running on the handler, otherwise you will + // get an error saying the workflow has already terminated. NexusGreetingService.GetLanguagesOutput languagesOutput = greetingService.getLanguages(new NexusGreetingService.GetLanguagesInput(false, userId)); log.add("Supported languages: " + languagesOutput.getLanguages()); logger.info("Supported languages: {}", languagesOutput.getLanguages()); - // 👉 Call a Nexus operation backed by an update against the entity workflow. + // Following are examples for each of the three messaging types - + // update, query, then signal. + + // Call a Nexus operation backed by an update against the entity workflow. Language previousLanguage = greetingService.setLanguage( new NexusGreetingService.SetLanguageInput(Language.ARABIC, userId)); - // 👉 Call a Nexus operation backed by a query to confirm the language change. + // Call a Nexus operation backed by a query to confirm the language change. Language currentLanguage = greetingService.getLanguage(new NexusGreetingService.GetLanguageInput(userId)); if (currentLanguage != Language.ARABIC) { @@ -53,7 +63,7 @@ public List run(String userId) { log.add("Language changed: " + previousLanguage.name() + " -> " + Language.ARABIC.name()); logger.info("Language changed from {} to {}", previousLanguage, Language.ARABIC); - // 👉 Call a Nexus operation backed by a signal against the entity workflow. + // Call a Nexus operation backed by a signal against the entity workflow. greetingService.approve(new NexusGreetingService.ApproveInput("caller", userId)); log.add("Workflow approved"); logger.info("Workflow approved"); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java index 5f90042e..f4d4ce35 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java @@ -24,9 +24,13 @@ public static void main(String[] args) throws InterruptedException { service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); // Start the long-running entity workflow that backs the Nexus service, if not already running. - // The workflow ID is derived from the user ID using the same prefix as - // NexusGreetingServiceImpl. + // Create a workflow ID derived from the given user ID. + // This would be for a process that would create a workflow for each UserID, + // if you had a single long running workflow for all users then you could + // remove all the USER_IDs from the inputs and just make everything refer + // to a single workflow ID. String workflowId = NexusGreetingServiceImpl.getWorkflowId(USER_ID); + GreetingWorkflow greetingWorkflow = client.newWorkflowStub( GreetingWorkflow.class, diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java index 90bba6d9..c4c8c31d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java @@ -21,6 +21,10 @@ public class NexusGreetingServiceImpl { static final String WORKFLOW_ID_PREFIX = "GreetingWorkflow_for_"; + // This example assumes you might have multiple workflows, one for each user. + // If you had a single workflow for all users, then you could remove the + // getWorkflowId method, remove the user ID from each input, and just + // use the single worflow ID in the getWorkflowStub method below. public static String getWorkflowId(String userId) { return WORKFLOW_ID_PREFIX + userId; } diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java index 25997bcb..6b4fe599 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java @@ -15,7 +15,10 @@ public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { private static final Logger logger = Workflow.getLogger(CallerRemoteWorkflowImpl.class); - // private static final String REMOTE_WORKFLOW_ID = "nexus-messaging-remote-greeting-workflow"; + // This is going to create two workflows and send messages to them. + // We need to have an ID to differentiate so that Nexus knows how to name + // a workflow and then how to know the correct destination workflow. + // So here we are just going to define two workflow IDs with different user IDs. private static final String REMOTE_WORKFLOW_ONE = "UserId One"; private static final String REMOTE_WORKFLOW_TWO = "UserId Two"; @@ -40,9 +43,19 @@ public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { @Override public List run() { + // Messages in the log array are passed back to the caller who will then log them to report what + // is happening. + // The same message is also logged for demo purposes, so that things are visible in the caller + // workflow output. List log = new ArrayList<>(); - // 👉 Async Nexus operation — starts a workflow on the handler and returns a handle. + // Each call is performed twice in this example. This assumes there are two users we want + // to process. The first call starts two workflows, one for each user. + // Subsequent calls perform different actions between the two users. + // There are examples for each of the three messaging types - + // update, query, then signal. + + // This is an Async Nexus operation — starts a workflow on the handler and returns a handle. // Unlike the sync operations below (getLanguages, setLanguage, etc.), this does not block // until the workflow completes. It is backed by WorkflowRunOperation on the handler side. NexusOperationHandle handleOne = diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java index 69d0192e..c5dc4439 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java @@ -30,11 +30,6 @@ private GreetingWorkflow getWorkflowStub(String workflowId) { // Starts a new GreetingWorkflow with the caller-specified workflow ID. This is an async // Nexus operation backed by WorkflowRunOperation. - // - // fromWorkflowHandle (rather than fromWorkflowMethod) is used here because the Nexus operation - // input (RunFromRemoteInput) differs from the workflow method parameters — run() takes no args. - // The input is consumed to set the workflow ID on the stub; the workflow itself is invoked - // with no arguments via the ::run method reference. @OperationImpl public OperationHandler runFromRemote() { return WorkflowRunOperation.fromWorkflowHandle( From 5ca7674a71a7a72a616baed050c2b1b8288d0086 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Fri, 10 Apr 2026 13:04:46 -0700 Subject: [PATCH 10/10] Updating a readme --- .../samples/nexus_messaging/callerpattern/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md index e7b5134e..a19fc5b0 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md @@ -1,10 +1,11 @@ ## Entity pattern -The handler worker starts a single `GreetingWorkflow` with a fixed workflow ID. -`NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. The caller's -inputs contain only business data — no workflow IDs. +The handler worker starts a `GreetingWorkflow` for a user ID. +`NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. +The caller's input does not have that workflow ID as the caller doesn't know it - but the caller sends in the User ID, +and `NexusGreetingServiceImpl` knows how to get the desired workflow ID from that User ID (see the getWorkflowId call). -Refer to the on demand pattern examples and you will see how to expand this example to not just a single fixed workflow ID - the NexusGreetingServiceImpl simply needs to be able to map something from the inputs to a Workflow ID. For example if the workflow ID is a user ID with a prefix, then if the Nexus inputs include a User ID, then Nexus can simple prepend the same string to get the desired workflow ID. +HandlerWorker is using the same getWorkflowId call to generate a workflow ID from a user ID when it launches the workflow. The caller workflow: 1. Queries for supported languages (`getLanguages` — backed by a `@QueryMethod`)