From ef19c9c8bc093578ecd568d4167c0ecc6665c756 Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Tue, 29 Sep 2026 15:45:43 -0700 Subject: [PATCH 1/5] Copy sample to v2 folder --- .../Caller/CallerWorkflow.workflow.cs | 39 ++++ .../CallerPattern/Caller/NexusEndpoints.cs | 6 + .../Handler/GreetingWorkflow.workflow.cs | 82 ++++++++ .../Handler/NexusGreetingService.cs | 74 +++++++ .../CallerPattern/INexusGreetingService.cs | 30 +++ src/NexusMessagingV2/CallerPattern/README.md | 65 +++++++ .../Common/GreetingActivities.cs | 19 ++ src/NexusMessagingV2/Common/Language.cs | 12 ++ .../Caller/CallerRemoteWorkflow.workflow.cs | 82 ++++++++ .../OnDemandPattern/Caller/NexusEndpoints.cs | 6 + .../Handler/GreetingWorkflow.workflow.cs | 90 +++++++++ .../Handler/NexusRemoteGreetingService.cs | 112 +++++++++++ .../INexusRemoteGreetingService.cs | 40 ++++ .../OnDemandPattern/README.md | 77 ++++++++ src/NexusMessagingV2/Program.cs | 184 ++++++++++++++++++ src/NexusMessagingV2/README.md | 18 ++ .../TemporalioSamples.NexusMessagingV2.csproj | 11 ++ tests/NexusMessagingV2/CallerPatternTests.cs | 80 ++++++++ .../NexusMessagingV2/OnDemandPatternTests.cs | 79 ++++++++ 19 files changed, 1106 insertions(+) create mode 100644 src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs create mode 100644 src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs create mode 100644 src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs create mode 100644 src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs create mode 100644 src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs create mode 100644 src/NexusMessagingV2/CallerPattern/README.md create mode 100644 src/NexusMessagingV2/Common/GreetingActivities.cs create mode 100644 src/NexusMessagingV2/Common/Language.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs create mode 100644 src/NexusMessagingV2/OnDemandPattern/README.md create mode 100644 src/NexusMessagingV2/Program.cs create mode 100644 src/NexusMessagingV2/README.md create mode 100644 src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj create mode 100644 tests/NexusMessagingV2/CallerPatternTests.cs create mode 100644 tests/NexusMessagingV2/OnDemandPatternTests.cs diff --git a/src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs b/src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs new file mode 100644 index 0000000..1897011 --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs @@ -0,0 +1,39 @@ +namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; + +using Temporalio.Workflows; +using TemporalioSamples.NexusMessagingV2.CallerPattern; +using TemporalioSamples.NexusMessagingV2.Common; + +[Workflow] +public class CallerWorkflow +{ + [WorkflowRun] + public async Task RunAsync(string userId) + { + var log = new List(); + var client = Workflow.CreateNexusWorkflowClient( + NexusEndpoints.GreetingService); + + // GetLanguages - query entity workflow for supported languages + var languagesOutput = await client.ExecuteNexusOperationAsync( + svc => svc.GetLanguages(new INexusGreetingService.GetLanguagesInput(false, userId))); + log.Add($"Supported languages: {string.Join(", ", languagesOutput.Languages)}"); + + // GetLanguage - query entity workflow for current language + var currentLanguage = await client.ExecuteNexusOperationAsync( + svc => svc.GetLanguage(new INexusGreetingService.GetLanguageInput(userId))); + log.Add($"Current language: {currentLanguage}"); + + // SetLanguage - update entity workflow to change language + var previousLanguage = await client.ExecuteNexusOperationAsync( + svc => svc.SetLanguage(new INexusGreetingService.SetLanguageInput(Language.Chinese, userId))); + log.Add($"Set language from {previousLanguage} to {Language.Chinese}"); + + // Approve - signal entity workflow to complete + await client.ExecuteNexusOperationAsync( + svc => svc.Approve(new INexusGreetingService.ApproveInput("CallerWorkflow", userId))); + log.Add("Approved workflow"); + + return log.ToArray(); + } +} diff --git a/src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs b/src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs new file mode 100644 index 0000000..af5b7d9 --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs @@ -0,0 +1,6 @@ +namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; + +public static class NexusEndpoints +{ + public const string GreetingService = "nexus-messaging-caller-pattern-endpoint"; +} diff --git a/src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs b/src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs new file mode 100644 index 0000000..44a148b --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs @@ -0,0 +1,82 @@ +namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; + +using Temporalio.Exceptions; +using Temporalio.Workflows; +using TemporalioSamples.NexusMessagingV2.CallerPattern; +using TemporalioSamples.NexusMessagingV2.Common; + +[Workflow] +public class GreetingWorkflow +{ + private readonly Dictionary greetings = new() + { + [Language.Chinese] = "你好,世界", + [Language.English] = "Hello, world", + }; + + private Language currentLanguage = Language.English; + private bool approved; + private string approvedBy = string.Empty; + + [WorkflowRun] + public async Task RunAsync(string userId) + { + // Wait for approve signal and all handlers to finish + await Workflow.WaitConditionAsync(() => approved && Workflow.AllHandlersFinished); + + var greeting = greetings.TryGetValue(currentLanguage, out var g) ? g : "Hello, world"; + return $"{greeting} (approved by {approvedBy})"; + } + + [WorkflowQuery] + public INexusGreetingService.GetLanguagesOutput QueryLanguages(bool includeUnsupported) + { + if (includeUnsupported) + { + return new(Enum.GetValues()); + } + + return new(greetings.Keys.ToArray()); + } + + [WorkflowQuery] + public Language QueryLanguage() => currentLanguage; + + [WorkflowUpdate] + public async Task SetLanguageAsync(Language newLanguage) + { + var prev = currentLanguage; + + // If language not yet in our greetings map, fetch from activity + if (!greetings.ContainsKey(newLanguage)) + { + var allGreetings = await Workflow.ExecuteActivityAsync( + (GreetingActivities a) => a.GetAllGreetingsAsync(), + new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) }); + foreach (var kvp in allGreetings) + { + greetings[kvp.Key] = kvp.Value; + } + } + + currentLanguage = newLanguage; + return prev; + } + + [WorkflowUpdateValidator(nameof(SetLanguageAsync))] + public void ValidateSetLanguage(Language newLanguage) + { + if (!Enum.IsDefined(newLanguage)) + { + throw new ApplicationFailureException($"Unsupported language: {newLanguage}"); + } + } + + [WorkflowSignal] + public Task ApproveAsync(string name) + { + approved = true; + approvedBy = name; + return Task.CompletedTask; + } +} diff --git a/src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs b/src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs new file mode 100644 index 0000000..a199a65 --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs @@ -0,0 +1,74 @@ +namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; + +using NexusRpc.Handlers; +using Temporalio.Client; +using Temporalio.Nexus; +using TemporalioSamples.NexusMessagingV2.CallerPattern; +using TemporalioSamples.NexusMessagingV2.Common; + +// Entity pattern: the handler worker pre-starts a GreetingWorkflow per user at boot time. +// This service routes each Nexus operation to that existing workflow by deriving the +// workflow ID from the caller-supplied UserId. +[NexusServiceHandler(typeof(INexusGreetingService))] +public class NexusGreetingService +{ +#pragma warning disable VSTHRD200 // Names must match the INexusGreetingService operations, which can't take the Async suffix + + // Query: read-only, no state mutation — uses workflow query + [TemporalOperation] + public async Task> GetLanguages( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusGreetingService.GetLanguagesInput input) + { + // Access the Temporal client from the Nexus client passed to the handler + var handle = client.TemporalClient.GetWorkflowHandle( + WorkflowIdForUser(input.UserId)); + return TemporalOperationResult.SyncResult( + await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported))); + } + + // Query: read-only — returns the workflow's current language + [TemporalOperation] + public async Task> GetLanguage( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusGreetingService.GetLanguageInput input) + { + var handle = client.TemporalClient.GetWorkflowHandle( + WorkflowIdForUser(input.UserId)); + return TemporalOperationResult.SyncResult( + await handle.QueryAsync(wf => wf.QueryLanguage())); + } + + // Update: mutates state and returns the previous value — uses workflow update + [TemporalOperation] + public Task> SetLanguage( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusGreetingService.SetLanguageInput input) => + client.StartWorkflowUpdateAsync( + WorkflowIdForUser(input.UserId), + wf => wf.SetLanguageAsync(input.Language), + // An Update-backed Operation must wait for the Accepted stage. Any other stage is + // rejected with "nexus op workflow updates only support WorkflowUpdateStageAccepted + // for async updates". + new(WorkflowUpdateStage.Accepted)); + + // Signal: fire-and-forget, no return value needed — uses workflow signal + [TemporalOperation] + public async Task> Approve( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusGreetingService.ApproveInput input) + { + var handle = client.TemporalClient.GetWorkflowHandle( + WorkflowIdForUser(input.UserId)); + await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); + return TemporalOperationResult.SyncResult(default); + } + +#pragma warning restore VSTHRD200 + + private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}"; +} diff --git a/src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs b/src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs new file mode 100644 index 0000000..94b5991 --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs @@ -0,0 +1,30 @@ +namespace TemporalioSamples.NexusMessagingV2.CallerPattern; + +using NexusRpc; +using TemporalioSamples.NexusMessagingV2.Common; + +[NexusService] +public interface INexusGreetingService +{ + [NexusOperation] + GetLanguagesOutput GetLanguages(GetLanguagesInput input); + + [NexusOperation] + Language GetLanguage(GetLanguageInput input); + + [NexusOperation] + Language SetLanguage(SetLanguageInput input); + + [NexusOperation] + void Approve(ApproveInput input); + + public record GetLanguagesInput(bool IncludeUnsupported, string UserId); + + public record GetLanguagesOutput(IReadOnlyList Languages); + + public record GetLanguageInput(string UserId); + + public record SetLanguageInput(Language Language, string UserId); + + public record ApproveInput(string Name, string UserId); +} diff --git a/src/NexusMessagingV2/CallerPattern/README.md b/src/NexusMessagingV2/CallerPattern/README.md new file mode 100644 index 0000000..4f21f3e --- /dev/null +++ b/src/NexusMessagingV2/CallerPattern/README.md @@ -0,0 +1,65 @@ +## Entity pattern + +The handler worker starts a `GreetingWorkflow` for a User ID at boot time. +`NexusGreetingService` routes every Nexus operation to that existing Workflow by deriving +the Workflow ID from the caller-supplied `UserId` (see the `WorkflowIdForUser` call). +The caller passes a `UserId`, not a Workflow ID -- the handler is responsible for the +ID mapping. + +The caller Workflow: +1. Queries for supported languages (`GetLanguages` -- backed by a Workflow query) +2. Queries the current language (`GetLanguage` -- backed by a Workflow query) +3. Changes the language to Chinese (`SetLanguage` -- backed by a Workflow update that calls an activity) +4. Approves the Workflow (`Approve` -- backed by a Workflow signal) + +### Running + +This sample requires a Temporal dev server build that supports Workflow Update callbacks. Download +the compatible binary from the [Temporal CLI pre-release instructions](https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support). + +Start the Temporal dev server with the required namespaces pre-created and Workflow Update +callbacks enabled: + +```bash +./temporal server start-dev \ + --dynamic-config-value history.enableUpdateCallbacks=true \ + --dynamic-config-value history.enableCHASMSignalBacklinks=true \ + --namespace nexus-messaging-handler-namespace \ + --namespace nexus-messaging-caller-namespace +``` + +Create the Nexus endpoint: + +```bash +./temporal operator nexus endpoint create \ + --name nexus-messaging-caller-pattern-endpoint \ + --target-namespace nexus-messaging-handler-namespace \ + --target-task-queue nexus-messaging-handler-sample +``` + +In one terminal, start the handler worker: + +```bash +dotnet run --project src/NexusMessagingV2 -- handler-worker +``` + +In a second terminal, start the caller worker: + +```bash +dotnet run --project src/NexusMessagingV2 -- caller-worker +``` + +In a third terminal, run the following command to start the example: + +```bash +dotnet run --project src/NexusMessagingV2 -- caller-workflow +``` + +Expected output: + +``` +Supported languages: Chinese, English +Current language: English +Set language from English to Chinese +Approved workflow +``` diff --git a/src/NexusMessagingV2/Common/GreetingActivities.cs b/src/NexusMessagingV2/Common/GreetingActivities.cs new file mode 100644 index 0000000..c826073 --- /dev/null +++ b/src/NexusMessagingV2/Common/GreetingActivities.cs @@ -0,0 +1,19 @@ +namespace TemporalioSamples.NexusMessagingV2.Common; + +using Temporalio.Activities; + +public class GreetingActivities +{ + [Activity] + public Task> GetAllGreetingsAsync() => + Task.FromResult(new Dictionary + { + [Language.Arabic] = "مرحبا بالعالم", + [Language.Chinese] = "你好,世界", + [Language.English] = "Hello, world", + [Language.French] = "Bonjour, monde", + [Language.Hindi] = "नमस्ते दुनिया", + [Language.Portuguese] = "Olá, mundo", + [Language.Spanish] = "Hola, mundo", + }); +} diff --git a/src/NexusMessagingV2/Common/Language.cs b/src/NexusMessagingV2/Common/Language.cs new file mode 100644 index 0000000..746560c --- /dev/null +++ b/src/NexusMessagingV2/Common/Language.cs @@ -0,0 +1,12 @@ +namespace TemporalioSamples.NexusMessagingV2.Common; + +public enum Language +{ + Arabic, + Chinese, + English, + French, + Hindi, + Portuguese, + Spanish, +} diff --git a/src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs b/src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs new file mode 100644 index 0000000..ec3e4a5 --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs @@ -0,0 +1,82 @@ +namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; + +using Temporalio.Workflows; +using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern; + +[Workflow] +public class CallerRemoteWorkflow +{ + [WorkflowRun] + public async Task RunAsync() + { + var log = new List(); + var client = Workflow.CreateNexusWorkflowClient( + NexusEndpoints.RemoteGreetingService); + + // Start two remote greeting workflows on demand + var userIdOne = "user-one"; + var userIdTwo = "user-two"; + + // Attach information before the Workflow exists. Since AttachApprovalContext is backed + // by Signal-with-Start on the handler, this call creates the Workflow and delivers the + // note to it. + await client.ExecuteNexusOperationAsync( + svc => svc.AttachApprovalContext(new INexusRemoteGreetingService.AttachApprovalContextInput( + "queued for localization review by the nightly batch", userIdOne))); + log.Add($"Attached approval context before the workflow existed: {userIdOne}"); + + // The Workflow for this user is already running due to AttachApprovalContext. The handler + // sets the conflict policy to UseExisting, so this call attaches the Operation's + // completion callback to the running execution. + var handleOne = await client.StartNexusOperationAsync( + svc => svc.RunFromRemote(new INexusRemoteGreetingService.RunFromRemoteInput(userIdOne))); + log.Add($"Started remote workflow for user: {userIdOne}"); + + var handleTwo = await client.StartNexusOperationAsync( + svc => svc.RunFromRemote(new INexusRemoteGreetingService.RunFromRemoteInput(userIdTwo))); + log.Add($"Started remote workflow for user: {userIdTwo}"); + + // This user's Workflow was created by RunFromRemote just above, so here Signal-with-Start + // skips the start and only delivers the Signal. + await client.ExecuteNexusOperationAsync( + svc => svc.AttachApprovalContext(new INexusRemoteGreetingService.AttachApprovalContextInput( + "translation approved by the localization team", userIdTwo))); + log.Add($"Attached approval context to the running workflow: {userIdTwo}"); + + // Interact with workflow one: get languages, set language, approve + var languagesOne = await client.ExecuteNexusOperationAsync( + svc => svc.GetLanguages(new INexusRemoteGreetingService.GetLanguagesInput(false, userIdOne))); + log.Add($"[One] Supported languages: {string.Join(", ", languagesOne.Languages)}"); + + var prevLangOne = await client.ExecuteNexusOperationAsync( + svc => svc.SetLanguage(new INexusRemoteGreetingService.SetLanguageInput(Language.Spanish, userIdOne))); + log.Add($"[One] Set language from {prevLangOne} to {Language.Spanish}"); + + await client.ExecuteNexusOperationAsync( + svc => svc.Approve(new INexusRemoteGreetingService.ApproveInput("CallerRemoteWorkflow", userIdOne))); + log.Add("[One] Approved"); + + // Interact with workflow two: get language, set language, approve + var currentLangTwo = await client.ExecuteNexusOperationAsync( + svc => svc.GetLanguage(new INexusRemoteGreetingService.GetLanguageInput(userIdTwo))); + log.Add($"[Two] Current language: {currentLangTwo}"); + + var prevLangTwo = await client.ExecuteNexusOperationAsync( + svc => svc.SetLanguage(new INexusRemoteGreetingService.SetLanguageInput(Language.French, userIdTwo))); + log.Add($"[Two] Set language from {prevLangTwo} to {Language.French}"); + + await client.ExecuteNexusOperationAsync( + svc => svc.Approve(new INexusRemoteGreetingService.ApproveInput("CallerRemoteWorkflow", userIdTwo))); + log.Add("[Two] Approved"); + + // Wait for both remote workflows to complete + var resultOne = await handleOne.GetResultAsync(); + log.Add($"[One] Result: {resultOne}"); + + var resultTwo = await handleTwo.GetResultAsync(); + log.Add($"[Two] Result: {resultTwo}"); + + return log.ToArray(); + } +} diff --git a/src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs b/src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs new file mode 100644 index 0000000..cf307fd --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs @@ -0,0 +1,6 @@ +namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; + +public static class NexusEndpoints +{ + public const string RemoteGreetingService = "nexus-messaging-on-demand-pattern-endpoint"; +} diff --git a/src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs b/src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs new file mode 100644 index 0000000..f789e08 --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs @@ -0,0 +1,90 @@ +namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; + +using Temporalio.Exceptions; +using Temporalio.Workflows; +using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern; + +[Workflow] +public class GreetingWorkflow +{ + private readonly Dictionary greetings = new() + { + [Language.Chinese] = "你好,世界", + [Language.English] = "Hello, world", + }; + + private Language currentLanguage = Language.English; + private bool approved; + private string approvedBy = string.Empty; + private string? approvalContext; + + [WorkflowRun] + public async Task RunAsync(string userId) + { + // Wait for approve signal and all handlers to finish + await Workflow.WaitConditionAsync(() => approved && Workflow.AllHandlersFinished); + + var greeting = greetings.TryGetValue(currentLanguage, out var g) ? g : "Hello, world"; + return $"{greeting} (approved by {approvedBy})"; + } + + [WorkflowQuery] + public INexusRemoteGreetingService.GetLanguagesOutput QueryLanguages(bool includeUnsupported) + { + if (includeUnsupported) + { + return new(Enum.GetValues()); + } + + return new(greetings.Keys.ToArray()); + } + + [WorkflowQuery] + public Language QueryLanguage() => currentLanguage; + + [WorkflowUpdate] + public async Task SetLanguageAsync(Language newLanguage) + { + var prev = currentLanguage; + + // If language not yet in our greetings map, fetch from activity + if (!greetings.ContainsKey(newLanguage)) + { + var allGreetings = await Workflow.ExecuteActivityAsync( + (GreetingActivities a) => a.GetAllGreetingsAsync(), + new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) }); + foreach (var kvp in allGreetings) + { + greetings[kvp.Key] = kvp.Value; + } + } + + currentLanguage = newLanguage; + return prev; + } + + [WorkflowUpdateValidator(nameof(SetLanguageAsync))] + public void ValidateSetLanguage(Language newLanguage) + { + if (!Enum.IsDefined(newLanguage)) + { + throw new ApplicationFailureException($"Unsupported language: {newLanguage}"); + } + } + + [WorkflowSignal] + public Task ApproveAsync(string name) + { + approved = true; + approvedBy = name; + return Task.CompletedTask; + } + + [WorkflowSignal] + public Task AttachApprovalContextAsync(string note) + { + approvalContext = note; + return Task.CompletedTask; + } +} diff --git a/src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs b/src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs new file mode 100644 index 0000000..12a05fd --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs @@ -0,0 +1,112 @@ +namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; + +using NexusRpc.Handlers; +using Temporalio.Api.Enums.V1; +using Temporalio.Client; +using Temporalio.Nexus; +using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern; + +// On-demand pattern: no workflow is pre-started. The caller creates workflow instances +// through Nexus operations. Each operation includes a UserId so the handler can derive +// the target workflow ID. +[NexusServiceHandler(typeof(INexusRemoteGreetingService))] +public class NexusRemoteGreetingService +{ +#pragma warning disable VSTHRD200 // Names must match the INexusRemoteGreetingService operations, which can't take the Async suffix + + // Starts the GreetingWorkflow for the given user, or attaches to one already running. + // StartWorkflowAsync returns an async result, which attaches a completion callback, + // so the Operation completes when the Workflow returns. + [TemporalOperation] + public Task> RunFromRemote( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.RunFromRemoteInput input) => + client.StartWorkflowAsync( + (GreetingWorkflow wf) => wf.RunAsync(input.UserId), + new() + { + Id = GetWorkflowId(input.UserId), + + // By default, starting a Workflow whose ID is already running fails the Operation. + // Since AttachApprovalContext below can create the GreetingWorkflow first, this + // Operation needs to attach to the running execution rather than fail. + IdConflictPolicy = WorkflowIdConflictPolicy.UseExisting, + }); + + // Query: read-only, no state mutation — uses workflow query + [TemporalOperation] + public async Task> GetLanguages( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.GetLanguagesInput input) + { + // Access the Temporal client from the Nexus client passed to the handler + var handle = client.TemporalClient.GetWorkflowHandle( + GetWorkflowId(input.UserId)); + return TemporalOperationResult.SyncResult( + await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported))); + } + + // Query: read-only — returns the workflow's current language + [TemporalOperation] + public async Task> GetLanguage( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.GetLanguageInput input) + { + var handle = client.TemporalClient.GetWorkflowHandle( + GetWorkflowId(input.UserId)); + return TemporalOperationResult.SyncResult( + await handle.QueryAsync(wf => wf.QueryLanguage())); + } + + // Update: mutates state and returns the previous value — uses workflow update + [TemporalOperation] + public Task> SetLanguage( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.SetLanguageInput input) => + client.StartWorkflowUpdateAsync( + GetWorkflowId(input.UserId), + wf => wf.SetLanguageAsync(input.Language), + // An Update-backed Operation must wait for the Accepted stage. Any other stage is + // rejected with "nexus op workflow updates only support WorkflowUpdateStageAccepted + // for async updates". + new(WorkflowUpdateStage.Accepted)); + + // Signal: fire-and-forget, no return value needed — uses workflow signal + [TemporalOperation] + public async Task> Approve( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.ApproveInput input) + { + var handle = client.TemporalClient.GetWorkflowHandle( + GetWorkflowId(input.UserId)); + await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); + return TemporalOperationResult.SyncResult(default); + } + + // Signal-with-Start: starts the Workflow first if it is not already running. + // When the Workflow already exists, only the Signal is delivered. + [TemporalOperation] + public async Task> AttachApprovalContext( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + INexusRemoteGreetingService.AttachApprovalContextInput input) + { + var options = new WorkflowOptions( + id: GetWorkflowId(input.UserId), + taskQueue: NexusOperationExecutionContext.Current.Info.TaskQueue); + options.SignalWithStart((GreetingWorkflow wf) => wf.AttachApprovalContextAsync(input.Note)); + await client.TemporalClient.StartWorkflowAsync( + (GreetingWorkflow wf) => wf.RunAsync(input.UserId), options); + return TemporalOperationResult.SyncResult(default); + } + +#pragma warning restore VSTHRD200 + + private static string GetWorkflowId(string userId) => $"GreetingWorkflow_for_{userId}"; +} diff --git a/src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs b/src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs new file mode 100644 index 0000000..e0996e7 --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs @@ -0,0 +1,40 @@ +namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern; + +using NexusRpc; +using TemporalioSamples.NexusMessagingV2.Common; + +[NexusService] +public interface INexusRemoteGreetingService +{ + [NexusOperation] + string RunFromRemote(RunFromRemoteInput input); + + [NexusOperation] + GetLanguagesOutput GetLanguages(GetLanguagesInput input); + + [NexusOperation] + Language GetLanguage(GetLanguageInput input); + + [NexusOperation] + Language SetLanguage(SetLanguageInput input); + + [NexusOperation] + void Approve(ApproveInput input); + + [NexusOperation] + void AttachApprovalContext(AttachApprovalContextInput input); + + public record RunFromRemoteInput(string UserId); + + public record GetLanguagesInput(bool IncludeUnsupported, string UserId); + + public record GetLanguagesOutput(IReadOnlyList Languages); + + public record GetLanguageInput(string UserId); + + public record SetLanguageInput(Language Language, string UserId); + + public record ApproveInput(string Name, string UserId); + + public record AttachApprovalContextInput(string Note, string UserId); +} diff --git a/src/NexusMessagingV2/OnDemandPattern/README.md b/src/NexusMessagingV2/OnDemandPattern/README.md new file mode 100644 index 0000000..fe4874e --- /dev/null +++ b/src/NexusMessagingV2/OnDemandPattern/README.md @@ -0,0 +1,77 @@ +## 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 +`GreetingWorkflow`, and every operation includes a `UserId` so the handler can derive +the target Workflow ID. + +The caller Workflow: +1. Attaches approval context for the first user via `AttachApprovalContext`, before anything has + started that user's Workflow +2. Starts two remote `GreetingWorkflow` instances via `RunFromRemote` (backed by a Workflow started + through `ITemporalNexusClient.StartWorkflowAsync`) +3. Attaches approval context for the second user, whose Workflow now already exists +4. Workflow one: queries supported languages, changes to Spanish, and approves +5. Workflow two: queries the current language, changes to French, and approves +6. Waits for each to complete and returns their results + +### Running + +This sample requires a Temporal dev server build that supports Workflow Update callbacks. Download +the compatible binary from the [Temporal CLI pre-release instructions](https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support). + +Start the Temporal dev server with the required namespaces pre-created, and Workflow Update +callbacks and signal-with-start from a Workflow enabled: + +```bash +./temporal server start-dev \ + --dynamic-config-value history.enableUpdateCallbacks=true \ + --dynamic-config-value history.enableCHASMSignalBacklinks=true \ + --dynamic-config-value history.enableSignalWithStartFromWorkflow=true \ + --namespace nexus-messaging-handler-namespace \ + --namespace nexus-messaging-caller-namespace +``` + +Create the Nexus endpoint: + +```bash +./temporal operator nexus endpoint create \ + --name nexus-messaging-on-demand-pattern-endpoint \ + --target-namespace nexus-messaging-handler-namespace \ + --target-task-queue nexus-messaging-handler-sample +``` + +In one terminal, start the handler worker: + +```bash +dotnet run --project src/NexusMessagingV2 -- remote-handler-worker +``` + +In a second terminal, start the caller worker: + +```bash +dotnet run --project src/NexusMessagingV2 -- remote-caller-worker +``` + +In a third terminal, run the following command to start the example: + +```bash +dotnet run --project src/NexusMessagingV2 -- remote-caller-workflow +``` + +Expected output: + +``` +Attached approval context before the workflow existed: user-one +Started remote workflow for user: user-one +Started remote workflow for user: user-two +Attached approval context to the running workflow: user-two +[One] Supported languages: Chinese, English +[One] Set language from English to Spanish +[One] Approved +[Two] Current language: English +[Two] Set language from English to French +[Two] Approved +[One] Result: Hola, mundo (approved by CallerRemoteWorkflow) +[Two] Result: Bonjour, monde (approved by CallerRemoteWorkflow) +``` diff --git a/src/NexusMessagingV2/Program.cs b/src/NexusMessagingV2/Program.cs new file mode 100644 index 0000000..a17590b --- /dev/null +++ b/src/NexusMessagingV2/Program.cs @@ -0,0 +1,184 @@ +using Microsoft.Extensions.Logging; +using Temporalio.Client; +using Temporalio.Common.EnvConfig; +using Temporalio.Worker; +using TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; +using TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; +using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; +using CallerGreetingWorkflow = TemporalioSamples.NexusMessagingV2.CallerPattern.Handler.GreetingWorkflow; +using OnDemandGreetingWorkflow = TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler.GreetingWorkflow; +using OnDemandNexusService = TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler.NexusRemoteGreetingService; + +using var loggerFactory = LoggerFactory.Create(builder => + builder. + AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). + SetMinimumLevel(LogLevel.Information)); +var logger = loggerFactory.CreateLogger(); + +// Cancellation token cancelled on ctrl+c +using var tokenSource = new CancellationTokenSource(); +Console.CancelKeyPress += (_, eventArgs) => +{ + tokenSource.Cancel(); + eventArgs.Cancel = true; +}; + +const string HandlerNamespace = "nexus-messaging-handler-namespace"; +const string CallerNamespace = "nexus-messaging-caller-namespace"; +const string HandlerTaskQueue = "nexus-messaging-handler-sample"; +const string CallerTaskQueue = "nexus-messaging-caller-sample"; + +Task ConnectClientAsync(string temporalNamespace) +{ + var connectOptions = ClientEnvConfig.LoadClientConnectOptions(); + connectOptions.TargetHost ??= "localhost:7233"; + connectOptions.Namespace = temporalNamespace; + connectOptions.LoggerFactory = loggerFactory; + return TemporalClient.ConnectAsync(connectOptions); +} + +async Task RunHandlerWorkerAsync() +{ + logger.LogInformation("Running handler worker"); + var client = await ConnectClientAsync(HandlerNamespace); + + // Start entity workflow with UseExisting policy (entity pattern: pre-start at boot) + var workflowId = "GreetingWorkflow_for_default-user"; + try + { + await client.StartWorkflowAsync( + (CallerGreetingWorkflow wf) => wf.RunAsync("default-user"), + new(id: workflowId, taskQueue: HandlerTaskQueue) + { + IdConflictPolicy = Temporalio.Api.Enums.V1.WorkflowIdConflictPolicy.UseExisting, + }); + logger.LogInformation("Started entity workflow {WorkflowId}", workflowId); + } + catch (Temporalio.Exceptions.RpcException ex) + { + logger.LogWarning(ex, "Could not start entity workflow"); + } + + using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions(HandlerTaskQueue). + AddNexusService(new NexusGreetingService()). + AddWorkflow(). + AddAllActivities(new GreetingActivities())); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Handler worker cancelled"); + } +} + +async Task RunCallerWorkerAsync() +{ + logger.LogInformation("Running caller worker"); + using var worker = new TemporalWorker( + await ConnectClientAsync(CallerNamespace), + new TemporalWorkerOptions(CallerTaskQueue). + AddWorkflow()); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Caller worker cancelled"); + } +} + +async Task ExecuteCallerWorkflowAsync() +{ + logger.LogInformation("Executing caller workflow"); + var client = await ConnectClientAsync(CallerNamespace); + var result = await client.ExecuteWorkflowAsync( + (CallerWorkflow wf) => wf.RunAsync("default-user"), + new(id: "nexus-messaging-caller-workflow-id", taskQueue: CallerTaskQueue)); + logger.LogInformation("Caller workflow result:"); + foreach (var entry in result) + { + logger.LogInformation(" {Entry}", entry); + } +} + +async Task RunRemoteHandlerWorkerAsync() +{ + logger.LogInformation("Running remote handler worker (on-demand pattern)"); + using var worker = new TemporalWorker( + await ConnectClientAsync(HandlerNamespace), + new TemporalWorkerOptions(HandlerTaskQueue). + AddNexusService(new OnDemandNexusService()). + AddWorkflow(). + AddAllActivities(new GreetingActivities())); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Remote handler worker cancelled"); + } +} + +async Task RunRemoteCallerWorkerAsync() +{ + logger.LogInformation("Running remote caller worker (on-demand pattern)"); + using var worker = new TemporalWorker( + await ConnectClientAsync(CallerNamespace), + new TemporalWorkerOptions(CallerTaskQueue). + AddWorkflow()); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Remote caller worker cancelled"); + } +} + +async Task ExecuteRemoteCallerWorkflowAsync() +{ + logger.LogInformation("Executing remote caller workflow (on-demand pattern)"); + var client = await ConnectClientAsync(CallerNamespace); + var result = await client.ExecuteWorkflowAsync( + (CallerRemoteWorkflow wf) => wf.RunAsync(), + new(id: "nexus-messaging-remote-caller-workflow-id", taskQueue: CallerTaskQueue)); + logger.LogInformation("Remote caller workflow result:"); + foreach (var entry in result) + { + logger.LogInformation(" {Entry}", entry); + } +} + +switch (args.ElementAtOrDefault(0)) +{ + case "handler-worker": + await RunHandlerWorkerAsync(); + break; + case "caller-worker": + await RunCallerWorkerAsync(); + break; + case "caller-workflow": + await ExecuteCallerWorkflowAsync(); + break; + case "remote-handler-worker": + await RunRemoteHandlerWorkerAsync(); + break; + case "remote-caller-worker": + await RunRemoteCallerWorkerAsync(); + break; + case "remote-caller-workflow": + await ExecuteRemoteCallerWorkflowAsync(); + break; + default: + throw new ArgumentException( + "Must pass 'handler-worker', 'caller-worker', 'caller-workflow', " + + "'remote-handler-worker', 'remote-caller-worker', or 'remote-caller-workflow' as the single argument"); +} diff --git a/src/NexusMessagingV2/README.md b/src/NexusMessagingV2/README.md new file mode 100644 index 0000000..223d528 --- /dev/null +++ b/src/NexusMessagingV2/README.md @@ -0,0 +1,18 @@ +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** | Query, update, and 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 passes a UserId; the handler derives the Workflow ID | +| **Nexus service** | `NexusGreetingService` | `NexusRemoteGreetingService` | + +The `GreetingActivities` and `Language` enum are shared between the two patterns (in the `Common/` +directory). Each pattern has its own `GreetingWorkflow`, Nexus service interface, and handler +implementation. This highlights that the same activity logic can be exposed through Nexus in different +ways depending on whether the caller needs lifecycle control. + +See each directory's README for running instructions: +- [CallerPattern README](CallerPattern/README.md) +- [OnDemandPattern README](OnDemandPattern/README.md) diff --git a/src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj b/src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj new file mode 100644 index 0000000..573e2c9 --- /dev/null +++ b/src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj @@ -0,0 +1,11 @@ + + + + Exe + + + + + + + diff --git a/tests/NexusMessagingV2/CallerPatternTests.cs b/tests/NexusMessagingV2/CallerPatternTests.cs new file mode 100644 index 0000000..1290cdc --- /dev/null +++ b/tests/NexusMessagingV2/CallerPatternTests.cs @@ -0,0 +1,80 @@ +namespace TemporalioSamples.Tests.NexusMessagingV2; + +using Temporalio.Client; +using Temporalio.Testing; +using Temporalio.Worker; +using TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; +using TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; +using TemporalioSamples.NexusMessagingV2.Common; +using Xunit; +using Xunit.Abstractions; + +public class CallerPatternTests : TestBase +{ + public CallerPatternTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public async Task RunAsync_CallerWorkflow_Succeeds() + { + // SetLanguage is backed by a Workflow Update, which needs a dev server build that supports + // update callbacks. + await using var env = await WorkflowEnvironment.StartLocalAsync(new() + { + DevServerOptions = new() + { + DownloadVersion = "v1.7.4-standalone-nexus-operations", + ExtraArgs = + [ + "--dynamic-config-value", + "history.enableUpdateCallbacks=true", + "--dynamic-config-value", + "history.enableCHASMSignalBacklinks=true", + ], + }, + }); + + var handlerTaskQueue = $"tq-{Guid.NewGuid()}"; + await env.CreateNexusEndpointAsync(NexusEndpoints.GreetingService, handlerTaskQueue); + var userId = $"user-{Guid.NewGuid()}"; + var workflowId = $"GreetingWorkflow_for_{userId}"; + + // Start entity workflow + await env.Client.StartWorkflowAsync( + (GreetingWorkflow wf) => wf.RunAsync(userId), + new(id: workflowId, taskQueue: handlerTaskQueue) + { + IdConflictPolicy = Temporalio.Api.Enums.V1.WorkflowIdConflictPolicy.UseExisting, + }); + + // Run handler worker + using var handlerWorker = new TemporalWorker( + env.Client, + new TemporalWorkerOptions(handlerTaskQueue). + AddNexusService(new NexusGreetingService()). + AddWorkflow(). + AddAllActivities(new GreetingActivities())); + await handlerWorker.ExecuteAsync(async () => + { + // Run caller worker + using var callerWorker = new TemporalWorker( + env.Client, + new TemporalWorkerOptions($"tq-{Guid.NewGuid()}"). + AddWorkflow()); + await callerWorker.ExecuteAsync(async () => + { + var result = await env.Client.ExecuteWorkflowAsync( + (CallerWorkflow wf) => wf.RunAsync(userId), + new(id: $"wf-{Guid.NewGuid()}", taskQueue: callerWorker.Options.TaskQueue!)); + + Assert.Equal(4, result.Length); + Assert.Contains("Supported languages:", result[0]); + Assert.Contains("Current language:", result[1]); + Assert.Contains($"Set language from {Language.English} to {Language.Chinese}", result[2]); + Assert.Equal("Approved workflow", result[3]); + }); + }); + } +} diff --git a/tests/NexusMessagingV2/OnDemandPatternTests.cs b/tests/NexusMessagingV2/OnDemandPatternTests.cs new file mode 100644 index 0000000..c12f9ea --- /dev/null +++ b/tests/NexusMessagingV2/OnDemandPatternTests.cs @@ -0,0 +1,79 @@ +namespace TemporalioSamples.Tests.NexusMessagingV2; + +using Temporalio.Client; +using Temporalio.Testing; +using Temporalio.Worker; +using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; +using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; +using Xunit; +using Xunit.Abstractions; + +public class OnDemandPatternTests : TestBase +{ + public OnDemandPatternTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public async Task RunAsync_CallerRemoteWorkflow_Succeeds() + { + // SetLanguage is backed by a Workflow Update and AttachApprovalContext by + // Signal-with-Start, both of which need a dev server build that supports them. + await using var env = await WorkflowEnvironment.StartLocalAsync(new() + { + DevServerOptions = new() + { + DownloadVersion = "v1.7.4-standalone-nexus-operations", + ExtraArgs = + [ + "--dynamic-config-value", + "history.enableUpdateCallbacks=true", + "--dynamic-config-value", + "history.enableCHASMSignalBacklinks=true", + "--dynamic-config-value", + "history.enableSignalWithStartFromWorkflow=true", + ], + }, + }); + + var handlerTaskQueue = $"tq-{Guid.NewGuid()}"; + await env.CreateNexusEndpointAsync(NexusEndpoints.RemoteGreetingService, handlerTaskQueue); + + // Run handler worker + using var handlerWorker = new TemporalWorker( + env.Client, + new TemporalWorkerOptions(handlerTaskQueue). + AddNexusService(new NexusRemoteGreetingService()). + AddWorkflow(). + AddAllActivities(new GreetingActivities())); + await handlerWorker.ExecuteAsync(async () => + { + // Run caller worker + using var callerWorker = new TemporalWorker( + env.Client, + new TemporalWorkerOptions($"tq-{Guid.NewGuid()}"). + AddWorkflow()); + await callerWorker.ExecuteAsync(async () => + { + var result = await env.Client.ExecuteWorkflowAsync( + (CallerRemoteWorkflow wf) => wf.RunAsync(), + new(id: $"wf-{Guid.NewGuid()}", taskQueue: callerWorker.Options.TaskQueue!)); + + Assert.Contains("Attached approval context before the workflow existed: user-one", result[0]); + Assert.Contains("Started remote workflow for user: user-one", result[1]); + Assert.Contains("Started remote workflow for user: user-two", result[2]); + Assert.Contains("Attached approval context to the running workflow: user-two", result[3]); + Assert.Contains("[One] Supported languages:", result[4]); + Assert.Contains($"[One] Set language from {Language.English} to {Language.Spanish}", result[5]); + Assert.Contains("[One] Approved", result[6]); + Assert.Contains("[Two] Current language:", result[7]); + Assert.Contains($"[Two] Set language from {Language.English} to {Language.French}", result[8]); + Assert.Contains("[Two] Approved", result[9]); + Assert.Contains("[One] Result:", result[10]); + Assert.Contains("[Two] Result:", result[11]); + }); + }); + } +} From f0622f248d7570c49acd6404433da6cb41b00acc Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Tue, 29 Sep 2026 16:07:03 -0700 Subject: [PATCH 2/5] Revert "Update Nexus messaging samples to use Temporal Operation handler (#225)" This reverts commit 1289de95c9c4e58e24e0c8cbfa964cdc81f2751f. --- .../Handler/NexusGreetingService.cs | 91 ++++++------ src/NexusMessaging/CallerPattern/README.md | 28 +--- .../Caller/CallerRemoteWorkflow.workflow.cs | 18 --- .../Handler/GreetingWorkflow.workflow.cs | 8 - .../Handler/NexusRemoteGreetingService.cs | 138 +++++++----------- .../INexusRemoteGreetingService.cs | 5 - src/NexusMessaging/OnDemandPattern/README.md | 53 ++----- tests/NexusMessaging/CallerPatternTests.cs | 34 +---- tests/NexusMessaging/OnDemandPatternTests.cs | 56 +++---- 9 files changed, 137 insertions(+), 294 deletions(-) diff --git a/src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs b/src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs index d0038ac..4c7aa4e 100644 --- a/src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs +++ b/src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs @@ -1,7 +1,6 @@ namespace TemporalioSamples.NexusMessaging.CallerPattern.Handler; using NexusRpc.Handlers; -using Temporalio.Client; using Temporalio.Nexus; using TemporalioSamples.NexusMessaging.CallerPattern; using TemporalioSamples.NexusMessaging.Common; @@ -12,63 +11,55 @@ namespace TemporalioSamples.NexusMessaging.CallerPattern.Handler; [NexusServiceHandler(typeof(INexusGreetingService))] public class NexusGreetingService { -#pragma warning disable VSTHRD200 // Names must match the INexusGreetingService operations, which can't take the Async suffix + // OperationHandler.Sync means the result is returned inline to the Nexus caller + // (as opposed to WorkflowRunOperationHandler, which returns an async operation token). + // The lambda may still be async internally. // Query: read-only, no state mutation — uses workflow query - [TemporalOperation] - public async Task> GetLanguages( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusGreetingService.GetLanguagesInput input) - { - // Access the Temporal client from the Nexus client passed to the handler - var handle = client.TemporalClient.GetWorkflowHandle( - WorkflowIdForUser(input.UserId)); - return TemporalOperationResult.SyncResult( - await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported))); - } + [NexusOperationHandler] + public IOperationHandler GetLanguages() => + OperationHandler.Sync( + async (ctx, input) => + { + // Access the Temporal client from the Nexus operation context + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(WorkflowIdForUser(input.UserId)); + return await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported)); + }); // Query: read-only — returns the workflow's current language - [TemporalOperation] - public async Task> GetLanguage( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusGreetingService.GetLanguageInput input) - { - var handle = client.TemporalClient.GetWorkflowHandle( - WorkflowIdForUser(input.UserId)); - return TemporalOperationResult.SyncResult( - await handle.QueryAsync(wf => wf.QueryLanguage())); - } + [NexusOperationHandler] + public IOperationHandler GetLanguage() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(WorkflowIdForUser(input.UserId)); + return await handle.QueryAsync(wf => wf.QueryLanguage()); + }); // Update: mutates state and returns the previous value — uses workflow update - [TemporalOperation] - public Task> SetLanguage( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusGreetingService.SetLanguageInput input) => - client.StartWorkflowUpdateAsync( - WorkflowIdForUser(input.UserId), - wf => wf.SetLanguageAsync(input.Language), - // An Update-backed Operation must wait for the Accepted stage. Any other stage is - // rejected with "nexus op workflow updates only support WorkflowUpdateStageAccepted - // for async updates". - new(WorkflowUpdateStage.Accepted)); + [NexusOperationHandler] + public IOperationHandler SetLanguage() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(WorkflowIdForUser(input.UserId)); + return await handle.ExecuteUpdateAsync(wf => wf.SetLanguageAsync(input.Language)); + }); // Signal: fire-and-forget, no return value needed — uses workflow signal - [TemporalOperation] - public async Task> Approve( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusGreetingService.ApproveInput input) - { - var handle = client.TemporalClient.GetWorkflowHandle( - WorkflowIdForUser(input.UserId)); - await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); - return TemporalOperationResult.SyncResult(default); - } - -#pragma warning restore VSTHRD200 + [NexusOperationHandler] + public IOperationHandler Approve() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(WorkflowIdForUser(input.UserId)); + await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); + return default; + }); private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}"; } diff --git a/src/NexusMessaging/CallerPattern/README.md b/src/NexusMessaging/CallerPattern/README.md index 54ff2a1..083e8ab 100644 --- a/src/NexusMessaging/CallerPattern/README.md +++ b/src/NexusMessaging/CallerPattern/README.md @@ -14,24 +14,19 @@ The caller Workflow: ### Running -This sample requires a Temporal dev server build that supports Workflow Update callbacks. Download -the compatible binary from the [Temporal CLI pre-release instructions](https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support). - -Start the Temporal dev server with the required namespaces pre-created and Workflow Update -callbacks enabled: +Start a Temporal server: ```bash -./temporal server start-dev \ - --dynamic-config-value history.enableUpdateCallbacks=true \ - --dynamic-config-value history.enableCHASMSignalBacklinks=true \ - --namespace nexus-messaging-handler-namespace \ - --namespace nexus-messaging-caller-namespace +temporal server start-dev ``` -Create the Nexus endpoint: +Create the namespaces and Nexus endpoint: ```bash -./temporal operator nexus endpoint create \ +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-caller-pattern-endpoint \ --target-namespace nexus-messaging-handler-namespace \ --target-task-queue nexus-messaging-handler-sample @@ -54,12 +49,3 @@ In a third terminal, run the following command to start the example: ```bash dotnet run --project src/NexusMessaging -- caller-workflow ``` - -Expected output: - -``` -Supported languages: Chinese, English -Current language: English -Set language from English to Chinese -Approved workflow -``` diff --git a/src/NexusMessaging/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs b/src/NexusMessaging/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs index 4e97f6e..39d8cd7 100644 --- a/src/NexusMessaging/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs +++ b/src/NexusMessaging/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs @@ -18,17 +18,6 @@ public async Task RunAsync() var userIdOne = "user-one"; var userIdTwo = "user-two"; - // Attach information before the Workflow exists. Since AttachApprovalContext is backed - // by Signal-with-Start on the handler, this call creates the Workflow and delivers the - // note to it. - await client.ExecuteNexusOperationAsync( - svc => svc.AttachApprovalContext(new INexusRemoteGreetingService.AttachApprovalContextInput( - "queued for localization review by the nightly batch", userIdOne))); - log.Add($"Attached approval context before the workflow existed: {userIdOne}"); - - // The Workflow for this user is already running due to AttachApprovalContext. The handler - // sets the conflict policy to UseExisting, so this call attaches the Operation's - // completion callback to the running execution. var handleOne = await client.StartNexusOperationAsync( svc => svc.RunFromRemote(new INexusRemoteGreetingService.RunFromRemoteInput(userIdOne))); log.Add($"Started remote workflow for user: {userIdOne}"); @@ -37,13 +26,6 @@ await client.ExecuteNexusOperationAsync( svc => svc.RunFromRemote(new INexusRemoteGreetingService.RunFromRemoteInput(userIdTwo))); log.Add($"Started remote workflow for user: {userIdTwo}"); - // This user's Workflow was created by RunFromRemote just above, so here Signal-with-Start - // skips the start and only delivers the Signal. - await client.ExecuteNexusOperationAsync( - svc => svc.AttachApprovalContext(new INexusRemoteGreetingService.AttachApprovalContextInput( - "translation approved by the localization team", userIdTwo))); - log.Add($"Attached approval context to the running workflow: {userIdTwo}"); - // Interact with workflow one: get languages, set language, approve var languagesOne = await client.ExecuteNexusOperationAsync( svc => svc.GetLanguages(new INexusRemoteGreetingService.GetLanguagesInput(false, userIdOne))); diff --git a/src/NexusMessaging/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs b/src/NexusMessaging/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs index e9c9c1d..f3042d7 100644 --- a/src/NexusMessaging/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs +++ b/src/NexusMessaging/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs @@ -17,7 +17,6 @@ public class GreetingWorkflow private Language currentLanguage = Language.English; private bool approved; private string approvedBy = string.Empty; - private string? approvalContext; [WorkflowRun] public async Task RunAsync(string userId) @@ -80,11 +79,4 @@ public Task ApproveAsync(string name) approvedBy = name; return Task.CompletedTask; } - - [WorkflowSignal] - public Task AttachApprovalContextAsync(string note) - { - approvalContext = note; - return Task.CompletedTask; - } } diff --git a/src/NexusMessaging/OnDemandPattern/Handler/NexusRemoteGreetingService.cs b/src/NexusMessaging/OnDemandPattern/Handler/NexusRemoteGreetingService.cs index f27e111..52b5c46 100644 --- a/src/NexusMessaging/OnDemandPattern/Handler/NexusRemoteGreetingService.cs +++ b/src/NexusMessaging/OnDemandPattern/Handler/NexusRemoteGreetingService.cs @@ -1,8 +1,6 @@ namespace TemporalioSamples.NexusMessaging.OnDemandPattern.Handler; using NexusRpc.Handlers; -using Temporalio.Api.Enums.V1; -using Temporalio.Client; using Temporalio.Nexus; using TemporalioSamples.NexusMessaging.Common; using TemporalioSamples.NexusMessaging.OnDemandPattern; @@ -13,100 +11,66 @@ namespace TemporalioSamples.NexusMessaging.OnDemandPattern.Handler; [NexusServiceHandler(typeof(INexusRemoteGreetingService))] public class NexusRemoteGreetingService { -#pragma warning disable VSTHRD200 // Names must match the INexusRemoteGreetingService operations, which can't take the Async suffix + // WorkflowRunOperationHandler starts a backing workflow and returns its handle to the + // Nexus infrastructure. The caller receives an async operation token and can poll for + // the workflow result later via GetResultAsync. + [NexusOperationHandler] + public IOperationHandler RunFromRemote() => + WorkflowRunOperationHandler.FromHandleFactory( + (WorkflowRunOperationContext context, INexusRemoteGreetingService.RunFromRemoteInput input) => + context.StartWorkflowAsync( + (GreetingWorkflow wf) => wf.RunAsync(input.UserId), + new() { Id = GetWorkflowId(input.UserId) })); - // Starts the GreetingWorkflow for the given user, or attaches to one already running. - // StartWorkflowAsync returns an async result, which attaches a completion callback, - // so the Operation completes when the Workflow returns. - [TemporalOperation] - public Task> RunFromRemote( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.RunFromRemoteInput input) => - client.StartWorkflowAsync( - (GreetingWorkflow wf) => wf.RunAsync(input.UserId), - new() - { - Id = GetWorkflowId(input.UserId), - - // By default, starting a Workflow whose ID is already running fails the Operation. - // Since AttachApprovalContext below can create the GreetingWorkflow first, this - // Operation needs to attach to the running execution rather than fail. - IdConflictPolicy = WorkflowIdConflictPolicy.UseExisting, - }); + // OperationHandler.Sync means the result is returned inline to the Nexus caller + // (as opposed to WorkflowRunOperationHandler, which returns an async operation token). + // The lambda may still be async internally. // Query: read-only, no state mutation — uses workflow query - [TemporalOperation] - public async Task> GetLanguages( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.GetLanguagesInput input) - { - // Access the Temporal client from the Nexus client passed to the handler - var handle = client.TemporalClient.GetWorkflowHandle( - GetWorkflowId(input.UserId)); - return TemporalOperationResult.SyncResult( - await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported))); - } + [NexusOperationHandler] + public IOperationHandler GetLanguages() => + OperationHandler.Sync( + async (ctx, input) => + { + // Access the Temporal client from the Nexus operation context + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(GetWorkflowId(input.UserId)); + return await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported)); + }); // Query: read-only — returns the workflow's current language - [TemporalOperation] - public async Task> GetLanguage( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.GetLanguageInput input) - { - var handle = client.TemporalClient.GetWorkflowHandle( - GetWorkflowId(input.UserId)); - return TemporalOperationResult.SyncResult( - await handle.QueryAsync(wf => wf.QueryLanguage())); - } + [NexusOperationHandler] + public IOperationHandler GetLanguage() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(GetWorkflowId(input.UserId)); + return await handle.QueryAsync(wf => wf.QueryLanguage()); + }); // Update: mutates state and returns the previous value — uses workflow update - [TemporalOperation] - public Task> SetLanguage( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.SetLanguageInput input) => - client.StartWorkflowUpdateAsync( - GetWorkflowId(input.UserId), - wf => wf.SetLanguageAsync(input.Language), - // An Update-backed Operation must wait for the Accepted stage. Any other stage is - // rejected with "nexus op workflow updates only support WorkflowUpdateStageAccepted - // for async updates". - new(WorkflowUpdateStage.Accepted)); + [NexusOperationHandler] + public IOperationHandler SetLanguage() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(GetWorkflowId(input.UserId)); + return await handle.ExecuteUpdateAsync(wf => wf.SetLanguageAsync(input.Language)); + }); // Signal: fire-and-forget, no return value needed — uses workflow signal - [TemporalOperation] - public async Task> Approve( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.ApproveInput input) - { - var handle = client.TemporalClient.GetWorkflowHandle( - GetWorkflowId(input.UserId)); - await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); - return TemporalOperationResult.SyncResult(default); - } - - // Signal-with-Start: starts the Workflow first if it is not already running. - // When the Workflow already exists, only the Signal is delivered. - [TemporalOperation] - public async Task> AttachApprovalContext( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - INexusRemoteGreetingService.AttachApprovalContextInput input) - { - var options = new WorkflowOptions( - id: GetWorkflowId(input.UserId), - taskQueue: NexusOperationExecutionContext.Current.Info.TaskQueue); - options.SignalWithStart((GreetingWorkflow wf) => wf.AttachApprovalContextAsync(input.Note)); - await client.TemporalClient.StartWorkflowAsync( - (GreetingWorkflow wf) => wf.RunAsync(input.UserId), options); - return TemporalOperationResult.SyncResult(default); - } - -#pragma warning restore VSTHRD200 + [NexusOperationHandler] + public IOperationHandler Approve() => + OperationHandler.Sync( + async (ctx, input) => + { + var client = NexusOperationExecutionContext.Current.TemporalClient; + var handle = client.GetWorkflowHandle(GetWorkflowId(input.UserId)); + await handle.SignalAsync(wf => wf.ApproveAsync(input.Name)); + return default; + }); private static string GetWorkflowId(string userId) => $"GreetingWorkflow_for_{userId}"; } diff --git a/src/NexusMessaging/OnDemandPattern/INexusRemoteGreetingService.cs b/src/NexusMessaging/OnDemandPattern/INexusRemoteGreetingService.cs index 5e3a6cf..d0b8121 100644 --- a/src/NexusMessaging/OnDemandPattern/INexusRemoteGreetingService.cs +++ b/src/NexusMessaging/OnDemandPattern/INexusRemoteGreetingService.cs @@ -21,9 +21,6 @@ public interface INexusRemoteGreetingService [NexusOperation] void Approve(ApproveInput input); - [NexusOperation] - void AttachApprovalContext(AttachApprovalContextInput input); - public record RunFromRemoteInput(string UserId); public record GetLanguagesInput(bool IncludeUnsupported, string UserId); @@ -35,6 +32,4 @@ public record GetLanguageInput(string UserId); public record SetLanguageInput(Language Language, string UserId); public record ApproveInput(string Name, string UserId); - - public record AttachApprovalContextInput(string Note, string UserId); } diff --git a/src/NexusMessaging/OnDemandPattern/README.md b/src/NexusMessaging/OnDemandPattern/README.md index 0643fb9..ef99357 100644 --- a/src/NexusMessaging/OnDemandPattern/README.md +++ b/src/NexusMessaging/OnDemandPattern/README.md @@ -1,41 +1,31 @@ ## 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 -`GreetingWorkflow`, and every operation includes a `UserId` so the handler can derive +operations. `NexusRemoteGreetingService` adds a `RunFromRemote` operation that starts a new +`GreetingWorkflow`, and every other operation includes a `UserId` so the handler can derive the target Workflow ID. The caller Workflow: -1. Attaches approval context for the first user via `AttachApprovalContext`, before anything has - started that user's Workflow -2. Starts two remote `GreetingWorkflow` instances via `RunFromRemote` (backed by a Workflow started - through `ITemporalNexusClient.StartWorkflowAsync`) -3. Attaches approval context for the second user, whose Workflow now already exists -4. Workflow one: queries supported languages, changes to Spanish, and approves -5. Workflow two: queries the current language, changes to French, and approves -6. Waits for each to complete and returns their results +1. Starts two remote `GreetingWorkflow` instances via `RunFromRemote` (backed by `WorkflowRunOperationHandler`) +2. Workflow one: queries supported languages, changes to Spanish, and approves +3. Workflow two: queries the current language, changes to French, and approves +4. Waits for each to complete and returns their results ### Running -This sample requires a Temporal dev server build that supports Workflow Update callbacks. Download -the compatible binary from the [Temporal CLI pre-release instructions](https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support). - -Start the Temporal dev server with the required namespaces pre-created, and Workflow Update -callbacks and signal-with-start from a Workflow enabled: +Start a Temporal server: ```bash -./temporal server start-dev \ - --dynamic-config-value history.enableUpdateCallbacks=true \ - --dynamic-config-value history.enableCHASMSignalBacklinks=true \ - --dynamic-config-value history.enableSignalWithStartFromWorkflow=true \ - --namespace nexus-messaging-handler-namespace \ - --namespace nexus-messaging-caller-namespace +temporal server start-dev ``` -Create the Nexus endpoint: +Create the namespaces and Nexus endpoint: ```bash -./temporal operator nexus endpoint create \ +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-on-demand-pattern-endpoint \ --target-namespace nexus-messaging-handler-namespace \ --target-task-queue nexus-messaging-handler-sample @@ -58,20 +48,3 @@ In a third terminal, run the following command to start the example: ```bash dotnet run --project src/NexusMessaging -- remote-caller-workflow ``` - -Expected output: - -``` -Attached approval context before the workflow existed: user-one -Started remote workflow for user: user-one -Started remote workflow for user: user-two -Attached approval context to the running workflow: user-two -[One] Supported languages: Chinese, English -[One] Set language from English to Spanish -[One] Approved -[Two] Current language: English -[Two] Set language from English to French -[Two] Approved -[One] Result: Hola, mundo (approved by CallerRemoteWorkflow) -[Two] Result: Bonjour, monde (approved by CallerRemoteWorkflow) -``` diff --git a/tests/NexusMessaging/CallerPatternTests.cs b/tests/NexusMessaging/CallerPatternTests.cs index 5ec1770..a9938b2 100644 --- a/tests/NexusMessaging/CallerPatternTests.cs +++ b/tests/NexusMessaging/CallerPatternTests.cs @@ -1,7 +1,6 @@ namespace TemporalioSamples.Tests.NexusMessaging; using Temporalio.Client; -using Temporalio.Testing; using Temporalio.Worker; using TemporalioSamples.NexusMessaging.CallerPattern.Caller; using TemporalioSamples.NexusMessaging.CallerPattern.Handler; @@ -9,40 +8,23 @@ namespace TemporalioSamples.Tests.NexusMessaging; using Xunit; using Xunit.Abstractions; -public class CallerPatternTests : TestBase +public class CallerPatternTests : WorkflowEnvironmentTestBase { - public CallerPatternTests(ITestOutputHelper output) - : base(output) + public CallerPatternTests(ITestOutputHelper output, WorkflowEnvironment env) + : base(output, env) { } [Fact] public async Task RunAsync_CallerWorkflow_Succeeds() { - // SetLanguage is backed by a Workflow Update, which needs a dev server build that supports - // update callbacks. - await using var env = await WorkflowEnvironment.StartLocalAsync(new() - { - DevServerOptions = new() - { - DownloadVersion = "v1.7.4-standalone-nexus-operations", - ExtraArgs = - [ - "--dynamic-config-value", - "history.enableUpdateCallbacks=true", - "--dynamic-config-value", - "history.enableCHASMSignalBacklinks=true", - ], - }, - }); - var handlerTaskQueue = $"tq-{Guid.NewGuid()}"; - await env.CreateNexusEndpointAsync(NexusEndpoints.GreetingService, handlerTaskQueue); + await Env.TestEnv.CreateNexusEndpointAsync(NexusEndpoints.GreetingService, handlerTaskQueue); var userId = $"user-{Guid.NewGuid()}"; var workflowId = $"GreetingWorkflow_for_{userId}"; // Start entity workflow - await env.Client.StartWorkflowAsync( + await Client.StartWorkflowAsync( (GreetingWorkflow wf) => wf.RunAsync(userId), new(id: workflowId, taskQueue: handlerTaskQueue) { @@ -51,7 +33,7 @@ await env.Client.StartWorkflowAsync( // Run handler worker using var handlerWorker = new TemporalWorker( - env.Client, + Client, new TemporalWorkerOptions(handlerTaskQueue). AddNexusService(new NexusGreetingService()). AddWorkflow(). @@ -60,12 +42,12 @@ await handlerWorker.ExecuteAsync(async () => { // Run caller worker using var callerWorker = new TemporalWorker( - env.Client, + Client, new TemporalWorkerOptions($"tq-{Guid.NewGuid()}"). AddWorkflow()); await callerWorker.ExecuteAsync(async () => { - var result = await env.Client.ExecuteWorkflowAsync( + var result = await Client.ExecuteWorkflowAsync( (CallerWorkflow wf) => wf.RunAsync(userId), new(id: $"wf-{Guid.NewGuid()}", taskQueue: callerWorker.Options.TaskQueue!)); diff --git a/tests/NexusMessaging/OnDemandPatternTests.cs b/tests/NexusMessaging/OnDemandPatternTests.cs index 4b7f7b3..61bc21c 100644 --- a/tests/NexusMessaging/OnDemandPatternTests.cs +++ b/tests/NexusMessaging/OnDemandPatternTests.cs @@ -1,7 +1,6 @@ namespace TemporalioSamples.Tests.NexusMessaging; using Temporalio.Client; -using Temporalio.Testing; using Temporalio.Worker; using TemporalioSamples.NexusMessaging.Common; using TemporalioSamples.NexusMessaging.OnDemandPattern.Caller; @@ -9,41 +8,22 @@ namespace TemporalioSamples.Tests.NexusMessaging; using Xunit; using Xunit.Abstractions; -public class OnDemandPatternTests : TestBase +public class OnDemandPatternTests : WorkflowEnvironmentTestBase { - public OnDemandPatternTests(ITestOutputHelper output) - : base(output) + public OnDemandPatternTests(ITestOutputHelper output, WorkflowEnvironment env) + : base(output, env) { } [Fact] public async Task RunAsync_CallerRemoteWorkflow_Succeeds() { - // SetLanguage is backed by a Workflow Update and AttachApprovalContext by - // Signal-with-Start, both of which need a dev server build that supports them. - await using var env = await WorkflowEnvironment.StartLocalAsync(new() - { - DevServerOptions = new() - { - DownloadVersion = "v1.7.4-standalone-nexus-operations", - ExtraArgs = - [ - "--dynamic-config-value", - "history.enableUpdateCallbacks=true", - "--dynamic-config-value", - "history.enableCHASMSignalBacklinks=true", - "--dynamic-config-value", - "history.enableSignalWithStartFromWorkflow=true", - ], - }, - }); - var handlerTaskQueue = $"tq-{Guid.NewGuid()}"; - await env.CreateNexusEndpointAsync(NexusEndpoints.RemoteGreetingService, handlerTaskQueue); + await Env.TestEnv.CreateNexusEndpointAsync(NexusEndpoints.RemoteGreetingService, handlerTaskQueue); // Run handler worker using var handlerWorker = new TemporalWorker( - env.Client, + Client, new TemporalWorkerOptions(handlerTaskQueue). AddNexusService(new NexusRemoteGreetingService()). AddWorkflow(). @@ -52,27 +32,25 @@ await handlerWorker.ExecuteAsync(async () => { // Run caller worker using var callerWorker = new TemporalWorker( - env.Client, + Client, new TemporalWorkerOptions($"tq-{Guid.NewGuid()}"). AddWorkflow()); await callerWorker.ExecuteAsync(async () => { - var result = await env.Client.ExecuteWorkflowAsync( + var result = await Client.ExecuteWorkflowAsync( (CallerRemoteWorkflow wf) => wf.RunAsync(), new(id: $"wf-{Guid.NewGuid()}", taskQueue: callerWorker.Options.TaskQueue!)); - Assert.Contains("Attached approval context before the workflow existed: user-one", result[0]); - Assert.Contains("Started remote workflow for user: user-one", result[1]); - Assert.Contains("Started remote workflow for user: user-two", result[2]); - Assert.Contains("Attached approval context to the running workflow: user-two", result[3]); - Assert.Contains("[One] Supported languages:", result[4]); - Assert.Contains($"[One] Set language from {Language.English} to {Language.Spanish}", result[5]); - Assert.Contains("[One] Approved", result[6]); - Assert.Contains("[Two] Current language:", result[7]); - Assert.Contains($"[Two] Set language from {Language.English} to {Language.French}", result[8]); - Assert.Contains("[Two] Approved", result[9]); - Assert.Contains("[One] Result:", result[10]); - Assert.Contains("[Two] Result:", result[11]); + Assert.Contains("Started remote workflow for user: user-one", result[0]); + Assert.Contains("Started remote workflow for user: user-two", result[1]); + Assert.Contains("[One] Supported languages:", result[2]); + Assert.Contains($"[One] Set language from {Language.English} to {Language.Spanish}", result[3]); + Assert.Contains("[One] Approved", result[4]); + Assert.Contains("[Two] Current language:", result[5]); + Assert.Contains($"[Two] Set language from {Language.English} to {Language.French}", result[6]); + Assert.Contains("[Two] Approved", result[7]); + Assert.Contains("[One] Result:", result[8]); + Assert.Contains("[Two] Result:", result[9]); }); }); } From c22b1fa129d92720f0d77c5cddbccd4a3a794331 Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Tue, 29 Sep 2026 16:08:13 -0700 Subject: [PATCH 3/5] Add NexusMessagingV2 to solution --- TemporalioSamples.sln | 93 ++++++++++++++++------------ tests/TemporalioSamples.Tests.csproj | 1 + 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/TemporalioSamples.sln b/TemporalioSamples.sln index c945182..5c20698 100644 --- a/TemporalioSamples.sln +++ b/TemporalioSamples.sln @@ -145,6 +145,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusStandaloneActivity", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusStandaloneActivity", "src\NexusStandaloneActivity\TemporalioSamples.NexusStandaloneActivity.csproj", "{4D8C9F9B-F8E3-4160-9286-32C3966A0125}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusMessagingV2", "src\NexusMessagingV2\TemporalioSamples.NexusMessagingV2.csproj", "{02BFCAD9-F6E2-42FA-AA7F-264515D6D158}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -587,18 +589,6 @@ Global {23473564-5056-4697-A652-5FB389EFC544}.Release|x64.Build.0 = Release|Any CPU {23473564-5056-4697-A652-5FB389EFC544}.Release|x86.ActiveCfg = Release|Any CPU {23473564-5056-4697-A652-5FB389EFC544}.Release|x86.Build.0 = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x64.ActiveCfg = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x64.Build.0 = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x86.ActiveCfg = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x86.Build.0 = Debug|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|Any CPU.Build.0 = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x64.ActiveCfg = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x64.Build.0 = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x86.ActiveCfg = Release|Any CPU - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x86.Build.0 = Release|Any CPU {72B8CC84-138D-42AD-B6DE-38C85B055794}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {72B8CC84-138D-42AD-B6DE-38C85B055794}.Debug|Any CPU.Build.0 = Debug|Any CPU {72B8CC84-138D-42AD-B6DE-38C85B055794}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -611,6 +601,18 @@ Global {72B8CC84-138D-42AD-B6DE-38C85B055794}.Release|x64.Build.0 = Release|Any CPU {72B8CC84-138D-42AD-B6DE-38C85B055794}.Release|x86.ActiveCfg = Release|Any CPU {72B8CC84-138D-42AD-B6DE-38C85B055794}.Release|x86.Build.0 = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x64.Build.0 = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Debug|x86.Build.0 = Debug|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|Any CPU.Build.0 = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x64.ActiveCfg = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x64.Build.0 = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x86.ActiveCfg = Release|Any CPU + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0}.Release|x86.Build.0 = Release|Any CPU {8BE23F78-7178-4924-AB45-4AF74454CC97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8BE23F78-7178-4924-AB45-4AF74454CC97}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BE23F78-7178-4924-AB45-4AF74454CC97}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -635,6 +637,30 @@ Global {52CE80AF-09C3-4209-8A21-6CFFAA3B2B01}.Release|x64.Build.0 = Release|Any CPU {52CE80AF-09C3-4209-8A21-6CFFAA3B2B01}.Release|x86.ActiveCfg = Release|Any CPU {52CE80AF-09C3-4209-8A21-6CFFAA3B2B01}.Release|x86.Build.0 = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x64.Build.0 = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x86.Build.0 = Debug|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|Any CPU.Build.0 = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x64.ActiveCfg = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x64.Build.0 = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x86.ActiveCfg = Release|Any CPU + {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x86.Build.0 = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x64.Build.0 = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x86.Build.0 = Debug|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|Any CPU.Build.0 = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x64.ActiveCfg = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x64.Build.0 = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x86.ActiveCfg = Release|Any CPU + {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x86.Build.0 = Release|Any CPU {B37B3E98-4B04-48B8-9017-F0EDEDC7BD98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B37B3E98-4B04-48B8-9017-F0EDEDC7BD98}.Debug|Any CPU.Build.0 = Debug|Any CPU {B37B3E98-4B04-48B8-9017-F0EDEDC7BD98}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -767,30 +793,6 @@ Global {6B6622AE-2970-4AAD-B2E4-A7EE1E2C40EA}.Release|x64.Build.0 = Release|Any CPU {6B6622AE-2970-4AAD-B2E4-A7EE1E2C40EA}.Release|x86.ActiveCfg = Release|Any CPU {6B6622AE-2970-4AAD-B2E4-A7EE1E2C40EA}.Release|x86.Build.0 = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x64.ActiveCfg = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x64.Build.0 = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x86.ActiveCfg = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Debug|x86.Build.0 = Debug|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|Any CPU.Build.0 = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x64.ActiveCfg = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x64.Build.0 = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x86.ActiveCfg = Release|Any CPU - {D1D6B7AD-0E12-4D8C-83BE-9CE3EB916DD7}.Release|x86.Build.0 = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x64.ActiveCfg = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x64.Build.0 = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x86.ActiveCfg = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Debug|x86.Build.0 = Debug|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|Any CPU.Build.0 = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x64.ActiveCfg = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x64.Build.0 = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x86.ActiveCfg = Release|Any CPU - {8A7E9E3C-3F5A-4C0B-9F4D-6C4C673EB25D}.Release|x86.Build.0 = Release|Any CPU {97376F57-BA10-464B-AFBD-583E187DE947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97376F57-BA10-464B-AFBD-583E187DE947}.Debug|Any CPU.Build.0 = Debug|Any CPU {97376F57-BA10-464B-AFBD-583E187DE947}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -815,6 +817,18 @@ Global {4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x64.Build.0 = Release|Any CPU {4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x86.ActiveCfg = Release|Any CPU {4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x86.Build.0 = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|x64.ActiveCfg = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|x64.Build.0 = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|x86.ActiveCfg = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Debug|x86.Build.0 = Debug|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|Any CPU.Build.0 = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|x64.ActiveCfg = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|x64.Build.0 = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|x86.ActiveCfg = Release|Any CPU + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -858,10 +872,10 @@ Global {60EDA2A6-04ED-460F-9EB1-EC397B095E41} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {13D329DC-C790-7DD5-6658-9BDA7FD42702} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {23473564-5056-4697-A652-5FB389EFC544} = {13D329DC-C790-7DD5-6658-9BDA7FD42702} - {8319A8E0-AEBA-F939-C4F0-9FEA1E4D71AB} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} - {C26AEFE0-ECF5-40A1-9921-137D707AE6E0} = {8319A8E0-AEBA-F939-C4F0-9FEA1E4D71AB} {5DDC5947-6E81-4634-AC27-6E89D321317B} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {72B8CC84-138D-42AD-B6DE-38C85B055794} = {5DDC5947-6E81-4634-AC27-6E89D321317B} + {8319A8E0-AEBA-F939-C4F0-9FEA1E4D71AB} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} + {C26AEFE0-ECF5-40A1-9921-137D707AE6E0} = {8319A8E0-AEBA-F939-C4F0-9FEA1E4D71AB} {18E26AEE-5DA3-7BF8-A1AD-13A28A6C7BA3} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {8BE23F78-7178-4924-AB45-4AF74454CC97} = {18E26AEE-5DA3-7BF8-A1AD-13A28A6C7BA3} {52CE80AF-09C3-4209-8A21-6CFFAA3B2B01} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} @@ -873,8 +887,8 @@ Global {AF077751-E4B9-4696-93CB-74653F0BB6C4} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {6D0BE4C4-9C4F-4A3D-78F1-B0B761568559} = {7123C63D-3158-4C9A-8EAD-6D4F1295BC04} {7123C63D-3158-4C9A-8EAD-6D4F1295BC04} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} - {8781BE47-D710-408E-B143-4D5E20C356E2} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {6BA6C609-6D33-425B-883F-88ECE2E3DDB9} = {8781BE47-D710-408E-B143-4D5E20C356E2} + {8781BE47-D710-408E-B143-4D5E20C356E2} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {FF13AD0E-4F24-4044-B8AD-5A57EF3AE398} = {8781BE47-D710-408E-B143-4D5E20C356E2} {035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8} = {8781BE47-D710-408E-B143-4D5E20C356E2} {CA136E75-FC34-44E1-B8B2-6E33D8AF520E} = {8781BE47-D710-408E-B143-4D5E20C356E2} @@ -887,5 +901,6 @@ Global {97376F57-BA10-464B-AFBD-583E187DE947} = {34ADAC00-5559-6993-F779-D71CE5A0F93F} {5D5AFBCD-B4B7-4A4E-91C8-FD80E4C1E27B} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {4D8C9F9B-F8E3-4160-9286-32C3966A0125} = {5D5AFBCD-B4B7-4A4E-91C8-FD80E4C1E27B} + {02BFCAD9-F6E2-42FA-AA7F-264515D6D158} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} EndGlobalSection EndGlobal diff --git a/tests/TemporalioSamples.Tests.csproj b/tests/TemporalioSamples.Tests.csproj index 66b6b0a..3695ce2 100644 --- a/tests/TemporalioSamples.Tests.csproj +++ b/tests/TemporalioSamples.Tests.csproj @@ -47,6 +47,7 @@ + From 28bdd484b44dab9c36f5dfd5e1b3ba5f83844eb2 Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Tue, 29 Sep 2026 16:12:18 -0700 Subject: [PATCH 4/5] Update READMEs --- README.md | 4 +++- src/NexusMessagingV2/README.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7002fc7..bdce652 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ Prerequisites: * [NexusCancellation](src/NexusCancellation) - Demonstrates how to cancel a running Nexus operation from a caller workflow. * [NexusContextPropagation](src/NexusContextPropagation) - Context propagation through Nexus services. * [NexusDependencyInjection](src/NexusDependencyInjection) - How to configure a Nexus service handler for dependency injection using a generic host worker. -* [Nexus Messaging](src/NexusMessaging): Demonstrates how send signal, update and query messages through Nexus. +* [Nexus Messaging](src/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. +* [Nexus Messaging V2](src/NexusMessagingV2): Demonstrates how to send signal, update and query messages through Nexus. + This version contains additional pre-release features. * [NexusMultiArg](src/NexusMultiArg) - Nexus service implementation calling a workflow with multiple arguments. * [NexusSimple](src/NexusSimple) - Simple Nexus service implementation. * [NexusStandaloneActivity](src/NexusStandaloneActivity) - Nexus Operation backed by a Standalone Activity. diff --git a/src/NexusMessagingV2/README.md b/src/NexusMessagingV2/README.md index 223d528..fe7e800 100644 --- a/src/NexusMessagingV2/README.md +++ b/src/NexusMessagingV2/README.md @@ -1,5 +1,6 @@ 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: +operations. The V2 sample contains pre-release features. There are two self-contained examples, +each in its own directory: | | `CallerPattern/` | `OnDemandPattern/` | |--------------------------------|------------------------------------------------|-----------------------------------------------------------------| From 798a09b8f7d67ab807cae1fcdd952a87a496564e Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Fri, 2 Oct 2026 09:24:18 -0700 Subject: [PATCH 5/5] Better naming --- README.md | 4 ++-- TemporalioSamples.sln | 2 +- .../Caller/CallerWorkflow.workflow.cs | 6 +++--- .../CallerPattern/Caller/NexusEndpoints.cs | 2 +- .../Handler/GreetingWorkflow.workflow.cs | 6 +++--- .../CallerPattern/Handler/NexusGreetingService.cs | 6 +++--- .../CallerPattern/INexusGreetingService.cs | 4 ++-- .../CallerPattern/README.md | 6 +++--- .../Common/GreetingActivities.cs | 2 +- .../Common/Language.cs | 2 +- .../Caller/CallerRemoteWorkflow.workflow.cs | 6 +++--- .../OnDemandPattern/Caller/NexusEndpoints.cs | 2 +- .../Handler/GreetingWorkflow.workflow.cs | 6 +++--- .../Handler/NexusRemoteGreetingService.cs | 6 +++--- .../OnDemandPattern/INexusRemoteGreetingService.cs | 4 ++-- .../OnDemandPattern/README.md | 6 +++--- .../Program.cs | 14 +++++++------- .../README.md | 9 ++++++--- ...Samples.NexusMessagingTemporalOperation.csproj} | 0 .../CallerPatternTests.cs | 8 ++++---- .../OnDemandPatternTests.cs | 8 ++++---- tests/TemporalioSamples.Tests.csproj | 2 +- 22 files changed, 57 insertions(+), 54 deletions(-) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/Caller/CallerWorkflow.workflow.cs (87%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/Caller/NexusEndpoints.cs (60%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/Handler/GreetingWorkflow.workflow.cs (91%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/Handler/NexusGreetingService.cs (93%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/INexusGreetingService.cs (83%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPattern/README.md (89%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/Common/GreetingActivities.cs (90%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/Common/Language.cs (63%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs (94%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/Caller/NexusEndpoints.cs (61%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs (91%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/Handler/NexusRemoteGreetingService.cs (95%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/INexusRemoteGreetingService.cs (87%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPattern/README.md (91%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/Program.cs (90%) rename src/{NexusMessagingV2 => NexusMessagingTemporalOperation}/README.md (79%) rename src/{NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj => NexusMessagingTemporalOperation/TemporalioSamples.NexusMessagingTemporalOperation.csproj} (100%) rename tests/{NexusMessagingV2 => NexusMessagingTemporalOperation}/CallerPatternTests.cs (90%) rename tests/{NexusMessagingV2 => NexusMessagingTemporalOperation}/OnDemandPatternTests.cs (91%) diff --git a/README.md b/README.md index bdce652..d59eb1c 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ Prerequisites: * [Nexus Messaging](src/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. -* [Nexus Messaging V2](src/NexusMessagingV2): Demonstrates how to send signal, update and query messages through Nexus. - This version contains additional pre-release features. +* [Nexus Messaging Temporal Operation](src/NexusMessagingTemporalOperation): Demonstrates how to send signal, update and query messages through Nexus. + This version uses the `[TemporalOperation]` attribute to declare a Temporal-backed Nexus operation start handler directly on a method within a `[NexusServiceHandler]` class. * [NexusMultiArg](src/NexusMultiArg) - Nexus service implementation calling a workflow with multiple arguments. * [NexusSimple](src/NexusSimple) - Simple Nexus service implementation. * [NexusStandaloneActivity](src/NexusStandaloneActivity) - Nexus Operation backed by a Standalone Activity. diff --git a/TemporalioSamples.sln b/TemporalioSamples.sln index 5c20698..2e68196 100644 --- a/TemporalioSamples.sln +++ b/TemporalioSamples.sln @@ -145,7 +145,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusStandaloneActivity", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusStandaloneActivity", "src\NexusStandaloneActivity\TemporalioSamples.NexusStandaloneActivity.csproj", "{4D8C9F9B-F8E3-4160-9286-32C3966A0125}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusMessagingV2", "src\NexusMessagingV2\TemporalioSamples.NexusMessagingV2.csproj", "{02BFCAD9-F6E2-42FA-AA7F-264515D6D158}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusMessagingTemporalOperation", "src\NexusMessagingTemporalOperation\TemporalioSamples.NexusMessagingTemporalOperation.csproj", "{02BFCAD9-F6E2-42FA-AA7F-264515D6D158}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs b/src/NexusMessagingTemporalOperation/CallerPattern/Caller/CallerWorkflow.workflow.cs similarity index 87% rename from src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs rename to src/NexusMessagingTemporalOperation/CallerPattern/Caller/CallerWorkflow.workflow.cs index 1897011..e37c179 100644 --- a/src/NexusMessagingV2/CallerPattern/Caller/CallerWorkflow.workflow.cs +++ b/src/NexusMessagingTemporalOperation/CallerPattern/Caller/CallerWorkflow.workflow.cs @@ -1,8 +1,8 @@ -namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; +namespace TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Caller; using Temporalio.Workflows; -using TemporalioSamples.NexusMessagingV2.CallerPattern; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; [Workflow] public class CallerWorkflow diff --git a/src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs b/src/NexusMessagingTemporalOperation/CallerPattern/Caller/NexusEndpoints.cs similarity index 60% rename from src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs rename to src/NexusMessagingTemporalOperation/CallerPattern/Caller/NexusEndpoints.cs index af5b7d9..c8a3b13 100644 --- a/src/NexusMessagingV2/CallerPattern/Caller/NexusEndpoints.cs +++ b/src/NexusMessagingTemporalOperation/CallerPattern/Caller/NexusEndpoints.cs @@ -1,4 +1,4 @@ -namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; +namespace TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Caller; public static class NexusEndpoints { diff --git a/src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs b/src/NexusMessagingTemporalOperation/CallerPattern/Handler/GreetingWorkflow.workflow.cs similarity index 91% rename from src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs rename to src/NexusMessagingTemporalOperation/CallerPattern/Handler/GreetingWorkflow.workflow.cs index 44a148b..01db39c 100644 --- a/src/NexusMessagingV2/CallerPattern/Handler/GreetingWorkflow.workflow.cs +++ b/src/NexusMessagingTemporalOperation/CallerPattern/Handler/GreetingWorkflow.workflow.cs @@ -1,9 +1,9 @@ -namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; +namespace TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Handler; using Temporalio.Exceptions; using Temporalio.Workflows; -using TemporalioSamples.NexusMessagingV2.CallerPattern; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; [Workflow] public class GreetingWorkflow diff --git a/src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs b/src/NexusMessagingTemporalOperation/CallerPattern/Handler/NexusGreetingService.cs similarity index 93% rename from src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs rename to src/NexusMessagingTemporalOperation/CallerPattern/Handler/NexusGreetingService.cs index a199a65..504d41c 100644 --- a/src/NexusMessagingV2/CallerPattern/Handler/NexusGreetingService.cs +++ b/src/NexusMessagingTemporalOperation/CallerPattern/Handler/NexusGreetingService.cs @@ -1,10 +1,10 @@ -namespace TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; +namespace TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Handler; using NexusRpc.Handlers; using Temporalio.Client; using Temporalio.Nexus; -using TemporalioSamples.NexusMessagingV2.CallerPattern; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; // Entity pattern: the handler worker pre-starts a GreetingWorkflow per user at boot time. // This service routes each Nexus operation to that existing workflow by deriving the diff --git a/src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs b/src/NexusMessagingTemporalOperation/CallerPattern/INexusGreetingService.cs similarity index 83% rename from src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs rename to src/NexusMessagingTemporalOperation/CallerPattern/INexusGreetingService.cs index 94b5991..53db39b 100644 --- a/src/NexusMessagingV2/CallerPattern/INexusGreetingService.cs +++ b/src/NexusMessagingTemporalOperation/CallerPattern/INexusGreetingService.cs @@ -1,7 +1,7 @@ -namespace TemporalioSamples.NexusMessagingV2.CallerPattern; +namespace TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern; using NexusRpc; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; [NexusService] public interface INexusGreetingService diff --git a/src/NexusMessagingV2/CallerPattern/README.md b/src/NexusMessagingTemporalOperation/CallerPattern/README.md similarity index 89% rename from src/NexusMessagingV2/CallerPattern/README.md rename to src/NexusMessagingTemporalOperation/CallerPattern/README.md index 4f21f3e..7549f18 100644 --- a/src/NexusMessagingV2/CallerPattern/README.md +++ b/src/NexusMessagingTemporalOperation/CallerPattern/README.md @@ -40,19 +40,19 @@ Create the Nexus endpoint: In one terminal, start the handler worker: ```bash -dotnet run --project src/NexusMessagingV2 -- handler-worker +dotnet run --project src/NexusMessagingTemporalOperation -- handler-worker ``` In a second terminal, start the caller worker: ```bash -dotnet run --project src/NexusMessagingV2 -- caller-worker +dotnet run --project src/NexusMessagingTemporalOperation -- caller-worker ``` In a third terminal, run the following command to start the example: ```bash -dotnet run --project src/NexusMessagingV2 -- caller-workflow +dotnet run --project src/NexusMessagingTemporalOperation -- caller-workflow ``` Expected output: diff --git a/src/NexusMessagingV2/Common/GreetingActivities.cs b/src/NexusMessagingTemporalOperation/Common/GreetingActivities.cs similarity index 90% rename from src/NexusMessagingV2/Common/GreetingActivities.cs rename to src/NexusMessagingTemporalOperation/Common/GreetingActivities.cs index c826073..d205ef1 100644 --- a/src/NexusMessagingV2/Common/GreetingActivities.cs +++ b/src/NexusMessagingTemporalOperation/Common/GreetingActivities.cs @@ -1,4 +1,4 @@ -namespace TemporalioSamples.NexusMessagingV2.Common; +namespace TemporalioSamples.NexusMessagingTemporalOperation.Common; using Temporalio.Activities; diff --git a/src/NexusMessagingV2/Common/Language.cs b/src/NexusMessagingTemporalOperation/Common/Language.cs similarity index 63% rename from src/NexusMessagingV2/Common/Language.cs rename to src/NexusMessagingTemporalOperation/Common/Language.cs index 746560c..6c8d1c4 100644 --- a/src/NexusMessagingV2/Common/Language.cs +++ b/src/NexusMessagingTemporalOperation/Common/Language.cs @@ -1,4 +1,4 @@ -namespace TemporalioSamples.NexusMessagingV2.Common; +namespace TemporalioSamples.NexusMessagingTemporalOperation.Common; public enum Language { diff --git a/src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs b/src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs similarity index 94% rename from src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs rename to src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs index ec3e4a5..9786758 100644 --- a/src/NexusMessagingV2/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/CallerRemoteWorkflow.workflow.cs @@ -1,8 +1,8 @@ -namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; +namespace TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Caller; using Temporalio.Workflows; -using TemporalioSamples.NexusMessagingV2.Common; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern; [Workflow] public class CallerRemoteWorkflow diff --git a/src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs b/src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/NexusEndpoints.cs similarity index 61% rename from src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs rename to src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/NexusEndpoints.cs index cf307fd..d98bfa5 100644 --- a/src/NexusMessagingV2/OnDemandPattern/Caller/NexusEndpoints.cs +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/Caller/NexusEndpoints.cs @@ -1,4 +1,4 @@ -namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; +namespace TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Caller; public static class NexusEndpoints { diff --git a/src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs b/src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs similarity index 91% rename from src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs rename to src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs index f789e08..45a51bd 100644 --- a/src/NexusMessagingV2/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/GreetingWorkflow.workflow.cs @@ -1,9 +1,9 @@ -namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; +namespace TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Handler; using Temporalio.Exceptions; using Temporalio.Workflows; -using TemporalioSamples.NexusMessagingV2.Common; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern; [Workflow] public class GreetingWorkflow diff --git a/src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs b/src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/NexusRemoteGreetingService.cs similarity index 95% rename from src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs rename to src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/NexusRemoteGreetingService.cs index 12a05fd..097eed9 100644 --- a/src/NexusMessagingV2/OnDemandPattern/Handler/NexusRemoteGreetingService.cs +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/Handler/NexusRemoteGreetingService.cs @@ -1,11 +1,11 @@ -namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; +namespace TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Handler; using NexusRpc.Handlers; using Temporalio.Api.Enums.V1; using Temporalio.Client; using Temporalio.Nexus; -using TemporalioSamples.NexusMessagingV2.Common; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern; // On-demand pattern: no workflow is pre-started. The caller creates workflow instances // through Nexus operations. Each operation includes a UserId so the handler can derive diff --git a/src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs b/src/NexusMessagingTemporalOperation/OnDemandPattern/INexusRemoteGreetingService.cs similarity index 87% rename from src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs rename to src/NexusMessagingTemporalOperation/OnDemandPattern/INexusRemoteGreetingService.cs index e0996e7..02cf3c6 100644 --- a/src/NexusMessagingV2/OnDemandPattern/INexusRemoteGreetingService.cs +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/INexusRemoteGreetingService.cs @@ -1,7 +1,7 @@ -namespace TemporalioSamples.NexusMessagingV2.OnDemandPattern; +namespace TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern; using NexusRpc; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; [NexusService] public interface INexusRemoteGreetingService diff --git a/src/NexusMessagingV2/OnDemandPattern/README.md b/src/NexusMessagingTemporalOperation/OnDemandPattern/README.md similarity index 91% rename from src/NexusMessagingV2/OnDemandPattern/README.md rename to src/NexusMessagingTemporalOperation/OnDemandPattern/README.md index fe4874e..444c477 100644 --- a/src/NexusMessagingV2/OnDemandPattern/README.md +++ b/src/NexusMessagingTemporalOperation/OnDemandPattern/README.md @@ -44,19 +44,19 @@ Create the Nexus endpoint: In one terminal, start the handler worker: ```bash -dotnet run --project src/NexusMessagingV2 -- remote-handler-worker +dotnet run --project src/NexusMessagingTemporalOperation -- remote-handler-worker ``` In a second terminal, start the caller worker: ```bash -dotnet run --project src/NexusMessagingV2 -- remote-caller-worker +dotnet run --project src/NexusMessagingTemporalOperation -- remote-caller-worker ``` In a third terminal, run the following command to start the example: ```bash -dotnet run --project src/NexusMessagingV2 -- remote-caller-workflow +dotnet run --project src/NexusMessagingTemporalOperation -- remote-caller-workflow ``` Expected output: diff --git a/src/NexusMessagingV2/Program.cs b/src/NexusMessagingTemporalOperation/Program.cs similarity index 90% rename from src/NexusMessagingV2/Program.cs rename to src/NexusMessagingTemporalOperation/Program.cs index a17590b..3932e3d 100644 --- a/src/NexusMessagingV2/Program.cs +++ b/src/NexusMessagingTemporalOperation/Program.cs @@ -2,13 +2,13 @@ using Temporalio.Client; using Temporalio.Common.EnvConfig; using Temporalio.Worker; -using TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; -using TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; -using TemporalioSamples.NexusMessagingV2.Common; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; -using CallerGreetingWorkflow = TemporalioSamples.NexusMessagingV2.CallerPattern.Handler.GreetingWorkflow; -using OnDemandGreetingWorkflow = TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler.GreetingWorkflow; -using OnDemandNexusService = TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler.NexusRemoteGreetingService; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Caller; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Handler; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Caller; +using CallerGreetingWorkflow = TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Handler.GreetingWorkflow; +using OnDemandGreetingWorkflow = TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Handler.GreetingWorkflow; +using OnDemandNexusService = TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Handler.NexusRemoteGreetingService; using var loggerFactory = LoggerFactory.Create(builder => builder. diff --git a/src/NexusMessagingV2/README.md b/src/NexusMessagingTemporalOperation/README.md similarity index 79% rename from src/NexusMessagingV2/README.md rename to src/NexusMessagingTemporalOperation/README.md index fe7e800..aa2b3ea 100644 --- a/src/NexusMessagingV2/README.md +++ b/src/NexusMessagingTemporalOperation/README.md @@ -1,6 +1,9 @@ -This sample shows how to expose a long-running Workflow's queries, updates, and signals as Nexus -operations. The V2 sample contains pre-release features. There are two self-contained examples, -each in its own directory: +This sample shows how to expose a long-running Workflow's queries, updates, and signals as Nexus operations. This +version uses the experimental `[TemporalOperation]` attribute to declare a Temporal-backed Nexus operation start handler +directly on a method within a `[NexusServiceHandler]` class, instead of returning an `IOperationHandler` from +`[NexusOperationHandler]`. + +There are two self-contained examples, each in its own directory: | | `CallerPattern/` | `OnDemandPattern/` | |--------------------------------|------------------------------------------------|-----------------------------------------------------------------| diff --git a/src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj b/src/NexusMessagingTemporalOperation/TemporalioSamples.NexusMessagingTemporalOperation.csproj similarity index 100% rename from src/NexusMessagingV2/TemporalioSamples.NexusMessagingV2.csproj rename to src/NexusMessagingTemporalOperation/TemporalioSamples.NexusMessagingTemporalOperation.csproj diff --git a/tests/NexusMessagingV2/CallerPatternTests.cs b/tests/NexusMessagingTemporalOperation/CallerPatternTests.cs similarity index 90% rename from tests/NexusMessagingV2/CallerPatternTests.cs rename to tests/NexusMessagingTemporalOperation/CallerPatternTests.cs index 1290cdc..98a29b3 100644 --- a/tests/NexusMessagingV2/CallerPatternTests.cs +++ b/tests/NexusMessagingTemporalOperation/CallerPatternTests.cs @@ -1,11 +1,11 @@ -namespace TemporalioSamples.Tests.NexusMessagingV2; +namespace TemporalioSamples.Tests.NexusMessagingTemporalOperation; using Temporalio.Client; using Temporalio.Testing; using Temporalio.Worker; -using TemporalioSamples.NexusMessagingV2.CallerPattern.Caller; -using TemporalioSamples.NexusMessagingV2.CallerPattern.Handler; -using TemporalioSamples.NexusMessagingV2.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Caller; +using TemporalioSamples.NexusMessagingTemporalOperation.CallerPattern.Handler; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; using Xunit; using Xunit.Abstractions; diff --git a/tests/NexusMessagingV2/OnDemandPatternTests.cs b/tests/NexusMessagingTemporalOperation/OnDemandPatternTests.cs similarity index 91% rename from tests/NexusMessagingV2/OnDemandPatternTests.cs rename to tests/NexusMessagingTemporalOperation/OnDemandPatternTests.cs index c12f9ea..e467bc6 100644 --- a/tests/NexusMessagingV2/OnDemandPatternTests.cs +++ b/tests/NexusMessagingTemporalOperation/OnDemandPatternTests.cs @@ -1,11 +1,11 @@ -namespace TemporalioSamples.Tests.NexusMessagingV2; +namespace TemporalioSamples.Tests.NexusMessagingTemporalOperation; using Temporalio.Client; using Temporalio.Testing; using Temporalio.Worker; -using TemporalioSamples.NexusMessagingV2.Common; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Caller; -using TemporalioSamples.NexusMessagingV2.OnDemandPattern.Handler; +using TemporalioSamples.NexusMessagingTemporalOperation.Common; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Caller; +using TemporalioSamples.NexusMessagingTemporalOperation.OnDemandPattern.Handler; using Xunit; using Xunit.Abstractions; diff --git a/tests/TemporalioSamples.Tests.csproj b/tests/TemporalioSamples.Tests.csproj index 3695ce2..f5bbb15 100644 --- a/tests/TemporalioSamples.Tests.csproj +++ b/tests/TemporalioSamples.Tests.csproj @@ -47,7 +47,7 @@ - +