Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.
Expand Down
7 changes: 6 additions & 1 deletion TemporalioSamples.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<Project Path="src/EagerWorkflowStart/TemporalioSamples.EagerWorkflowStart.csproj" />
<Project Path="src/EnvConfig/TemporalioSamples.EnvConfig.csproj" />
<Project Path="src/Mutex/TemporalioSamples.Mutex.csproj" />
<Project Path="src/NexusMessaging/TemporalioSamples.NexusMessaging.csproj" />
<Project Path="src/Patching/TemporalioSamples.Patching.csproj" />
<Project Path="src/SafeMessageHandlers/TemporalioSamples.SafeMessageHandlers.csproj" />
<Project Path="src/Saga/TemporalioSamples.Saga.csproj" />
Expand Down Expand Up @@ -60,6 +59,12 @@
<Folder Name="/src/NexusDependencyInjection/">
<Project Path="src/NexusDependencyInjection/TemporalioSamples.NexusDependencyInjection.csproj" />
</Folder>
<Folder Name="/src/NexusMessaging/">
<Project Path="src/NexusMessaging/TemporalioSamples.NexusMessaging.csproj" />
</Folder>
<Folder Name="/src/NexusMessagingTemporalOperation/">
<Project Path="src/NexusMessagingTemporalOperation/TemporalioSamples.NexusMessagingTemporalOperation.csproj" />
</Folder>
<Folder Name="/src/NexusMultiArg/">
<Project Path="src/NexusMultiArg/TemporalioSamples.NexusMultiArg.csproj" />
</Folder>
Expand Down
91 changes: 41 additions & 50 deletions src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<TemporalOperationResult<INexusGreetingService.GetLanguagesOutput>> 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<GreetingWorkflow>(
WorkflowIdForUser(input.UserId));
return TemporalOperationResult<INexusGreetingService.GetLanguagesOutput>.SyncResult(
await handle.QueryAsync(wf => wf.QueryLanguages(input.IncludeUnsupported)));
}
[NexusOperationHandler]
public IOperationHandler<INexusGreetingService.GetLanguagesInput, INexusGreetingService.GetLanguagesOutput> GetLanguages() =>
OperationHandler.Sync<INexusGreetingService.GetLanguagesInput, INexusGreetingService.GetLanguagesOutput>(
async (ctx, input) =>
{
// Access the Temporal client from the Nexus operation context
var client = NexusOperationExecutionContext.Current.TemporalClient;
var handle = client.GetWorkflowHandle<GreetingWorkflow>(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<TemporalOperationResult<Language>> GetLanguage(
TemporalOperationStartContext ctx,
ITemporalNexusClient client,
INexusGreetingService.GetLanguageInput input)
{
var handle = client.TemporalClient.GetWorkflowHandle<GreetingWorkflow>(
WorkflowIdForUser(input.UserId));
return TemporalOperationResult<Language>.SyncResult(
await handle.QueryAsync(wf => wf.QueryLanguage()));
}
[NexusOperationHandler]
public IOperationHandler<INexusGreetingService.GetLanguageInput, Language> GetLanguage() =>
OperationHandler.Sync<INexusGreetingService.GetLanguageInput, Language>(
async (ctx, input) =>
{
var client = NexusOperationExecutionContext.Current.TemporalClient;
var handle = client.GetWorkflowHandle<GreetingWorkflow>(WorkflowIdForUser(input.UserId));
return await handle.QueryAsync(wf => wf.QueryLanguage());
});

// Update: mutates state and returns the previous value — uses workflow update
[TemporalOperation]
public Task<TemporalOperationResult<Language>> SetLanguage(
TemporalOperationStartContext ctx,
ITemporalNexusClient client,
INexusGreetingService.SetLanguageInput input) =>
client.StartWorkflowUpdateAsync<GreetingWorkflow, Language>(
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<INexusGreetingService.SetLanguageInput, Language> SetLanguage() =>
OperationHandler.Sync<INexusGreetingService.SetLanguageInput, Language>(
async (ctx, input) =>
{
var client = NexusOperationExecutionContext.Current.TemporalClient;
var handle = client.GetWorkflowHandle<GreetingWorkflow>(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<TemporalOperationResult<NoValue>> Approve(
TemporalOperationStartContext ctx,
ITemporalNexusClient client,
INexusGreetingService.ApproveInput input)
{
var handle = client.TemporalClient.GetWorkflowHandle<GreetingWorkflow>(
WorkflowIdForUser(input.UserId));
await handle.SignalAsync(wf => wf.ApproveAsync(input.Name));
return TemporalOperationResult<NoValue>.SyncResult(default);
}

#pragma warning restore VSTHRD200
[NexusOperationHandler]
public IOperationHandler<INexusGreetingService.ApproveInput, NoValue> Approve() =>
OperationHandler.Sync<INexusGreetingService.ApproveInput, NoValue>(
async (ctx, input) =>
{
var client = NexusOperationExecutionContext.Current.TemporalClient;
var handle = client.GetWorkflowHandle<GreetingWorkflow>(WorkflowIdForUser(input.UserId));
await handle.SignalAsync(wf => wf.ApproveAsync(input.Name));
return default;
});

private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}";
}
28 changes: 7 additions & 21 deletions src/NexusMessaging/CallerPattern/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@ public async Task<string[]> 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}");
Expand All @@ -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)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> RunAsync(string userId)
Expand Down Expand Up @@ -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;
}
}
Loading
Loading