Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ The caller Workflow:

### Running

Start a Temporal server:
Start a Temporal server with Workflow Update callbacks enabled:

```bash
temporal server start-dev
temporal server start-dev \
--dynamic-config-value history.enableUpdateCallbacks=true \
--dynamic-config-value history.enableCHASMSignalBacklinks=true
```

`history.enableUpdateCallbacks` defaults to `false`. The `setLanguage` operation starts its Update
through the Nexus client, so its result is delivered over a Nexus completion callback; without this
flag the callback is never registered and the operation never completes.

Create the namespaces and Nexus endpoint:

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,24 @@
import io.nexusrpc.handler.OperationHandler;
import io.nexusrpc.handler.OperationImpl;
import io.nexusrpc.handler.ServiceImpl;
import io.temporal.nexus.Nexus;
import io.temporal.client.UpdateOptions;
import io.temporal.client.WorkflowUpdateStage;
import io.temporal.nexus.TemporalNexusClient;
import io.temporal.nexus.TemporalOperationHandler;
import io.temporal.nexus.TemporalOperationResult;
import io.temporal.samples.nexusmessaging.callerpattern.service.Language;
import io.temporal.samples.nexusmessaging.callerpattern.service.NexusGreetingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Nexus operation handler implementation. Each operation receives a userId, which is mapped to a
* workflow ID using {@link #WORKFLOW_ID_PREFIX}. The operations are synchronous because queries and
* updates against a running workflow complete quickly.
* workflow ID using {@link #WORKFLOW_ID_PREFIX}.
*
* <p>Every operation is a {@link TemporalOperationHandler}: the start handler receives a {@link
* TemporalNexusClient} scoped to the invocation and returns a {@link TemporalOperationResult}. The
* queries and the signal complete inline and return a sync result; {@code setLanguage} starts the
* update through the Nexus client, which makes it an asynchronous operation.
*/
@ServiceImpl(service = NexusGreetingService.class)
public class NexusGreetingServiceImpl {
Expand All @@ -29,8 +37,8 @@ public static String getWorkflowId(String userId) {
return WORKFLOW_ID_PREFIX + userId;
}

private GreetingWorkflow getWorkflowStub(String userId) {
return Nexus.getOperationContext()
private GreetingWorkflow getWorkflowStub(TemporalNexusClient client, String userId) {
return client
.getWorkflowClient()
.newWorkflowStub(GreetingWorkflow.class, getWorkflowId(userId));
}
Expand All @@ -39,41 +47,56 @@ private GreetingWorkflow getWorkflowStub(String userId) {
public OperationHandler<
NexusGreetingService.GetLanguagesInput, NexusGreetingService.GetLanguagesOutput>
getLanguages() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Query for GetLanguages was received for user {}", input.getUserId());
return getWorkflowStub(input.getUserId()).getLanguages(input);
return TemporalOperationResult.sync(
getWorkflowStub(client, input.getUserId()).getLanguages(input));
});
}

@OperationImpl
public OperationHandler<NexusGreetingService.GetLanguageInput, Language> getLanguage() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Query for GetLanguage was received for user {}", input.getUserId());
return getWorkflowStub(input.getUserId()).getLanguage();
return TemporalOperationResult.sync(
getWorkflowStub(client, input.getUserId()).getLanguage());
});
}

// Routes to setLanguageUsingActivity (not setLanguage) so that new languages not already in the
// greetings map can be fetched via an activity.
//
// Starting the update through the Nexus client makes this an asynchronous Nexus operation: the
// caller receives an operation token and the update result is delivered later over the Nexus
// completion callback. If the update is already complete when the start call returns, the result
// comes back synchronously instead.
@OperationImpl
public OperationHandler<NexusGreetingService.SetLanguageInput, Language> setLanguage() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Update for SetLanguage was received for user {}", input.getUserId());
return getWorkflowStub(input.getUserId()).setLanguageUsingActivity(input);
return client.startWorkflowUpdate(
GreetingWorkflow.class,
getWorkflowId(input.getUserId()),
GreetingWorkflow::setLanguageUsingActivity,
input,
UpdateOptions.newBuilder(Language.class)
.setUpdateName("setLanguageUsingActivity")
.setWaitForStage(WorkflowUpdateStage.ACCEPTED)
.build());
});
}

@OperationImpl
public OperationHandler<NexusGreetingService.ApproveInput, NexusGreetingService.ApproveOutput>
approve() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Signal for Approve was received for user {}", input.getUserId());
getWorkflowStub(input.getUserId()).approve(input);
return new NexusGreetingService.ApproveOutput();
getWorkflowStub(client, input.getUserId()).approve(input);
return TemporalOperationResult.sync(new NexusGreetingService.ApproveOutput());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ The caller Workflow:

### Running

Start a Temporal server:
Start a Temporal server with Workflow Update callbacks enabled:

```bash
temporal server start-dev
temporal server start-dev \
--dynamic-config-value history.enableUpdateCallbacks=true \
--dynamic-config-value history.enableCHASMSignalBacklinks=true
```

`history.enableUpdateCallbacks` defaults to `false`. The `setLanguage` operation starts its Update
through the Nexus client, so its result is delivered over a Nexus completion callback; without this
flag the callback is never registered and the operation never completes.

Create the namespaces and Nexus endpoint:

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
import io.nexusrpc.handler.OperationHandler;
import io.nexusrpc.handler.OperationImpl;
import io.nexusrpc.handler.ServiceImpl;
import io.temporal.client.UpdateOptions;
import io.temporal.client.WorkflowOptions;
import io.temporal.client.WorkflowUpdateStage;
import io.temporal.nexus.Nexus;
import io.temporal.nexus.TemporalNexusClient;
import io.temporal.nexus.TemporalOperationHandler;
import io.temporal.nexus.TemporalOperationResult;
import io.temporal.nexus.WorkflowHandle;
import io.temporal.nexus.WorkflowRunOperation;
import io.temporal.samples.nexusmessaging.ondemandpattern.service.Language;
Expand All @@ -15,6 +20,12 @@
/**
* Nexus operation handler for the on-demand pattern. Each operation receives the target workflow ID
* in its input, and {@code runFromRemote} starts a brand-new GreetingWorkflow.
*
* <p>The messaging operations are {@link TemporalOperationHandler}s: the start handler receives a
* {@link TemporalNexusClient} scoped to the invocation and returns a {@link
* TemporalOperationResult}. The queries and the signal complete inline and return a sync result;
* {@code setLanguage} starts the update through the Nexus client, which makes it an asynchronous
* operation.
*/
@ServiceImpl(service = NexusRemoteGreetingService.class)
public class NexusRemoteGreetingServiceImpl {
Expand All @@ -32,8 +43,8 @@ public static String getWorkflowId(String userId) {
return WORKFLOW_ID_PREFIX + userId;
}

private GreetingWorkflow getWorkflowStub(String userId) {
return Nexus.getOperationContext()
private GreetingWorkflow getWorkflowStub(TemporalNexusClient client, String userId) {
return client
.getWorkflowClient()
.newWorkflowStub(GreetingWorkflow.class, getWorkflowId(userId));
}
Expand Down Expand Up @@ -63,44 +74,59 @@ public OperationHandler<NexusRemoteGreetingService.RunFromRemoteInput, String> r
NexusRemoteGreetingService.GetLanguagesInput,
NexusRemoteGreetingService.GetLanguagesOutput>
getLanguages() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Query for GetLanguages was received for userId {}", input.getUserId());
return getWorkflowStub(input.getUserId())
.getLanguages(new GreetingWorkflow.GetLanguagesInput(input.isIncludeUnsupported()));
return TemporalOperationResult.sync(
getWorkflowStub(client, input.getUserId())
.getLanguages(
new GreetingWorkflow.GetLanguagesInput(input.isIncludeUnsupported())));
});
}

@OperationImpl
public OperationHandler<NexusRemoteGreetingService.GetLanguageInput, Language> getLanguage() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Query for GetLanguage was received for userId {}", input.getUserId());
return getWorkflowStub(input.getUserId()).getLanguage();
return TemporalOperationResult.sync(
getWorkflowStub(client, input.getUserId()).getLanguage());
});
}

// Uses setLanguageUsingActivity so that new languages are fetched via an activity.
//
// Starting the update through the Nexus client makes this an asynchronous Nexus operation: the
// caller receives an operation token and the update result is delivered later over the Nexus
// completion callback. If the update is already complete when the start call returns, the result
// comes back synchronously instead.
@OperationImpl
public OperationHandler<NexusRemoteGreetingService.SetLanguageInput, Language> setLanguage() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Update for SetLanguage was received for userId {}", input.getUserId());
return getWorkflowStub(input.getUserId())
.setLanguageUsingActivity(new GreetingWorkflow.SetLanguageInput(input.getLanguage()));
return client.startWorkflowUpdate(
GreetingWorkflow.class,
getWorkflowId(input.getUserId()),
GreetingWorkflow::setLanguageUsingActivity,
new GreetingWorkflow.SetLanguageInput(input.getLanguage()),
UpdateOptions.newBuilder(Language.class)
.setUpdateName("setLanguageUsingActivity")
.setWaitForStage(WorkflowUpdateStage.ACCEPTED)
.build());
});
}

@OperationImpl
public OperationHandler<
NexusRemoteGreetingService.ApproveInput, NexusRemoteGreetingService.ApproveOutput>
approve() {
return OperationHandler.sync(
(ctx, details, input) -> {
return TemporalOperationHandler.create(
(ctx, client, input) -> {
logger.info("Signal for Approve was received for userId {}", input.getUserId());
getWorkflowStub(input.getUserId())
getWorkflowStub(client, input.getUserId())
.approve(new GreetingWorkflow.ApproveInput(input.getName()));
return new NexusRemoteGreetingService.ApproveOutput();
return TemporalOperationResult.sync(new NexusRemoteGreetingService.ApproveOutput());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ from the environment (and optionally a profile from `temporal.toml`).

```bash
./temporal server start-dev \
--dynamic-config-value nexusoperation.enableStandalone=true \
--namespace my-caller-namespace \
--namespace my-handler-namespace
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ from the environment (and optionally a profile from `temporal.toml`).

```bash
./temporal server start-dev \
--dynamic-config-value nexusoperation.enableStandalone=true \
--dynamic-config-value activity.enableCallbacks=true \
--namespace my-caller-namespace \
--namespace my-handler-namespace
Expand Down
Loading