From 87c691bb11de208c1c361613cf104356453664ad Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 8 Apr 2026 13:30:38 -0700 Subject: [PATCH 01/15] 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 000000000..5d76d1fe9 --- /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 000000000..48c3758db --- /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 000000000..284bf9bde --- /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 000000000..1061f09a8 --- /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 000000000..82e96c057 --- /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 000000000..ed1507803 --- /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 000000000..b7ab336f6 --- /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 000000000..726ae5b26 --- /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 000000000..05b46828f --- /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 000000000..1f1e6af87 --- /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 000000000..05851047d --- /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 000000000..407d309de --- /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 000000000..5bc9f0ddf --- /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 000000000..a8fe380e0 --- /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 000000000..498ba5244 --- /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 000000000..182c96f3a --- /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 000000000..ba04df245 --- /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 000000000..f44bf4eca --- /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 000000000..1a9a9b71a --- /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 000000000..55a6cf666 --- /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 000000000..8af0c721e --- /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 000000000..dad19f816 --- /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 000000000..e6e2c5a01 --- /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 000000000..897e4a510 --- /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 000000000..68129be03 --- /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 000000000..675bf7fe1 --- /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 000000000..60e88c0f3 --- /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 000000000..edb72864b --- /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 000000000..0d607d9a5 --- /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 000000000..fb57ccd13 --- /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/15] 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 5d76d1fe9..15dcf21a4 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 284bf9bde..083e7b14e 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 82e96c057..e639f54e9 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 000000000..4ca946ba0 --- /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 000000000..320734764 --- /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 000000000..b6e49bf7c --- /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 000000000..08f29bd8b --- /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 05b46828f..8604c47d7 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 1f1e6af87..22d66c457 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 05851047d..85dbc6f12 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 726ae5b26..f88839f39 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 000000000..0358f58dd --- /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 407d309de..69f1d8536 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 000000000..4841ae0a7 --- /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 000000000..044a5ddd9 --- /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/15] 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 48c3758db..bb4163da5 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 083e7b14e..1ac68d76c 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 1061f09a8..6c830d7d2 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 e639f54e9..5a37caf06 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 4ca946ba0..c255f2884 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 320734764..1e89c59b7 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 b6e49bf7c..bd72d4acf 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 08f29bd8b..8477c10e4 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 ed1507803..83f97d1f4 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 b7ab336f6..e7927eba0 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 8604c47d7..786309f3e 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 22d66c457..c7140e607 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 85dbc6f12..ea219b0cb 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 f88839f39..afe9075ea 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 0358f58dd..1deecf60d 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 5bc9f0ddf..3c81c7c5a 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 69f1d8536..841efb768 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 4841ae0a7..60447a61a 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 a8fe380e0..000000000 --- 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 498ba5244..000000000 --- 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 182c96f3a..000000000 --- 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 ba04df245..000000000 --- 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 f44bf4eca..000000000 --- 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 1a9a9b71a..000000000 --- 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 55a6cf666..000000000 --- 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 8af0c721e..000000000 --- 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 dad19f816..000000000 --- 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 e6e2c5a01..000000000 --- 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 897e4a510..000000000 --- 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 68129be03..000000000 --- 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 675bf7fe1..000000000 --- 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 60e88c0f3..000000000 --- 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 edb72864b..000000000 --- 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 0d607d9a5..000000000 --- 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/15] 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 044a5ddd9..000000000 --- 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 fb57ccd13..000000000 --- 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/15] 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 15dcf21a4..066bc213f 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 bb4163da5..95a3d025a 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 1ac68d76c..8194503bc 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 5a37caf06..ed07f99e8 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 c255f2884..ad20ab2a5 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 1e89c59b7..4aa850ccd 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 8477c10e4..16ee4d9cd 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 c7140e607..c5ca55965 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 ea219b0cb..e23fff617 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 afe9075ea..0926f6cec 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 1deecf60d..42867f583 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/15] 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 066bc213f..d25424f45 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 ed07f99e8..dbea78437 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 16ee4d9cd..000000000 --- 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 ad20ab2a5..670d6123a 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 4aa850ccd..49dad3419 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 bd72d4acf..b707fa3bd 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 000000000..61bdee13b --- /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 c5ca55965..0021607eb 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/15] 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 d25424f45..fd047d66f 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 000000000..9f876393b --- /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 95a3d025a..7fd8b5aa9 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 8194503bc..50cda0f49 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 6c830d7d2..4bb91ce71 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 dbea78437..3bfe1eaa3 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 83f97d1f4..a7bfcd752 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 e7927eba0..2a4b94928 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 786309f3e..8cab7cf65 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 0021607eb..5ebe7e678 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 e23fff617..1f0a8aacb 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 0926f6cec..1f9bc87be 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 3c81c7c5a..9ca70913a 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 841efb768..70966b670 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 000000000..fbf3fde79 --- /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 670d6123a..35be6ff0b 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 49dad3419..832a2f153 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 b707fa3bd..5f3485f2f 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 61bdee13b..25997bcb3 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 000000000..aa69c643e --- /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 000000000..354e8dfab --- /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 000000000..1c37564ae --- /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 000000000..499069de6 --- /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 000000000..5cc1f6453 --- /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 42867f583..69d0192eb 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 000000000..bb23915fd --- /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 60447a61a..42f9ecf66 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/15] 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 9f876393b..4e3a78bc4 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 7fd8b5aa9..d771f545a 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 4bb91ce71..1f6fa1f8f 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 3bfe1eaa3..903ae2bea 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 8cab7cf65..6b12720f3 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 5ebe7e678..a6ab634ee 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 1f0a8aacb..5f90042e4 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 1f9bc87be..90bba6d96 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 70966b670..f42149c4d 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/15] 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 fd047d66f..0ff69765f 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 4e3a78bc4..e7b5134e5 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 d771f545a..e947eb6cd 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 903ae2bea..31958a267 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 5f90042e4..f4d4ce359 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 90bba6d96..c4c8c31d6 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 25997bcb3..6b4fe599e 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 69d0192eb..c5dc44398 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/15] 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 e7b5134e5..a19fc5b07 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`) From 7facf39aef836306189917a4ce42fd45f7a92db1 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Mon, 13 Apr 2026 15:19:16 -0700 Subject: [PATCH 11/15] Updated for code review comments --- .../nexus_messaging/callerpattern/README.md | 5 ++ .../callerpattern/caller/CallerStarter.java | 26 +++++-- .../callerpattern/caller/CallerWorker.java | 27 +++++-- .../callerpattern/handler/HandlerWorker.java | 27 +++++-- .../nexus_messaging/ondemandpattern/README.md | 5 ++ .../caller/CallerRemoteStarter.java | 26 +++++-- .../caller/CallerRemoteWorker.java | 31 +++++--- .../handler/HandlerWorker.java | 27 +++++-- .../NexusRemoteGreetingServiceImpl.java | 34 +++++---- .../service/NexusRemoteGreetingService.java | 71 +++++++++---------- core/src/main/resources/config.toml | 9 +++ 11 files changed, 202 insertions(+), 86 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 a19fc5b07..6301e9f05 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 @@ -33,6 +33,11 @@ temporal operator nexus endpoint create \ --target-task-queue nexus-messaging-handler-task-queue ``` +This sample loads connection settings from `ClientConfigProfile`. The +`nexus-messaging-handler` and `nexus-messaging-caller` profiles are defined in +`core/src/main/resources/config.toml`. You can override settings with environment +variables or by editing the TOML file (see the `envconfig` sample for details). + In one terminal, start the handler worker: ```bash 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 e947eb6cd..7e921a36f 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 @@ -1,20 +1,34 @@ package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; import io.temporal.client.WorkflowOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; import io.temporal.serviceclient.WorkflowServiceStubs; +import java.nio.file.Paths; 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()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(CallerStarter.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CallerWorker.CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); CallerWorkflow workflow = client.newWorkflowStub( diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java index 50cda0f49..4a7452807 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java @@ -1,12 +1,14 @@ package io.temporal.samples.nexus_messaging.callerpattern.caller; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; 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.nio.file.Paths; import java.util.Collections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,15 +16,28 @@ public class CallerWorker { private static final Logger logger = LoggerFactory.getLogger(CallerWorker.class); - public static final String NAMESPACE = "nexus-messaging-caller-namespace"; + static final String CONFIG_PROFILE = "nexus-messaging-caller"; 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(); - WorkflowClient client = - WorkflowClient.newInstance( - service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(CallerWorker.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); WorkerFactory factory = WorkerFactory.newInstance(client); Worker worker = factory.newWorker(TASK_QUEUE); 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 f4d4ce359..0db33d131 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 @@ -1,27 +1,42 @@ package io.temporal.samples.nexus_messaging.callerpattern.handler; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; import io.temporal.client.WorkflowExecutionAlreadyStarted; import io.temporal.client.WorkflowOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; +import java.nio.file.Paths; 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"; + static final String CONFIG_PROFILE = "nexus-messaging-handler"; public static final String TASK_QUEUE = "nexus-messaging-handler-task-queue"; static final String USER_ID = "user-1"; public static void main(String[] args) throws InterruptedException { - WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs(); - WorkflowClient client = - WorkflowClient.newInstance( - service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(HandlerWorker.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); // Start the long-running entity workflow that backs the Nexus service, if not already running. // Create a workflow ID derived from the given user ID. 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 index fbf3fde79..284de2cc5 100644 --- 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 @@ -33,6 +33,11 @@ temporal operator nexus endpoint create \ --target-task-queue nexus-messaging-handler-task-queue ``` +This sample loads connection settings from `ClientConfigProfile`. The +`nexus-messaging-handler` and `nexus-messaging-caller` profiles are defined in +`core/src/main/resources/config.toml`. You can override settings with environment +variables or by editing the TOML file (see the `envconfig` sample for details). + In one terminal, start the handler worker: ```bash diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java index 35be6ff0b..45c367686 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java @@ -1,20 +1,34 @@ package io.temporal.samples.nexus_messaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; import io.temporal.client.WorkflowOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; import io.temporal.serviceclient.WorkflowServiceStubs; +import java.nio.file.Paths; 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()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(CallerRemoteStarter.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CallerRemoteWorker.CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); CallerRemoteWorkflow workflow = client.newWorkflowStub( diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java index 832a2f153..547f685a2 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java @@ -1,28 +1,43 @@ package io.temporal.samples.nexus_messaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; 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 io.temporal.workflow.Workflow; +import java.nio.file.Paths; import java.util.Collections; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CallerRemoteWorker { - private static final Logger logger = Workflow.getLogger(CallerRemoteWorker.class); + private static final Logger logger = LoggerFactory.getLogger(CallerRemoteWorker.class); - public static final String NAMESPACE = "nexus-messaging-caller-namespace"; + static final String CONFIG_PROFILE = "nexus-messaging-caller"; 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(); - WorkflowClient client = - WorkflowClient.newInstance( - service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(CallerRemoteWorker.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); WorkerFactory factory = WorkerFactory.newInstance(client); Worker worker = factory.newWorker(TASK_QUEUE); 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 index 5cc1f6453..c21336705 100644 --- 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 @@ -1,24 +1,39 @@ package io.temporal.samples.nexus_messaging.ondemandpattern.handler; import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; +import io.temporal.envconfig.ClientConfigProfile; +import io.temporal.envconfig.LoadClientConfigProfileOptions; import io.temporal.serviceclient.WorkflowServiceStubs; import io.temporal.worker.Worker; import io.temporal.worker.WorkerFactory; +import java.nio.file.Paths; 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"; + static final String CONFIG_PROFILE = "nexus-messaging-handler"; 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()); + ClientConfigProfile profile; + try { + String configFilePath = + Paths.get(HandlerWorker.class.getResource("/config.toml").toURI()).toString(); + profile = + ClientConfigProfile.load( + LoadClientConfigProfileOptions.newBuilder() + .setConfigFilePath(configFilePath) + .setConfigFileProfile(CONFIG_PROFILE) + .build()); + } catch (Exception e) { + throw new RuntimeException("Failed to load client configuration", e); + } + + WorkflowServiceStubs service = + WorkflowServiceStubs.newServiceStubs(profile.toWorkflowServiceStubsOptions()); + WorkflowClient client = WorkflowClient.newInstance(service, profile.toWorkflowClientOptions()); WorkerFactory factory = WorkerFactory.newInstance(client); Worker worker = factory.newWorker(TASK_QUEUE); 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 c5dc44398..4d53226ff 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 @@ -22,10 +22,20 @@ public class NexusRemoteGreetingServiceImpl { private static final Logger logger = LoggerFactory.getLogger(NexusRemoteGreetingServiceImpl.class); - private GreetingWorkflow getWorkflowStub(String workflowId) { + 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; + } + + private GreetingWorkflow getWorkflowStub(String userId) { return Nexus.getOperationContext() .getWorkflowClient() - .newWorkflowStub(GreetingWorkflow.class, workflowId); + .newWorkflowStub(GreetingWorkflow.class, getWorkflowId(userId)); } // Starts a new GreetingWorkflow with the caller-specified workflow ID. This is an async @@ -34,14 +44,14 @@ private GreetingWorkflow getWorkflowStub(String workflowId) { public OperationHandler runFromRemote() { return WorkflowRunOperation.fromWorkflowHandle( (ctx, details, input) -> { - logger.info("RunFromRemote was received for workflow {}", input.getWorkflowId()); + logger.info("RunFromRemote was received for userID {}", input.getUserId()); return WorkflowHandle.fromWorkflowMethod( Nexus.getOperationContext() .getWorkflowClient() .newWorkflowStub( GreetingWorkflow.class, WorkflowOptions.newBuilder() - .setWorkflowId(input.getWorkflowId()) + .setWorkflowId(getWorkflowId(input.getUserId())) .setTaskQueue(HandlerWorker.TASK_QUEUE) .build()) ::run); @@ -55,8 +65,8 @@ public OperationHandler r getLanguages() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Query for GetLanguages was received for workflow {}", input.getWorkflowId()); - return getWorkflowStub(input.getWorkflowId()) + logger.info("Query for GetLanguages was received for userId {}", input.getUserId()); + return getWorkflowStub(input.getUserId()) .getLanguages(new GreetingWorkflow.GetLanguagesInput(input.isIncludeUnsupported())); }); } @@ -65,8 +75,8 @@ public OperationHandler r 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(); + logger.info("Query for GetLanguage was received for userId {}", input.getUserId()); + return getWorkflowStub(input.getUserId()).getLanguage(); }); } @@ -75,8 +85,8 @@ public OperationHandler g public OperationHandler setLanguage() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Update for SetLanguage was received for workflow {}", input.getWorkflowId()); - return getWorkflowStub(input.getWorkflowId()) + logger.info("Update for SetLanguage was received for userId {}", input.getUserId()); + return getWorkflowStub(input.getUserId()) .setLanguageUsingActivity(new GreetingWorkflow.SetLanguageInput(input.getLanguage())); }); } @@ -87,8 +97,8 @@ public OperationHandler s approve() { return OperationHandler.sync( (ctx, details, input) -> { - logger.info("Signal for Approve was received for workflow {}", input.getWorkflowId()); - getWorkflowStub(input.getWorkflowId()) + logger.info("Signal for Approve was received for userId {}", input.getUserId()); + getWorkflowStub(input.getUserId()) .approve(new GreetingWorkflow.ApproveInput(input.getName())); return new NexusRemoteGreetingService.ApproveOutput(); }); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java index 42f9ecf66..f79ee6a6d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java @@ -8,37 +8,38 @@ import java.util.List; /** - * 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. + * Nexus service definition for the on-demand pattern. Every operation includes a {@code userId} so + * the caller controls which workflow instance is targeted though that, while the Nexus service + * converts that UserId into a WorkflowId. This also exposes a {@code runFromRemote} operation that + * starts a new GreetingWorkflow. */ @Service public interface NexusRemoteGreetingService { class RunFromRemoteInput { - private final String workflowId; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public RunFromRemoteInput(@JsonProperty("workflowId") String workflowId) { - this.workflowId = workflowId; + public RunFromRemoteInput(@JsonProperty("userId") String userId) { + this.userId = userId; } - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; + @JsonProperty("userId") + public String getUserId() { + return userId; } } class GetLanguagesInput { private final boolean includeUnsupported; - private final String workflowId; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public GetLanguagesInput( @JsonProperty("includeUnsupported") boolean includeUnsupported, - @JsonProperty("workflowId") String workflowId) { + @JsonProperty("userId") String userId) { this.includeUnsupported = includeUnsupported; - this.workflowId = workflowId; + this.userId = userId; } @JsonProperty("includeUnsupported") @@ -46,37 +47,36 @@ public boolean isIncludeUnsupported() { return includeUnsupported; } - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; + @JsonProperty("userId") + public String getUserId() { + return userId; } } @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) class GetLanguageInput { - private final String workflowId; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public GetLanguageInput(@JsonProperty("workflowId") String workflowId) { - this.workflowId = workflowId; + public GetLanguageInput(@JsonProperty("userId") String userId) { + this.userId = userId; } - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; + @JsonProperty("userId") + public String getUserId() { + return userId; } } class SetLanguageInput { private final Language language; - private final String workflowId; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public SetLanguageInput( - @JsonProperty("language") Language language, - @JsonProperty("workflowId") String workflowId) { + @JsonProperty("language") Language language, @JsonProperty("userId") String userId) { this.language = language; - this.workflowId = workflowId; + this.userId = userId; } @JsonProperty("language") @@ -84,21 +84,20 @@ public Language getLanguage() { return language; } - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; + @JsonProperty("userId") + public String getUserId() { + return userId; } } class ApproveInput { private final String name; - private final String workflowId; + private final String userId; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public ApproveInput( - @JsonProperty("name") String name, @JsonProperty("workflowId") String workflowId) { + public ApproveInput(@JsonProperty("name") String name, @JsonProperty("userId") String userId) { this.name = name; - this.workflowId = workflowId; + this.userId = userId; } @JsonProperty("name") @@ -106,9 +105,9 @@ public String getName() { return name; } - @JsonProperty("workflowId") - public String getWorkflowId() { - return workflowId; + @JsonProperty("userId") + public String getUserId() { + return userId; } } @@ -132,7 +131,7 @@ class ApproveOutput { public ApproveOutput() {} } - // Starts a new GreetingWorkflow with the given workflow ID. This is an asynchronous Nexus + // Starts a new GreetingWorkflow for the given user 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); diff --git a/core/src/main/resources/config.toml b/core/src/main/resources/config.toml index 81f07f786..9169298b6 100644 --- a/core/src/main/resources/config.toml +++ b/core/src/main/resources/config.toml @@ -17,6 +17,15 @@ trace-id = "dev-trace-123" address = "localhost:9999" namespace = "staging" +# Nexus messaging sample profiles +[profile.nexus-messaging-handler] +address = "localhost:7233" +namespace = "nexus-messaging-handler-namespace" + +[profile.nexus-messaging-caller] +address = "localhost:7233" +namespace = "nexus-messaging-caller-namespace" + # An example production profile for Temporal Cloud [profile.prod] address = "your-namespace.a1b2c.tmprl.cloud:7233" From 039abcf10dbe4d23e8e9703d52e4bbdfa5ab25f3 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Tue, 14 Apr 2026 16:47:19 -0700 Subject: [PATCH 12/15] Fixes from code review --- .../{nexus_messaging => nexusmessaging}/README.md | 2 +- .../callerpattern/README.md | 6 +++--- .../callerpattern/caller/CallerStarter.java | 2 +- .../callerpattern/caller/CallerWorker.java | 2 +- .../callerpattern/caller/CallerWorkflow.java | 2 +- .../callerpattern/caller/CallerWorkflowImpl.java | 6 +++--- .../callerpattern/handler/GreetingActivity.java | 4 ++-- .../callerpattern/handler/GreetingActivityImpl.java | 4 ++-- .../callerpattern/handler/GreetingWorkflow.java | 6 +++--- .../callerpattern/handler/GreetingWorkflowImpl.java | 6 +++--- .../callerpattern/handler/HandlerWorker.java | 2 +- .../handler/NexusGreetingServiceImpl.java | 8 ++++---- .../callerpattern/service/Language.java | 2 +- .../callerpattern/service/NexusGreetingService.java | 2 +- .../ondemandpattern/README.md | 6 +++--- .../ondemandpattern/caller/CallerRemoteStarter.java | 2 +- .../ondemandpattern/caller/CallerRemoteWorker.java | 2 +- .../ondemandpattern/caller/CallerRemoteWorkflow.java | 2 +- .../caller/CallerRemoteWorkflowImpl.java | 9 ++++----- .../ondemandpattern/handler/GreetingActivity.java | 4 ++-- .../ondemandpattern/handler/GreetingActivityImpl.java | 4 ++-- .../ondemandpattern/handler/GreetingWorkflow.java | 6 +++--- .../ondemandpattern/handler/GreetingWorkflowImpl.java | 10 ++++------ .../ondemandpattern/handler/HandlerWorker.java | 2 +- .../handler/NexusRemoteGreetingServiceImpl.java | 8 ++++---- .../ondemandpattern/service/Language.java | 2 +- .../service/NexusRemoteGreetingService.java | 2 +- 27 files changed, 55 insertions(+), 58 deletions(-) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/README.md (93%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/README.md (94%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/caller/CallerStarter.java (96%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/caller/CallerWorker.java (97%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/caller/CallerWorkflow.java (77%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/caller/CallerWorkflowImpl.java (92%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/GreetingActivity.java (68%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/GreetingActivityImpl.java (83%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/GreetingWorkflow.java (87%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/GreetingWorkflowImpl.java (93%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/HandlerWorker.java (97%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/handler/NexusGreetingServiceImpl.java (90%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/service/Language.java (60%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/callerpattern/service/NexusGreetingService.java (98%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/README.md (93%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/caller/CallerRemoteStarter.java (95%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/caller/CallerRemoteWorker.java (97%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/caller/CallerRemoteWorkflow.java (76%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/caller/CallerRemoteWorkflowImpl.java (94%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/GreetingActivity.java (68%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/GreetingActivityImpl.java (83%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/GreetingWorkflow.java (91%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/GreetingWorkflowImpl.java (88%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/HandlerWorker.java (96%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java (93%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/service/Language.java (59%) rename core/src/main/java/io/temporal/samples/{nexus_messaging => nexusmessaging}/ondemandpattern/service/NexusRemoteGreetingService.java (98%) diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md b/core/src/main/java/io/temporal/samples/nexusmessaging/README.md similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/README.md rename to core/src/main/java/io/temporal/samples/nexusmessaging/README.md index 0ff69765f..209fd654d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/README.md @@ -9,7 +9,7 @@ operations. There are two self-contained examples, each in its own directory: | **Nexus service** | `NexusGreetingService` | `NexusRemoteGreetingService` | Each directory is fully self-contained for clarity. The -`GreetingWorkflow`, `GreetingWorkflowImpl`, `GreetingActivity` and `GreetingActivityImpl` classes are **identical** between the two — only the +`GreetingWorkflow`, `GreetingWorkflowImpl`, `GreetingActivity` and `GreetingActivityImpl` classes are pretty much the same 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/nexusmessaging/callerpattern/README.md similarity index 94% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md index 6301e9f05..8f753d047 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md @@ -41,19 +41,19 @@ variables or by editing the TOML file (see the `envconfig` sample for details). In one terminal, start the handler worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.callerpattern.handler.HandlerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.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 +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.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 +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.callerpattern.caller.CallerStarter ``` Expected output: diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerStarter.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerStarter.java index 7e921a36f..01a4aa745 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerStarter.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.caller; +package io.temporal.samples.nexusmessaging.callerpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorker.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorker.java index 4a7452807..e08b4f92e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.caller; +package io.temporal.samples.nexusmessaging.callerpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.envconfig.ClientConfigProfile; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflow.java similarity index 77% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflow.java index 1f6fa1f8f..70de274dc 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.caller; +package io.temporal.samples.nexusmessaging.callerpattern.caller; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflowImpl.java similarity index 92% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflowImpl.java index 31958a267..24bdb9c92 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/caller/CallerWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/caller/CallerWorkflowImpl.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_messaging.callerpattern.caller; +package io.temporal.samples.nexusmessaging.callerpattern.caller; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_messaging.callerpattern.service.Language; -import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.callerpattern.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_messaging/callerpattern/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivity.java similarity index 68% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivity.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivity.java index a7bfcd752..eee983dde 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivity.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivity.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.callerpattern.handler; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; -import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; @ActivityInterface public interface GreetingActivity { diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivityImpl.java similarity index 83% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivityImpl.java index 2a4b94928..196601ffb 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingActivityImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingActivityImpl.java @@ -1,6 +1,6 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.callerpattern.handler; -import io.temporal.samples.nexus_messaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; import java.util.EnumMap; import java.util.Map; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflow.java similarity index 87% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflow.java index 6b12720f3..37651ed57 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflow.java @@ -1,7 +1,7 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.callerpattern.handler; -import io.temporal.samples.nexus_messaging.callerpattern.service.Language; -import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.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/callerpattern/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflowImpl.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflowImpl.java index a6ab634ee..e7ea41a76 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/GreetingWorkflowImpl.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.callerpattern.handler; import io.temporal.activity.ActivityOptions; import io.temporal.failure.ApplicationFailure; -import io.temporal.samples.nexus_messaging.callerpattern.service.Language; -import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.callerpattern.service.NexusGreetingService; import io.temporal.workflow.Workflow; import java.time.Duration; import java.util.ArrayList; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/HandlerWorker.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/HandlerWorker.java index 0db33d131..b35f6f924 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/HandlerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.callerpattern.handler; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowExecutionAlreadyStarted; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/NexusGreetingServiceImpl.java similarity index 90% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/NexusGreetingServiceImpl.java index c4c8c31d6..2a6d04c65 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/handler/NexusGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/NexusGreetingServiceImpl.java @@ -1,11 +1,11 @@ -package io.temporal.samples.nexus_messaging.callerpattern.handler; +package io.temporal.samples.nexusmessaging.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.callerpattern.service.Language; -import io.temporal.samples.nexus_messaging.callerpattern.service.NexusGreetingService; +import io.temporal.samples.nexusmessaging.callerpattern.service.Language; +import io.temporal.samples.nexusmessaging.callerpattern.service.NexusGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +24,7 @@ public class NexusGreetingServiceImpl { // 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. + // use the single workflow 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/callerpattern/service/Language.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/Language.java similarity index 60% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/Language.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/Language.java index 9ca70913a..42c31d0ba 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/Language.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/Language.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.service; +package io.temporal.samples.nexusmessaging.callerpattern.service; public enum Language { ARABIC, diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/NexusGreetingService.java similarity index 98% rename from core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/NexusGreetingService.java index f42149c4d..4515a66b9 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/callerpattern/service/NexusGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/service/NexusGreetingService.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.callerpattern.service; +package io.temporal.samples.nexusmessaging.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/nexusmessaging/ondemandpattern/README.md similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md index 284de2cc5..a3a945812 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md @@ -41,19 +41,19 @@ variables or by editing the TOML file (see the `envconfig` sample for details). In one terminal, start the handler worker: ```bash -./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexus_messaging.ondemandpattern.handler.HandlerWorker +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.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 +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.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 +./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.ondemandpattern.caller.CallerRemoteStarter ``` Expected output: diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteStarter.java similarity index 95% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteStarter.java index 45c367686..bb3d8dc8e 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteStarter.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteStarter.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.caller; +package io.temporal.samples.nexusmessaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowOptions; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorker.java similarity index 97% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorker.java index 547f685a2..9a49230fd 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorker.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.caller; +package io.temporal.samples.nexusmessaging.ondemandpattern.caller; import io.temporal.client.WorkflowClient; import io.temporal.envconfig.ClientConfigProfile; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflow.java similarity index 76% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflow.java index 5f3485f2f..5b8d3b1cb 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflow.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.caller; +package io.temporal.samples.nexusmessaging.ondemandpattern.caller; import io.temporal.workflow.WorkflowInterface; import io.temporal.workflow.WorkflowMethod; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java similarity index 94% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java index 6b4fe599e..d6767c04a 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/caller/CallerRemoteWorkflowImpl.java @@ -1,7 +1,7 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.caller; +package io.temporal.samples.nexusmessaging.ondemandpattern.caller; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.NexusRemoteGreetingService; import io.temporal.workflow.NexusOperationHandle; import io.temporal.workflow.NexusOperationOptions; import io.temporal.workflow.NexusServiceOptions; @@ -18,7 +18,6 @@ public class CallerRemoteWorkflowImpl implements CallerRemoteWorkflow { // 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"; @@ -69,7 +68,7 @@ public List run() { NexusOperationHandle handleTwo = Workflow.startNexusOperation( - greetingRemoteServiceOne::runFromRemote, + greetingRemoteServiceTwo::runFromRemote, new NexusRemoteGreetingService.RunFromRemoteInput(REMOTE_WORKFLOW_TWO)); // Wait for the operation to be started (workflow is now running on the handler). handleTwo.getExecution().get(); diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivity.java similarity index 68% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivity.java index aa69c643e..86bbb771d 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivity.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivity.java @@ -1,8 +1,8 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.ondemandpattern.handler; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.Language; @ActivityInterface public interface GreetingActivity { diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivityImpl.java similarity index 83% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivityImpl.java index 354e8dfab..804f199fc 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingActivityImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingActivityImpl.java @@ -1,6 +1,6 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.ondemandpattern.handler; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.Language; import java.util.EnumMap; import java.util.Map; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflow.java similarity index 91% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflow.java index 1c37564ae..bd115c9b4 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflow.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflow.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.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.samples.nexusmessaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.NexusRemoteGreetingService; 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/ondemandpattern/handler/GreetingWorkflowImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflowImpl.java similarity index 88% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflowImpl.java index 499069de6..ca923da15 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/GreetingWorkflowImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/GreetingWorkflowImpl.java @@ -1,9 +1,9 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.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.samples.nexusmessaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.NexusRemoteGreetingService; import io.temporal.workflow.Workflow; import java.time.Duration; import java.util.ArrayList; @@ -16,13 +16,11 @@ public class GreetingWorkflowImpl implements GreetingWorkflow { - private static final Logger logger1 = Workflow.getLogger(GreetingWorkflowImpl.class); + private static final Logger logger = 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, diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/HandlerWorker.java similarity index 96% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/HandlerWorker.java index c21336705..9d5e1fce7 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/HandlerWorker.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/HandlerWorker.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.ondemandpattern.handler; import io.temporal.client.WorkflowClient; import io.temporal.envconfig.ClientConfigProfile; diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java similarity index 93% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java index 4d53226ff..b73979878 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/handler/NexusRemoteGreetingServiceImpl.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.handler; +package io.temporal.samples.nexusmessaging.ondemandpattern.handler; import io.nexusrpc.handler.OperationHandler; import io.nexusrpc.handler.OperationImpl; @@ -7,8 +7,8 @@ import io.temporal.nexus.Nexus; import io.temporal.nexus.WorkflowHandle; import io.temporal.nexus.WorkflowRunOperation; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.Language; -import io.temporal.samples.nexus_messaging.ondemandpattern.service.NexusRemoteGreetingService; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.Language; +import io.temporal.samples.nexusmessaging.ondemandpattern.service.NexusRemoteGreetingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +27,7 @@ public class NexusRemoteGreetingServiceImpl { // 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. + // use the single workflow 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/service/Language.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/Language.java similarity index 59% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/Language.java index bb23915fd..d367cb039 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/Language.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/Language.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.service; +package io.temporal.samples.nexusmessaging.ondemandpattern.service; public enum Language { ARABIC, diff --git a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/NexusRemoteGreetingService.java similarity index 98% rename from core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java rename to core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/NexusRemoteGreetingService.java index f79ee6a6d..6c6b35cb1 100644 --- a/core/src/main/java/io/temporal/samples/nexus_messaging/ondemandpattern/service/NexusRemoteGreetingService.java +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/service/NexusRemoteGreetingService.java @@ -1,4 +1,4 @@ -package io.temporal.samples.nexus_messaging.ondemandpattern.service; +package io.temporal.samples.nexusmessaging.ondemandpattern.service; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; From 91ebd1955dbe8b819aba55764dd7d7a644e86d59 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 29 Apr 2026 12:26:23 -0700 Subject: [PATCH 13/15] Some changes from code reviews --- README.md | 3 +++ .../io/temporal/samples/nexusmessaging/README.md | 10 +++++----- .../samples/nexusmessaging/callerpattern/README.md | 14 +++++++------- .../nexusmessaging/ondemandpattern/README.md | 8 ++++---- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4d00e320b..b2187e2dc 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,9 @@ Load client configuration from TOML files with programmatic overrides. - [**Context/Header Propagation**](/core/src/main/java/io/temporal/samples/nexuscontextpropagation): Demonstrates how to propagate context through Nexus operation headers. +- [**Nexus Messaging**](/core/src/main/java/io/temporal/samples/nexusmessaging): Demonstrates how send signal, update and query messages through Nexus. + This contains two samples, one sending messages to an existing workflow and a second that creates a workflow through Nexus + and sends messages to it. ### Running SpringBoot Samples diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/README.md b/core/src/main/java/io/temporal/samples/nexusmessaging/README.md index 209fd654d..8175d7272 100644 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/README.md @@ -1,16 +1,16 @@ -This sample shows how to expose a long-running workflow's queries, updates, and signals as Nexus +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, 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 | +| **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 for clarity. The `GreetingWorkflow`, `GreetingWorkflowImpl`, `GreetingActivity` and `GreetingActivityImpl` classes are pretty much the same between the two — only the -Nexus service interface and its implementation differ. This highlights that the same workflow can be +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. See each directory's README for running instructions. diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md index 8f753d047..1f5756dea 100644 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md @@ -1,17 +1,17 @@ ## Entity pattern -The handler worker starts a `GreetingWorkflow` for a user ID. +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). +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). -HandlerWorker is using the same getWorkflowId call to generate a workflow ID from a user ID when it launches the workflow. +HandlerWorker is using the same getWorkflowId call to generate a Workflow ID from a User ID when it launches the Workflow. -The caller workflow: +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`) +4. Approves the Workflow (`approve` — backed by a `@SignalMethod`) ### Running @@ -50,7 +50,7 @@ In a second terminal, start the caller worker: ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.callerpattern.caller.CallerWorker ``` -In a third terminal, start the caller workflow: +In a third terminal, run the following command to start the example: ```bash ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.callerpattern.caller.CallerStarter diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md index a3a945812..c987e85f8 100644 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/README.md @@ -1,16 +1,16 @@ ## On-demand pattern -No workflow is pre-started. The caller creates and controls workflow instances through Nexus +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: +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 +5. Approves both Workflows 6. Waits for each to complete and returns their results ### Running @@ -50,7 +50,7 @@ In a second terminal, start the caller worker: ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.ondemandpattern.caller.CallerRemoteWorker ``` -In a third terminal, start the caller workflow: +In a third terminal, run the following command to start the example: ```bash ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusmessaging.ondemandpattern.caller.CallerRemoteStarter From dc622620c55c5c5e7550b76ad49f6c727005c382 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 29 Apr 2026 14:30:32 -0700 Subject: [PATCH 14/15] Fixed package name in a readme --- .../io/temporal/samples/nexusmessaging/callerpattern/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md index 1f5756dea..b369a95fc 100644 --- a/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md +++ b/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/README.md @@ -1,4 +1,4 @@ -## Entity pattern +## Caller pattern The handler worker starts a `GreetingWorkflow` for a User ID. `NexusGreetingServiceImpl` holds that ID and routes every Nexus operation to it. From 1835613eac3b4ea2a10dbb6d81c1b7e77d4aa0f1 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Fri, 1 May 2026 12:17:30 -0700 Subject: [PATCH 15/15] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2187e2dc..487bd25a2 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Load client configuration from TOML files with programmatic overrides. - [**Context/Header Propagation**](/core/src/main/java/io/temporal/samples/nexuscontextpropagation): Demonstrates how to propagate context through Nexus operation headers. -- [**Nexus Messaging**](/core/src/main/java/io/temporal/samples/nexusmessaging): Demonstrates how send signal, update and query messages through Nexus. +- [**Nexus Messaging**](/core/src/main/java/io/temporal/samples/nexusmessaging): Demonstrates how to send signal, update and query messages through Nexus. This contains two samples, one sending messages to an existing workflow and a second that creates a workflow through Nexus and sends messages to it.