From b1245fa41e874b70bc3f2447774de63dc39f5895 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Fri, 28 Aug 2026 20:48:00 -0700 Subject: [PATCH 01/10] Updated for Nexus SDK Ergonomics --- .../java/nexus/developer-experience.mdx | 696 ++++++++++++++++++ .../nexus/nexus-code-generator.mdx | 49 ++ .../nexus/nexus-standalone-activity.mdx | 54 ++ .../nexus/temporal-operation-handler.mdx | 36 + sidebars.js | 7 +- 5 files changed, 841 insertions(+), 1 deletion(-) create mode 100644 docs/develop/java/nexus/developer-experience.mdx create mode 100644 docs/encyclopedia/nexus/nexus-code-generator.mdx create mode 100644 docs/encyclopedia/nexus/nexus-standalone-activity.mdx create mode 100644 docs/encyclopedia/nexus/temporal-operation-handler.mdx diff --git a/docs/develop/java/nexus/developer-experience.mdx b/docs/develop/java/nexus/developer-experience.mdx new file mode 100644 index 0000000000..c34157ffc3 --- /dev/null +++ b/docs/develop/java/nexus/developer-experience.mdx @@ -0,0 +1,696 @@ +--- +id: developer-experience +slug: /develop/java/nexus/developer-experience +title: Nexus Developer Experience - Java SDK feature guide +sidebar_label: Nexus Developer Experience +description: Build a Nexus Service in Java with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. +toc_max_heading_level: 4 +tags: + - Nexus + - Java SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +:::tip + +New to Nexus? Start with the [Nexus Java Quickstart](/develop/java/nexus/quickstart). + +::: + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) +- [Create caller and handler Namespaces](#create-caller-handler-namespaces) +- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +:::note + +This documentation uses source code derived from the +[Java Nexus sample](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexus). + +::: + +## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} + +Prerequisites: + +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/java/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (v1.3.0 or higher recommended) +- [Install the latest Temporal Java SDK](https://learn.temporal.io/getting_started/java/dev_environment/#add-temporal-java-sdk-dependencies) + (v1.28.0 or higher recommended) + +The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. + +``` +temporal server start-dev +``` + +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. + +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. + +## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} + +Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. + +``` +temporal operator namespace create --namespace my-target-namespace +temporal operator namespace create --namespace my-caller-namespace +``` + +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. + +## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} + +After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. + +``` +temporal operator nexus endpoint create \ + --name my-nexus-endpoint-name \ + --target-namespace my-target-namespace \ + --target-task-queue my-handler-task-queue +``` + +You can also use the Web UI to create the Namespaces and Nexus endpoint. + +## Define the Nexus Service contract {/* #define-nexus-service-contract */} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. + +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. + +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} + +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). +Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `TemporalOperationHandler.create(...)` hands your start handler three things: a context, a Client, and the +Operation input. What you do with the Client decides what backs the Operation: + +- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `startWorkflow`, `startActivity`, or `startWorkflowUpdate` on the Client. The handler returns + as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be days + later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an Operation + outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. + +### Develop a Synchronous Nexus Operation handler + +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +```java +@ServiceImpl(service = SampleNexusService.class) +public class SampleNexusServiceImpl { + + @OperationImpl + public OperationHandler echo() { + return TemporalOperationHandler.create( + (ctx, client, input) -> + TemporalOperationResult.sync(new SampleNexusService.EchoOutput(input.getMessage()))); + } +} +``` + +### Use the Temporal Client for Signals, Queries, and Updates + +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +Updates are the exception. Do not wait for one inside the handler. Start it with `startWorkflowUpdate` and it backs the +Operation. The handler returns straight away, and the Operation completes when the Update does, however long it takes. + +The [nexus_messaging](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. + +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through it rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: + +```java +@OperationImpl +public OperationHandler approve() { + return TemporalOperationHandler.create( + (ctx, client, input) -> { + client + .getWorkflowClient() + .newWorkflowStub(GreetingWorkflow.class, "GreetingWorkflow_for_" + input.getUserId()) + .approve(input); + return TemporalOperationResult.sync(new ApproveOutput()); + }); +} +``` + +There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/). +The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return +value is delivered to the caller as the Operation's result. + +```java +@OperationImpl +public OperationHandler hello() { + return TemporalOperationHandler.create( + (ctx, client, input) -> + client.startWorkflow( + HelloHandlerWorkflow.class, + HelloHandlerWorkflow::hello, + input, + WorkflowOptions.newBuilder() + .setWorkflowId( + String.format( + "hello-%s-%s", + input.getName(), input.getLanguage().name().toLowerCase(Locale.ROOT))) + .build())); +} +``` + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass the arguments +directly to `startWorkflow` between the method reference and the Workflow options: + +```java +@OperationImpl +public OperationHandler hello() { + return TemporalOperationHandler.create( + (ctx, client, input) -> + client.startWorkflow( + HelloHandlerWorkflow.class, + HelloHandlerWorkflow::hello, + input.getName(), + input.getLanguage(), + WorkflowOptions.newBuilder() + .setWorkflowId("hello-" + input.getName()) + .build())); +} +``` + +### Register a Nexus Service in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus +Service in a Worker. + + + +[core/src/main/java/io/temporal/samples/nexus/handler/HandlerWorker.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/handler/HandlerWorker.java) + +```java +package io.temporal.samples.nexus.handler; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.nexus.options.ClientOptions; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; + +public class HandlerWorker { + public static final String DEFAULT_TASK_QUEUE_NAME = "my-handler-task-queue"; + + public static void main(String[] args) { + WorkflowClient client = ClientOptions.getWorkflowClient(args); + + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); + worker.registerWorkflowImplementationTypes(HelloHandlerWorkflowImpl.class); + worker.registerNexusServiceImplementation(new SampleNexusServiceImpl()); + + factory.start(); + } +} +``` + + + +## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} + +Import the Service API package that has the necessary service and operation names and input/output types to execute a +Nexus Operation from the caller Workflow: + + + +[core/src/main/java/io/temporal/samples/nexus/caller/EchoCallerWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/EchoCallerWorkflowImpl.java) + +```java +package io.temporal.samples.nexus.caller; + +import io.temporal.samples.nexus.service.SampleNexusService; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; + +public class EchoCallerWorkflowImpl implements EchoCallerWorkflow { + SampleNexusService sampleNexusService = + Workflow.newNexusServiceStub( + SampleNexusService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public String echo(String message) { + return sampleNexusService.echo(new SampleNexusService.EchoInput(message)).getMessage(); + } +} +``` + + + + + +[core/src/main/java/io/temporal/samples/nexus/caller/HelloCallerWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/HelloCallerWorkflowImpl.java) + +```java +package io.temporal.samples.nexus.caller; + +import io.temporal.samples.nexus.service.SampleNexusService; +import io.temporal.workflow.NexusOperationHandle; +import io.temporal.workflow.NexusOperationOptions; +import io.temporal.workflow.NexusServiceOptions; +import io.temporal.workflow.Workflow; +import java.time.Duration; + +public class HelloCallerWorkflowImpl implements HelloCallerWorkflow { + SampleNexusService sampleNexusService = + Workflow.newNexusServiceStub( + SampleNexusService.class, + NexusServiceOptions.newBuilder() + .setOperationOptions( + NexusOperationOptions.newBuilder() + .setScheduleToCloseTimeout(Duration.ofSeconds(10)) + .build()) + .build()); + + @Override + public String hello(String message, SampleNexusService.Language language) { + NexusOperationHandle handle = + Workflow.startNexusOperation( + sampleNexusService::hello, new SampleNexusService.HelloInput(message, language)); + // Optionally wait for the operation to be started. NexusOperationExecution will contain the + // operation token in case this operation is asynchronous. + handle.getExecution().get(); + return handle.getResult().get().getMessage(); + } +} +``` + + + +### Register the caller Workflow in a Worker + +After developing the caller Workflow, the next step is to register it with a Worker. + + + +[core/src/main/java/io/temporal/samples/nexus/caller/CallerWorker.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/CallerWorker.java) + +```java +package io.temporal.samples.nexus.caller; + +import io.temporal.client.WorkflowClient; +import io.temporal.samples.nexus.options.ClientOptions; +import io.temporal.worker.Worker; +import io.temporal.worker.WorkerFactory; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.NexusServiceOptions; +import java.util.Collections; + +public class CallerWorker { + public static final String DEFAULT_TASK_QUEUE_NAME = "my-caller-workflow-task-queue"; + + public static void main(String[] args) { + WorkflowClient client = ClientOptions.getWorkflowClient(args); + + WorkerFactory factory = WorkerFactory.newInstance(client); + + Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); + worker.registerWorkflowImplementationTypes( + WorkflowImplementationOptions.newBuilder() + .setNexusServiceOptions( + Collections.singletonMap( + "SampleNexusService", + NexusServiceOptions.newBuilder().setEndpoint("my-nexus-endpoint-name").build())) + .build(), + EchoCallerWorkflowImpl.class, + HelloCallerWorkflowImpl.class); + + factory.start(); + } +} +``` + + + +### Develop a starter to start the caller Workflow + +To initiate the caller Workflow, a starter program is used. + + + +[core/src/main/java/io/temporal/samples/nexus/caller/CallerStarter.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/CallerStarter.java) + +```java +package io.temporal.samples.nexus.caller; + +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.client.WorkflowClient; +import io.temporal.client.WorkflowOptions; +import io.temporal.samples.nexus.options.ClientOptions; +import io.temporal.samples.nexus.service.SampleNexusService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CallerStarter { + private static final Logger logger = LoggerFactory.getLogger(CallerStarter.class); + + public static void main(String[] args) { + WorkflowClient client = ClientOptions.getWorkflowClient(args); + + WorkflowOptions workflowOptions = + WorkflowOptions.newBuilder().setTaskQueue(CallerWorker.DEFAULT_TASK_QUEUE_NAME).build(); + EchoCallerWorkflow echoWorkflow = + client.newWorkflowStub(EchoCallerWorkflow.class, workflowOptions); + WorkflowExecution execution = WorkflowClient.start(echoWorkflow::echo, "Nexus Echo πŸ‘‹"); + logger.info( + "Started EchoCallerWorkflow workflowId: {} runId: {}", + execution.getWorkflowId(), + execution.getRunId()); + logger.info("Workflow result: {}", echoWorkflow.echo("Nexus Echo πŸ‘‹")); + HelloCallerWorkflow helloWorkflow = + client.newWorkflowStub(HelloCallerWorkflow.class, workflowOptions); + execution = WorkflowClient.start(helloWorkflow::hello, "Nexus", SampleNexusService.Language.EN); + logger.info( + "Started HelloCallerWorkflow workflowId: {} runId: {}", + execution.getWorkflowId(), + execution.getRunId()); + logger.info("Workflow result: {}", helloWorkflow.hello("Nexus", SampleNexusService.Language.ES)); + } +} +``` + + + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} + +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. + +### Run Workers connected to a local development server + +Run the Nexus handler Worker: + +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \ + --args="-target-host localhost:7233 -namespace my-target-namespace" +``` + +In another terminal window, run the Nexus caller Worker: + +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \ + --args="-target-host localhost:7233 -namespace my-caller-namespace" +``` + +### Start a caller Workflow + +With the Workers running, the final step in the local development process is to start a caller Workflow. + +Run the starter: + +```bash +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \ + --args="-target-host localhost:7233 -namespace my-caller-namespace" +``` + +This will result in: + +``` +[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba +[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo πŸ‘‹ +[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc +[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Β‘Hola! Nexus πŸ‘‹ +``` + +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} + +To cancel a Nexus Operation from within a Workflow, create a `CancellationScope` using the +`Workflow.newCancellationScope` API. `Workflow.newCancellationScope` takes a `Runnable`. Any SDK methods started in this +runnable, such as Nexus operations, will be associated with this scope. `Workflow.newCancellationScope` returns a new +scope that, when the `cancel()` method is called, cancels the context and any SDK method that was started in the scope. +The promise returned by `Workflow.startNexusOperation` is resolved when the operation finishes, whether it succeeds, +fails, times out, or is canceled. + +Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or +other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter +a terminal state. + +When a Nexus operation is started the caller can specify different cancellation types that will control how the caller +reacts to cancellation: + +- `ABANDON` - Do not request cancellation of the operation. +- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type + doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is + done. +- `WAIT_REQUESTED` - Request cancellation of the operation and wait for confirmation that the request was received. + Doesn't wait for actual cancellation. +- `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. + +The default is `WAIT_COMPLETED`. Users can set a different option on the `NexusServiceOptions` by calling +`.setCancellationType()` on `NexusServiceOptions.Builder`. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. + +It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. + +See the +[Nexus cancelation sample](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexuscancellation) +for reference. + +## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} + +This section assumes you are already familiar with +[how connect a Worker to Temporal Cloud](/develop/java/client/temporal-client#start-workflow-execution). The same +[source code](https://github.com/temporalio/samples-go/tree/main/nexus) is used in this section, but the `tcld` CLI will +be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the +caller and handler Workers to their respective Temporal Cloud Namespaces. + +### Install the latest `tcld` CLI and generate certificates + +To install the latest version of the `tcld` CLI, run the following command (on MacOS): + +``` +brew install temporalio/brew/tcld +``` + +If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: + +``` +tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key +``` + +These certificates will be valid for one year. + +### Create caller and handler Namespaces + +Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. +If you already have these Namespaces, you don't need to do this. + +``` +tcld login + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 +``` + +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). + +### Create a Nexus Endpoint to route requests from caller to handler + +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. + +``` +tcld nexus endpoint create \ + --name \ + --target-task-queue my-handler-task-queue \ + --target-namespace \ + --allow-namespace \ + --description-file ./core/src/main/java/io/temporal/samples/nexus/service/description.md +``` + +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. + +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +### Run Workers Connected to Temporal Cloud + +Run the handler Worker: + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \ + --args="-target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key'" +``` + +Run the caller Worker: + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \ + --args="-target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key'" +``` + +### Start a caller Workflow + +``` +./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \ + --args="-target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key'" +``` + +This will result in: + +``` +[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba +[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo πŸ‘‹ +[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc +[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Β‘Hola! Nexus πŸ‘‹ +``` + +## Observability + +### Web UI + +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: + + + +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: + + + +### Temporal CLI + +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: + +``` +temporal workflow describe -w +``` + +Nexus events are included in the caller's Event history: + +``` +temporal workflow show -w +``` + +For **asynchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationStarted` +- `NexusOperationCompleted` + +For **synchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationCompleted` + +:::note + +`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. + +::: + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/encyclopedia/nexus/nexus-code-generator.mdx b/docs/encyclopedia/nexus/nexus-code-generator.mdx new file mode 100644 index 0000000000..00e282c8c9 --- /dev/null +++ b/docs/encyclopedia/nexus/nexus-code-generator.mdx @@ -0,0 +1,49 @@ +--- +id: nexus-code-generator +title: Nexus Code Generator +sidebar_label: Nexus Code Generator +description: The Nexus Code Generator turns one schema into typed models, runtime validators, and Nexus Service definitions for Go, Java, Python, and TypeScript. +toc_max_heading_level: 4 +slug: /nexus/code-generator +tags: + - Nexus + - Concepts +--- + +import { ReleaseNoteHeader } from '@site/src/components'; + + + APIs are experimental and may be subject to backwards-incompatible changes. + + +The Nexus Code Generator, [`nexgen`](https://github.com/temporalio/nex-gen), turns one schema into client code for Go, Java, Python, and TypeScript. +Both sides of a [Nexus Service](/nexus/services) generate from the same file, so neither hand-writes the types and neither can drift from the contract. + +For each type it emits: + +- **A typed model** β€” an idiomatic struct, class, interface, or dataclass, with doc comments carried over from the schema. +- **A runtime validator**, applied when a value is parsed off the wire and again when it is serialized onto it. +- **A [Nexus Service](/nexus/services) definition**, for a file that declares Services. The handler implements it; the caller uses it to invoke Operations. + +## How it works + +You write the contract once, as a JSON definition file, and run `nexgen` against it. +The generator emits client code in Go, Java, Python, or TypeScript. + +Both sides use that generated code: the handler implements the Service, and the caller invokes its Operations. +Because both were generated from the same file, they agree on the contract by construction, and the generated validators enforce it at runtime on every payload. + +This is what makes a Nexus Service polyglot. +A Python handler and a Go caller never share code β€” they share a definition file. +Generate from it in each language and they interoperate, with no coordination between the teams beyond the contract itself. + +## Data validation + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when it is serialized onto it. +Bad data is rejected at the boundary instead of reaching your Workflow. + +Failures aggregate into a single error listing every violation, each naming the offending field and the bound it broke. +A handler maps that to a `BAD_REQUEST` [Nexus error](/nexus/error-handling), so a malformed request tells the caller everything that was wrong in one response. + +A value is validated identically in every language, which is what lets a caller and a handler written in different ones trust the same contract. +Keeping that promise is why the supported schema subset is deliberately strict: anything ambiguous, or anything that cannot be expressed the same way everywhere, is rejected at generation time rather than becoming code that validates differently in one language than another. diff --git a/docs/encyclopedia/nexus/nexus-standalone-activity.mdx b/docs/encyclopedia/nexus/nexus-standalone-activity.mdx new file mode 100644 index 0000000000..05b2fd6ac8 --- /dev/null +++ b/docs/encyclopedia/nexus/nexus-standalone-activity.mdx @@ -0,0 +1,54 @@ +--- +id: nexus-standalone-activity +title: Nexus Standalone Activity +sidebar_label: Nexus Standalone Activity +description: Back a Nexus Operation with a Standalone Activity when the work is a single durable step, with no Workflow wrapped around it. +toc_max_heading_level: 4 +slug: /nexus/standalone-activity +tags: + - Nexus + - Concepts +--- + +import { ReleaseNoteHeader } from '@site/src/components'; + + + APIs are experimental and may be subject to backwards-incompatible changes. + + +:::note Not the same as a Standalone Nexus Operation + +The two names are close and describe opposite ends of the call. +A [Standalone Nexus Operation](/standalone-nexus-operation) is about the **caller**: a Client starts an Operation directly, with no caller Workflow around it. +A Nexus Standalone Activity is about the **handler**: an Operation is backed by a single Activity, with no Workflow behind it. +They are independent choices, and either can be used without the other. + +::: + +An Activity-backed [Nexus Operation](/nexus/operations) runs a [Standalone Activity](/standalone-activity) and completes when that Activity returns. +Use it when the work behind an Operation is one durable step rather than a process: calling an external API, running a computation, writing to another system. + +Two things combine to make this happen. +The [Activity](/activities) supplies durability β€” retries on the policy you set, timeouts you control, and a record of every attempt. +The Operation supplies a typed contract and a [Namespace](/namespaces) boundary, so another team can call it without sharing your code, your deployment, or write access to your Namespace. + +Because the Activity carries the durability, no Workflow is needed behind the Operation. +A Workflow wrapping a single Activity costs two [Billable Actions](/cloud/actions-usage#actions-in-workflows) in Temporal Cloud β€” one to start the Workflow, one to start the Activity β€” where a Standalone Activity costs one. +Retries and heartbeats are billed the same way in either shape. + +## Required options + +Starting an Activity this way needs values a Workflow-called Activity does not, because there is no parent Workflow to supply them: + +- **An Activity Id**, unique within the Namespace. Deriving it from the Nexus request Id makes the start idempotent, so a retried request targets the same Activity Execution instead of sending a second notification or charge. +- **A timeout.** At least one of start-to-close or schedule-to-close. + +The Task Queue is optional and defaults to the one the Operation is running on. Set it explicitly to run the Activity on its own Worker fleet. + +## Cancellation + +An Activity is not interrupted by a cancellation request the way a Workflow is. +The Worker only learns about it on the next Heartbeat, so an Activity that never Heartbeats runs until it completes or times out. +Nothing here is Nexus-specific β€” see [Activity Cancellation](/activity-execution#cancellation). + +Back an Operation with a **Workflow** instead when the work has more than one step, needs to wait for something, needs to receive [messages](/sending-messages), or needs durable intermediate state. diff --git a/docs/encyclopedia/nexus/temporal-operation-handler.mdx b/docs/encyclopedia/nexus/temporal-operation-handler.mdx new file mode 100644 index 0000000000..a8b7fcf616 --- /dev/null +++ b/docs/encyclopedia/nexus/temporal-operation-handler.mdx @@ -0,0 +1,36 @@ +--- +id: temporal-operation-handler +title: Temporal Operation Handler +sidebar_label: Temporal Operation Handler +description: The Temporal Operation Handler is a single handler type that backs a Nexus Operation with a Workflow, an Update, or an Activity, and links every Execution back to the caller. +toc_max_heading_level: 4 +slug: /nexus/temporal-operation-handler +tags: + - Nexus + - Concepts +--- + +import { ReleaseNoteHeader } from '@site/src/components'; + + + APIs are experimental and may be subject to backwards-incompatible changes. + + +Temporal has unified the Workflow handler and the synchronous operation handler into a single handler, and added the ability to back an Operation with a [Standalone Activity](/nexus/standalone-activity). + +What runs behind an Operation remains private to the handler, so you can change it later without touching the contract or any caller. + +## The Nexus-aware Client + +The start handler receives a context, the Operation input, and a Client. + +That Client is not an ordinary Temporal Client. +It propagates [bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so caller-side and handler-side Executions are connected in the UI and in [Event History](/encyclopedia/event-history) without wiring anything. +Constructing your own Client inside a handler works, but the Executions it starts are not linked back to the caller. + +It exposes two kinds of call: + +- **Async backings**, at most one per Operation invocation. These determine what the Operation *is*, and their result reaches the caller through the Nexus completion callback. Starting a Workflow, starting an Activity, and starting a Workflow Update are all async backings. +- **Sync messaging**, as many as you need. Signals and Signal-with-Start take effect during the handler call and do not require an async backing. + +Deriving the backing Execution's Id from the Nexus request Id keeps a retried start request targeting the same Execution instead of creating a second one. diff --git a/sidebars.js b/sidebars.js index ba42163641..095eec559c 100644 --- a/sidebars.js +++ b/sidebars.js @@ -420,6 +420,7 @@ const developJavaCategory = { items: [ 'develop/java/nexus/quickstart', 'develop/java/nexus/feature-guide', + 'develop/java/nexus/developer-experience', 'develop/java/nexus/standalone-operations', ], }, @@ -2111,7 +2112,6 @@ module.exports = { items: [ 'encyclopedia/nexus/nexus-services', 'encyclopedia/nexus/nexus-operations', - 'encyclopedia/nexus/standalone-nexus-operation', 'encyclopedia/nexus/nexus-endpoints', 'encyclopedia/nexus/nexus-registry', 'encyclopedia/nexus/nexus-patterns', @@ -2119,6 +2119,11 @@ module.exports = { 'encyclopedia/nexus/nexus-execution-debugging', 'encyclopedia/nexus/nexus-error-handling', 'encyclopedia/nexus/nexus-metrics', + // Pre-release features, kept at the bottom of the section. + 'encyclopedia/nexus/temporal-operation-handler', + 'encyclopedia/nexus/nexus-code-generator', + 'encyclopedia/nexus/standalone-nexus-operation', + 'encyclopedia/nexus/nexus-standalone-activity', ], }, { From 842873435818c67392c47e031964d0bb2ee520b1 Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Wed, 2 Sep 2026 10:57:50 -0700 Subject: [PATCH 02/10] Updated to address a PR comment. --- docs/encyclopedia/nexus/nexus-code-generator.mdx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/encyclopedia/nexus/nexus-code-generator.mdx b/docs/encyclopedia/nexus/nexus-code-generator.mdx index 00e282c8c9..55dd4275d5 100644 --- a/docs/encyclopedia/nexus/nexus-code-generator.mdx +++ b/docs/encyclopedia/nexus/nexus-code-generator.mdx @@ -16,8 +16,11 @@ import { ReleaseNoteHeader } from '@site/src/components'; APIs are experimental and may be subject to backwards-incompatible changes. -The Nexus Code Generator, [`nexgen`](https://github.com/temporalio/nex-gen), turns one schema into client code for Go, Java, Python, and TypeScript. -Both sides of a [Nexus Service](/nexus/services) generate from the same file, so neither hand-writes the types and neither can drift from the contract. +A [Nexus Service](/nexus/services) is called across a team boundary, often by a caller written in a different language and deployed on its own schedule. +When each side hand-writes its own request and response types, the two copies drift, and nothing catches it until a call fails. + +The Nexus Code Generator, [`nexgen`](https://github.com/temporalio/nex-gen), generates client code for Go, Java, Python, and TypeScript from a schema file that defines the contract. +Both sides can then use code generated from the same file, which gives data validation and type safety across the languages and helps prevent drift. For each type it emits: From e4fab9a13fef3a2b595129fb58daa7e5fb6e3012 Mon Sep 17 00:00:00 2001 From: Alice Lin Date: Tue, 1 Sep 2026 17:20:09 -0700 Subject: [PATCH 03/10] Add docs for other languages --- .../dotnet/nexus/developer-experience.mdx | 549 ++++++++++++++ .../develop/go/nexus/developer-experience.mdx | 699 ++++++++++++++++++ .../python/nexus/developer-experience.mdx | 482 ++++++++++++ .../typescript/nexus/developer-experience.mdx | 476 ++++++++++++ sidebars.js | 4 + 5 files changed, 2210 insertions(+) create mode 100644 docs/develop/dotnet/nexus/developer-experience.mdx create mode 100644 docs/develop/go/nexus/developer-experience.mdx create mode 100644 docs/develop/python/nexus/developer-experience.mdx create mode 100644 docs/develop/typescript/nexus/developer-experience.mdx diff --git a/docs/develop/dotnet/nexus/developer-experience.mdx b/docs/develop/dotnet/nexus/developer-experience.mdx new file mode 100644 index 0000000000..44f2c88b4b --- /dev/null +++ b/docs/develop/dotnet/nexus/developer-experience.mdx @@ -0,0 +1,549 @@ +--- +id: developer-experience +slug: /develop/dotnet/nexus/developer-experience +title: Nexus Developer Experience - .NET SDK feature guide +sidebar_label: Nexus Developer Experience +description: Build a Nexus Service in .NET with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a single Service contract. +toc_max_heading_level: 4 +tags: + - Nexus + - .NET SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +:::tip + +New to Nexus? Start with the [Nexus .NET Quickstart](/develop/dotnet/nexus/quickstart). + +::: + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) +- [Create caller and handler Namespaces](#create-caller-handler-namespaces) +- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +:::note + +This documentation uses source code derived from the +[.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). + +::: + +## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} + +Prerequisites: + +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/dotnet/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (v1.3.0 or higher recommended) +- [Install the latest Temporal .NET SDK](https://learn.temporal.io/getting_started/dotnet/dev_environment/#install-the-temporal-net-sdk) + (v1.18.0 or higher recommended) + +The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. + +``` +temporal server start-dev +``` + +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. + +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. + +## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} + +Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. + +``` +temporal operator namespace create --namespace nexus-simple-handler-namespace +temporal operator namespace create --namespace nexus-simple-caller-namespace +``` + +`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in +`nexus-simple-caller-namespace` to call that Operation handler. We use different namespaces to demonstrate +cross-Namespace Nexus calls. + +## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} + +After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. + +``` +temporal operator nexus endpoint create \ + --name nexus-simple-endpoint \ + --target-namespace nexus-simple-handler-namespace \ + --target-task-queue nexus-simple-handler-sample +``` + +You can also use the Web UI to create the Namespaces and Nexus endpoint. + +## Define the Nexus Service contract {/* #define-nexus-service-contract */} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. + +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. + +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} + +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). +Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method +`[TemporalOperation]` and the method body itself becomes the start handler, receiving three things: a +`TemporalOperationStartContext`, an `ITemporalNexusClient`, and the Operation input. The Operation the method handles is +matched by method name to the corresponding `[NexusOperation]` method on the Service interface. What you do with the +Client decides what backs the Operation: + +- **Synchronous.** Return `TemporalOperationResult.SyncResult(...)` and the Operation completes during the handler + call. The caller has its result as soon as the call returns. +- **Asynchronous.** Call `StartWorkflowAsync`, `StartActivityAsync`, or `StartWorkflowUpdateAsync` on the Client. The + handler returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, + which may be days later. Its result is delivered to the caller through the Nexus completion callback. This is what + lets an Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. + +### Develop a Synchronous Nexus Operation handler + +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +```csharp +using NexusRpc.Handlers; +using Temporalio.Nexus; + +[NexusServiceHandler(typeof(IHelloService))] +public class HelloService +{ + [TemporalOperation] + public Task> Echo( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + IHelloService.EchoInput input) => + Task.FromResult(TemporalOperationResult.SyncResult( + new(input.Message))); +} +``` + +### Use the Temporal Client for Signals, Queries, and Updates + +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +Updates are the exception. Do not wait for one inside the handler. Start it with `StartWorkflowUpdateAsync` and it backs +the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it +takes. + +The [NexusMessaging](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. + +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.TemporalClient` rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: + +```csharp +private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}"; + +[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)); + return TemporalOperationResult.SyncResult(new()); +} +``` + +There are two examples of messaging through Nexus in the sample code: the [caller pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/CallerPattern) and the [on-demand pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/OnDemandPattern). +The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Call `StartWorkflowAsync` on the Client. The Operation completes when the Workflow returns, and the Workflow's return +value is delivered to the caller as the Operation's result. + +```csharp +[NexusServiceHandler(typeof(IHelloService))] +public class HelloService +{ + [TemporalOperation] + public Task> SayHello( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + IHelloService.HelloInput input) => + client.StartWorkflowAsync( + (HelloHandlerWorkflow wf) => wf.RunAsync(input), + // Workflow IDs should typically be business meaningful IDs and are used to dedupe + // workflow starts. Task queue defaults to the operation's task queue when omitted. + new() { Id = $"hello-{input.Name}-{input.Language}" }); +} +``` + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them as separate +arguments in the `RunAsync` lambda: + +```csharp +client.StartWorkflowAsync( + (HelloHandlerWorkflow wf) => wf.RunAsync(input.Language, input.Name), + new() { Id = $"hello-{input.Name}-{input.Language}" }); +``` + +### Register a Nexus Service in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus +Service in a Worker. + +[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + +```csharp +async Task RunHandlerWorkerAsync() +{ + // Run worker until cancelled + logger.LogInformation("Running handler worker"); + using var worker = new TemporalWorker( + await ConnectClientAsync("nexus-simple-handler-namespace"), + new TemporalWorkerOptions(taskQueue: "nexus-simple-handler-sample"). + AddNexusService(new HelloService()). + AddWorkflow()); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Handler worker cancelled"); + } +} +``` + +Nexus Service handlers also support dependency injection through the generic-host Worker. See +[Use dependency injection with a Nexus Service handler](/develop/dotnet/nexus/feature-guide#dependency-injection). + +## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} + +Import the Service interface that has the necessary Operation names and input/output types to execute a Nexus Operation +from the caller Workflow: + +[NexusSimple/Caller/EchoCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs) + +```csharp +using Temporalio.Workflows; + +[Workflow] +public class EchoCallerWorkflow +{ + [WorkflowRun] + public async Task RunAsync(string message) + { + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). + ExecuteNexusOperationAsync(svc => svc.Echo(new(message))); + return output.Message; + } +} +``` + +[NexusSimple/Caller/HelloCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs) + +```csharp +using Temporalio.Workflows; + +[Workflow] +public class HelloCallerWorkflow +{ + [WorkflowRun] + public async Task RunAsync(string name, IHelloService.HelloLanguage language) + { + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). + ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language))); + return output.Message; + } +} +``` + +### Register the caller Workflow in a Worker + +After developing the caller Workflow, the next step is to register it with a Worker. + +[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + +```csharp +async Task RunCallerWorkerAsync() +{ + // Run worker until cancelled + logger.LogInformation("Running caller worker"); + using var worker = new TemporalWorker( + await ConnectClientAsync("nexus-simple-caller-namespace"), + new TemporalWorkerOptions(taskQueue: "nexus-simple-caller-sample"). + AddWorkflow(). + AddWorkflow()); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + logger.LogInformation("Caller worker cancelled"); + } +} +``` + +### Develop a starter to start the caller Workflow + +To initiate the caller Workflow, a starter program is used. + +[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + +```csharp +async Task ExecuteCallerWorkflowAsync() +{ + logger.LogInformation("Executing caller echo workflow"); + var client = await ConnectClientAsync("nexus-simple-caller-namespace"); + var result1 = await client.ExecuteWorkflowAsync( + (EchoCallerWorkflow wf) => wf.RunAsync("Nexus Echo πŸ‘‹"), + new(id: "nexus-simple-echo-id", taskQueue: "nexus-simple-caller-sample")); + logger.LogInformation("Workflow result: {Result}", result1); + + logger.LogInformation("Executing caller hello workflow"); + var result2 = await client.ExecuteWorkflowAsync( + (HelloCallerWorkflow wf) => wf.RunAsync("Temporal", IHelloService.HelloLanguage.Es), + new(id: "nexus-simple-hello-id", taskQueue: "nexus-simple-caller-sample")); + logger.LogInformation("Workflow result: {Result}", result2); +} +``` + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} + +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. + +### Run Workers connected to a local development server + +Run the Nexus handler Worker: + +```bash +dotnet run handler-worker +``` + +In another terminal window, run the Nexus caller Worker: + +```bash +dotnet run caller-worker +``` + +### Start a caller Workflow + +With the Workers running, the final step in the local development process is to start a caller Workflow. + +Run the starter: + +```bash +dotnet run caller-workflow +``` + +This will show the two workflows started and their results. + +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} + +To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only +asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or +other resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter +a terminal state. + +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller +reacts to cancellation: + +- `Abandon` - Do not request cancellation of the operation. +- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type + doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is + done. +- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was + received. Doesn't wait for actual cancellation. +- `WaitCancellationCompleted` - Wait for operation completion. Operation may or may not complete as cancelled. + +The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in +`NexusWorkflowOperationOptions` when starting an operation. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. + +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. + +See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for +reference. + +## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} + +This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to +create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and +handler Workers to their respective Temporal Cloud Namespaces. + +### Install the latest `tcld` CLI and generate certificates + +To install the latest version of the `tcld` CLI, run the following command (on MacOS): + +``` +brew install temporalio/brew/tcld +``` + +If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: + +``` +tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key +``` + +These certificates will be valid for one year. + +### Create caller and handler Namespaces + +Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. +If you already have these Namespaces, you don't need to do this. + +``` +tcld login + +tcld namespace create \ + --namespace \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 + +tcld namespace create \ + --namespace \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 +``` + +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). + +### Create a Nexus Endpoint to route requests from caller to handler + +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. + +``` +tcld nexus endpoint create \ + --name nexus-simple-endpoint \ + --target-task-queue nexus-simple-handler-sample \ + --target-namespace \ + --allow-namespace \ + --description-file endpoint_description.md +``` + +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. + +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +## Observability + +### Web UI + +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: + + + +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: + + + +### Temporal CLI + +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: + +``` +temporal workflow describe -w +``` + +Nexus events are included in the caller's Event history: + +``` +temporal workflow show -w +``` + +For **asynchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationStarted` +- `NexusOperationCompleted` + +For **synchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationCompleted` + +:::note + +`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. + +::: + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/go/nexus/developer-experience.mdx b/docs/develop/go/nexus/developer-experience.mdx new file mode 100644 index 0000000000..ec4e9c3a90 --- /dev/null +++ b/docs/develop/go/nexus/developer-experience.mdx @@ -0,0 +1,699 @@ +--- +id: developer-experience +slug: /develop/go/nexus/developer-experience +title: Nexus Developer Experience - Go SDK feature guide +sidebar_label: Nexus Developer Experience +description: Build a Nexus Service in Go with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. +toc_max_heading_level: 4 +tags: + - Nexus + - Go SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +:::tip + +New to Nexus? Start with the [Nexus Go Quickstart](/develop/go/nexus/quickstart). + +::: + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) +- [Create caller and handler Namespaces](#create-caller-handler-namespaces) +- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +:::note + +This documentation uses source code derived from the +[Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). + +::: + +## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} + +Prerequisites: + +- [Install the latest Temporal CLI](/develop/run-a-development-server) (v1.3.0 or higher recommended) +- [Install the latest Temporal Go SDK](/develop/go/set-up-your-local-go) (v1.48.0 or higher recommended) + +The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. + +``` +temporal server start-dev +``` + +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. + +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. + +## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} + +Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. + +``` +temporal operator namespace create --namespace my-target-namespace +temporal operator namespace create --namespace my-caller-namespace +``` + +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. + +## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} + +After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. + +``` +temporal operator nexus endpoint create \ + --name my-nexus-endpoint-name \ + --target-namespace my-target-namespace \ + --target-task-queue my-handler-task-queue +``` + +You can also use the Web UI to create the Namespaces and Nexus endpoint. + +## Define the Nexus Service contract {/* #define-nexus-service-contract */} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. + +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. + +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} + +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). +Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the +Operation input. What you do with the Client decides what backs the Operation: + +- **Synchronous.** Return `temporalnexus.NewSyncResult(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `temporalnexus.StartWorkflow`, `temporalnexus.StartActivity`, or + `temporalnexus.StartUpdateWorkflow` with the Client. The handler returns as soon as that Execution has started, and + the Operation stays open until the Execution finishes, which may be days later. Its result is delivered to the caller + through the Nexus completion callback. This is what lets an Operation outlive the + [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. + +### Develop a Synchronous Nexus Operation handler + +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +```go +var EchoOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.EchoInput, service.EchoOutput]{ + Name: service.EchoOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.EchoInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.EchoOutput], error) { + return temporalnexus.NewSyncResult(service.EchoOutput(input)), nil + }, + }) +``` + +### Use the Temporal Client for Signals, Queries, and Updates + +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +Updates are the exception. Do not wait for one inside the handler. Start it with `temporalnexus.StartUpdateWorkflow` and +it backs the Operation. The handler returns straight away, and the Operation completes when the Update does, however +long it takes. + +The [nexus-messaging](https://github.com/temporalio/samples-go/tree/main/nexus-messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. + +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `nc.GetWorkflowClient()` rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: + +```go +var ApproveOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.ApproveInput, service.ApproveOutput]{ + Name: service.ApproveOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.ApproveInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.ApproveOutput], error) { + err := nc.GetWorkflowClient().SignalWorkflow( + ctx, GetWorkflowID(input.UserID), "", service.ApproveSignalName, input) + if err != nil { + return temporalnexus.TemporalOperationResult[service.ApproveOutput]{}, err + } + return temporalnexus.NewSyncResult(service.ApproveOutput{}), nil + }, + }) +``` + +There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/ondemandpattern/). +The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Call `temporalnexus.StartWorkflow` with the Client. The Operation completes when the Workflow returns, and the +Workflow's return value is delivered to the caller as the Operation's result. + +```go +var HelloOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.HelloInput, service.HelloOutput]{ + Name: service.HelloOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.HelloInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.HelloOutput], error) { + return temporalnexus.StartWorkflow(ctx, nc, client.StartWorkflowOptions{ + ID: service.HelloWorkflowID(input), + // Task queue defaults to the task queue this operation is handled on. + }, HelloHandlerWorkflow, input) + }, + }) +``` + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. `temporalnexus.StartWorkflow` is typed for a Workflow that takes a +single argument, so to start a Workflow that takes several, use `temporalnexus.StartUntypedWorkflow` and pass the +arguments after the Workflow function: + +```go +return temporalnexus.StartUntypedWorkflow[service.HelloOutput](ctx, nc, client.StartWorkflowOptions{ + ID: service.HelloWorkflowID(input), +}, HelloHandlerWorkflow, input.Name, input.Language) +``` + +### Register a Nexus Service in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus +Service in a Worker. + + +[nexus/handler/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/worker/main.go) +```go +package main + +import ( + "log" + "os" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" + + "github.com/nexus-rpc/sdk-go/nexus" + "github.com/temporalio/samples-go/nexus/handler" + "github.com/temporalio/samples-go/nexus/options" + "github.com/temporalio/samples-go/nexus/service" +) + +const ( + taskQueue = "my-handler-task-queue" +) + +func main() { + // The client and worker are heavyweight objects that should be created once per process. + clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) + if err != nil { + log.Fatalf("Invalid arguments: %v", err) + } + c, err := client.Dial(clientOptions) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + w := worker.New(c, taskQueue, worker.Options{}) + service := nexus.NewService(service.HelloServiceName) + err = service.Register(handler.EchoOperation, handler.HelloOperation) + if err != nil { + log.Fatalln("Unable to register operations", err) + } + w.RegisterNexusService(service) + w.RegisterWorkflow(handler.HelloHandlerWorkflow) + + err = w.Run(worker.InterruptCh()) + if err != nil { + log.Fatalln("Unable to start worker", err) + } +} +``` + + +## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} + +Import the Service API package that has the necessary service and operation names and input/output types to execute a +Nexus Operation from the caller Workflow: + + +[nexus/caller/workflows.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/workflows.go) +```go +package caller + +import ( + "github.com/temporalio/samples-go/nexus/service" + "go.temporal.io/sdk/workflow" +) + +const ( + TaskQueue = "my-caller-workflow-task-queue" + endpointName = "my-nexus-endpoint-name" +) + +func EchoCallerWorkflow(ctx workflow.Context, message string) (string, error) { + c := workflow.NewNexusClient(endpointName, service.HelloServiceName) + + fut := c.ExecuteOperation(ctx, service.EchoOperationName, service.EchoInput{Message: message}, workflow.NexusOperationOptions{}) + + var res service.EchoOutput + if err := fut.Get(ctx, &res); err != nil { + return "", err + } + + return res.Message, nil +} + +func HelloCallerWorkflow(ctx workflow.Context, name string, language service.Language) (string, error) { + c := workflow.NewNexusClient(endpointName, service.HelloServiceName) + + fut := c.ExecuteOperation(ctx, service.HelloOperationName, service.HelloInput{Name: name, Language: language}, workflow.NexusOperationOptions{}) + var res service.HelloOutput + + // Optionally wait for the operation to be started. NexusOperationExecution will contain the operation token in + // case this operation is asynchronous, which is a handle that can be used to perform additional actions like + // cancelling an operation. + var exec workflow.NexusOperationExecution + if err := fut.GetNexusOperationExecution().Get(ctx, &exec); err != nil { + return "", err + } + if err := fut.Get(ctx, &res); err != nil { + return "", err + } + + return res.Message, nil +} + +``` + + +### Register the caller Workflow in a Worker + +After developing the caller Workflow, the next step is to register it with a Worker. + + +[nexus/caller/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/worker/main.go) +```go +package main + +import ( + "log" + "os" + + "github.com/temporalio/samples-go/nexus/caller" + "github.com/temporalio/samples-go/nexus/options" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" +) + +func main() { + // The client and worker are heavyweight objects that should be created once per process. + clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) + if err != nil { + log.Fatalf("Invalid arguments: %v", err) + } + c, err := client.Dial(clientOptions) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + + w := worker.New(c, caller.TaskQueue, worker.Options{}) + + w.RegisterWorkflow(caller.EchoCallerWorkflow) + w.RegisterWorkflow(caller.HelloCallerWorkflow) + + err = w.Run(worker.InterruptCh()) + if err != nil { + log.Fatalln("Unable to start worker", err) + } +} +``` + + +### Develop a starter to start the caller Workflow + +To initiate the caller Workflow, a starter program is used. + + +[nexus/caller/starter/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/starter/main.go) +```go +package main + +import ( + "context" + "log" + "os" + "time" + + "go.temporal.io/sdk/client" + + "github.com/temporalio/samples-go/nexus/caller" + "github.com/temporalio/samples-go/nexus/options" + "github.com/temporalio/samples-go/nexus/service" +) + +func main() { + clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) + if err != nil { + log.Fatalf("Invalid arguments: %v", err) + } + c, err := client.Dial(clientOptions) + if err != nil { + log.Fatalln("Unable to create client", err) + } + defer c.Close() + runWorkflow(c, caller.EchoCallerWorkflow, "Nexus Echo πŸ‘‹") + runWorkflow(c, caller.HelloCallerWorkflow, "Nexus", service.ES) +} + +func runWorkflow(c client.Client, workflow interface{}, args ...interface{}) { + ctx := context.Background() + workflowOptions := client.StartWorkflowOptions{ + ID: "nexus_hello_caller_workflow_" + time.Now().Format("20060102150405"), + TaskQueue: caller.TaskQueue, + } + + wr, err := c.ExecuteWorkflow(ctx, workflowOptions, workflow, args...) + if err != nil { + log.Fatalln("Unable to execute workflow", err) + } + log.Println("Started workflow", "WorkflowID", wr.GetID(), "RunID", wr.GetRunID()) + + // Synchronously wait for the workflow completion. + var result string + err = wr.Get(context.Background(), &result) + if err != nil { + log.Fatalln("Unable get workflow result", err) + } + log.Println("Workflow result:", result) +} +``` + + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} + +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. + +### Run Workers connected to a local development server + +Run the Nexus handler Worker: + +```bash +cd handler +go run ./worker \ + -target-host localhost:7233 \ + -namespace my-target-namespace +``` + +In another terminal window, run the Nexus caller Worker: + +```bash +cd caller +go run ./worker \ + -target-host localhost:7233 \ + -namespace my-caller-namespace +``` + +### Start a caller Workflow + +With the Workers running, the final step in the local development process is to start a caller Workflow. + +Run the starter: + +```bash +cd caller +go run ./starter \ + -target-host localhost:7233 \ + -namespace my-caller-namespace +``` + +This will result in: + +``` +2024/10/04 19:57:40 Workflow result: Nexus Echo πŸ‘‹ +2024/10/04 19:57:40 Started workflow WorkflowID nexus_hello_caller_workflow_20240723195740 RunID c9789128-2fcd-4083-829d-95e43279f6d7 +2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ +``` + +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} + +To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. This +returns a new context and a function that, when called, cancels the context and any SDK method that was passed this +context. The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it +succeeds, fails, times out, or is canceled. + +Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or +other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter +a terminal state. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. + +It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. + +See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) +for reference. + +## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} + +This section assumes you are already familiar with +[how to connect a Worker to Temporal Cloud](/develop/go/client/temporal-client#connect-to-temporal-cloud). The same +[source code](https://github.com/temporalio/samples-go/tree/main/nexus) is used in this section, but the `tcld` CLI will +be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the +caller and handler Workers to their respective Temporal Cloud Namespaces. + +### Install the latest `tcld` CLI and generate certificates + +To install the latest version of the `tcld` CLI, run the following command (on MacOS): + +``` +brew install temporalio/brew/tcld +``` + +If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: + +``` +tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key +``` + +These certificates will be valid for one year. + +### Create caller and handler Namespaces + +Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. +If you already have these Namespaces, you don't need to do this. + +``` +tcld login + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 +``` + +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). + +### Create a Nexus Endpoint to route requests from caller to handler + +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. + +``` +tcld nexus endpoint create \ + --name \ + --target-task-queue my-handler-task-queue \ + --target-namespace \ + --allow-namespace \ + --description-file ./nexus/service/description.md +``` + +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. + +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +### Run Workers connected to Temporal Cloud + +Run the handler Worker: + +``` +cd handler + +go run ./worker \ + -target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key' +``` + +Run the caller Worker: + +``` +cd caller + +go run ./worker \ + -target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key' +``` + +To connect with an API key instead of mTLS certificates, replace `-client-cert` and `-client-key` with +`-api-key `. + +### Start a caller Workflow in Temporal Cloud + +``` +cd caller + +go run ./starter \ + -target-host .tmprl.cloud:7233 \ + -namespace \ + -client-cert 'path/to/your/ca.pem' \ + -client-key 'path/to/your/ca.key' +``` + +This will result in: + +``` +2024/10/04 19:57:40 Workflow result: Nexus Echo πŸ‘‹ +2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ +``` + +## Observability + +### Web UI + +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: + + + +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: + + + +### Temporal CLI + +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: + +``` +temporal workflow describe -w +``` + +Nexus events are included in the caller's Event history: + +``` +temporal workflow show -w +``` + +For **asynchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationStarted` +- `NexusOperationCompleted` + +For **synchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationCompleted` + +:::note + +`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. + +::: + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/python/nexus/developer-experience.mdx b/docs/develop/python/nexus/developer-experience.mdx new file mode 100644 index 0000000000..ab37841647 --- /dev/null +++ b/docs/develop/python/nexus/developer-experience.mdx @@ -0,0 +1,482 @@ +--- +id: developer-experience +slug: /develop/python/nexus/developer-experience +title: Nexus Developer Experience - Python SDK feature guide +sidebar_label: Nexus Developer Experience +description: Build a Nexus Service in Python with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. +toc_max_heading_level: 4 +tags: + - Nexus + - Python SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +:::tip + +New to Nexus? Start with the [Nexus Python Quickstart](/develop/python/nexus/quickstart). + +::: + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) +- [Create caller and handler Namespaces](#create-caller-handler-namespaces) +- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +:::note + +This documentation uses source code derived from the +[Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). + +::: + +## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} + +Prerequisites: + +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/python/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (`v1.3.0` or higher recommended) +- [Install the latest Temporal Python SDK](https://learn.temporal.io/getting_started/python/dev_environment/#add-temporal-python-sdk-dependencies) + (`v1.32.0` or higher recommended) + +The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. + +``` +temporal server start-dev +``` + +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. + +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. + +## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} + +Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. + +``` +temporal operator namespace create --namespace my-target-namespace +temporal operator namespace create --namespace my-caller-namespace +``` + +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. + +## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} + +After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. + +``` +temporal operator nexus endpoint create \ + --name my-nexus-endpoint-name \ + --target-namespace my-target-namespace \ + --target-task-queue my-handler-task-queue +``` + +You can also use the Web UI to create the Namespaces and Nexus endpoint. + +## Define the Nexus Service contract {/* #define-nexus-service-contract */} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service module that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. + +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. + +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} + +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). +Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). The +`@nexus.temporal_operation` decorator hands your start method three things: a context, a Client, and the Operation +input. What you do with the Client decides what backs the Operation: + +- **Synchronous.** Return `nexus.TemporalOperationResult.sync(...)` and the Operation completes during the handler call. + The caller has its result as soon as the call returns. +- **Asynchronous.** Call `start_workflow`, `start_activity`, or `start_workflow_update` on the Client. The handler + returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be + days later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an + Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. + +### Develop a Synchronous Nexus Operation handler + +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +```python +import nexusrpc +from temporalio import nexus + + +@nexusrpc.handler.service_handler(service=MyNexusService) +class MyNexusServiceHandler: + @nexus.temporal_operation + async def echo( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: EchoInput, + ) -> nexus.TemporalOperationResult[EchoOutput]: + return nexus.TemporalOperationResult.sync(EchoOutput(message=input.message)) +``` + +### Use the Temporal Client for Signals, Queries, and Updates + +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +Updates are the exception. Do not wait for one inside the handler. Start it with `start_workflow_update` and it backs +the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it +takes. + +The [nexus_messaging](https://github.com/temporalio/samples-python/tree/main/nexus_messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. + +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.client` rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: + +```python +@nexusrpc.handler.service_handler(service=NexusGreetingService) +class NexusGreetingServiceHandler: + def _get_workflow_handle( + self, client: Client, user_id: str + ) -> WorkflowHandle[GreetingWorkflow, str]: + return client.get_workflow_handle_for( + GreetingWorkflow.run, f"GreetingWorkflow_for_{user_id}" + ) + + @nexus.temporal_operation + async def approve( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: ApproveInput, + ) -> nexus.TemporalOperationResult[ApproveOutput]: + await self._get_workflow_handle(client.client, input.user_id).signal( + GreetingWorkflow.approve, input + ) + return nexus.TemporalOperationResult.sync(ApproveOutput()) +``` + +There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/ondemandpattern/). +The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Call `start_workflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value +is delivered to the caller as the Operation's result. + +```python +@nexusrpc.handler.service_handler(service=MyNexusService) +class MyNexusServiceHandler: + @nexus.temporal_operation + async def hello( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: HelloInput, + ) -> nexus.TemporalOperationResult[HelloOutput]: + return await client.start_workflow( + HelloHandlerWorkflow.run, + input, + id=f"hello-{input.name}-{input.language}", + ) +``` + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them to +`start_workflow` as `args` instead of a single positional argument: + +```python +return await client.start_workflow( + HelloHandlerWorkflow.run, + args=[input.name, input.language], + id=f"hello-{input.name}-{input.language}", +) +``` + +### Register a Nexus Service in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus +Service handler in a Worker. At this stage you can pass any arguments you need to your service handler's `__init__` +method. + +[hello_nexus/handler/worker.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/worker.py) + +```python +async def main(): + client = await Client.connect("localhost:7233", namespace=NAMESPACE) + worker = Worker( + client, + task_queue=TASK_QUEUE, + workflows=[HelloHandlerWorkflow], + nexus_service_handlers=[MyNexusServiceHandler()], + ) + await worker.run() +``` + +## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} + +To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation +input/output types: + +[hello_nexus/caller/workflows.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/workflows.py) + +```python +from temporalio import workflow + +with workflow.unsafe.imports_passed_through(): + from hello_nexus.service import MyInput, MyNexusService, MyOutput + + +@workflow.defn +class CallerWorkflow: + @workflow.run + async def run(self, name: str) -> tuple[MyOutput, MyOutput]: + nexus_client = workflow.create_nexus_client( + service=MyNexusService, + endpoint=NEXUS_ENDPOINT, + ) + # Start the nexus operation and wait for the result in one go, using execute_operation. + wf_result = await nexus_client.execute_operation( + MyNexusService.my_workflow_run_operation, + MyInput(name), + ) + # Alternatively, you can use start_operation to obtain the operation handle and + # then `await` the handle to obtain the result. + sync_operation_handle = await nexus_client.start_operation( + MyNexusService.my_sync_operation, + MyInput(name), + ) + sync_result = await sync_operation_handle + return sync_result, wf_result +``` + +### Register the caller Workflow in a Worker and start the caller Workflow + +After developing the caller Workflow, the next step is to register it with a Worker. + +Finally, the caller Workflow must be started using `client.start_workflow()` or `client.execute_workflow()`. + +These steps are the same as for any normal Workflow. The Python sample combines them in a single application. +See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for +reference. + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} + +In one terminal, run the Temporal worker in the handler namespace: +``` +uv run handler/worker.py +``` + +In another terminal, run the Temporal worker in the caller namespace and start the caller workflow: +``` +uv run caller/app.py +``` + +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} + +To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous +operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or other +resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter a +terminal state. + +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller +reacts to cancellation: + +- `ABANDON` - Do not request cancellation of the operation. +- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type + doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is + done. +- `WAIT_REQUESTED` - Request cancellation of the operation and wait for confirmation that the request was received. + Doesn't wait for actual cancellation. +- `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. + +The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an +operation. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. + +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. + +See the [Nexus cancellation sample](https://github.com/temporalio/samples-python/tree/main/nexus_cancel) for reference. + +## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} + +This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to +create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and +handler Workers to their respective Temporal Cloud Namespaces. + +### Install the latest `tcld` CLI and generate certificates + +To install the latest version of the `tcld` CLI, run the following command (on macOS): + +``` +brew install temporalio/brew/tcld +``` + +If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: + +``` +tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key +``` + +These certificates will be valid for one year. + +### Create caller and handler Namespaces + +Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. +If you already have these Namespaces, you don't need to do this. + +``` +tcld login + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 +``` + +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). + +### Create a Nexus Endpoint to route requests from caller to handler + +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. + +``` +tcld nexus endpoint create \ + --name \ + --target-task-queue my-handler-task-queue \ + --target-namespace \ + --allow-namespace \ + --description-file hello_nexus/endpoint_description.md +``` + +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. + +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +## Observability + +### Web UI + +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: + + + +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: + + + +### Temporal CLI + +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: + +``` +temporal workflow describe -w +``` + +Nexus events are included in the caller's Event history: + +``` +temporal workflow show -w +``` + +For **asynchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationStarted` +- `NexusOperationCompleted` + +For **synchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationCompleted` + +:::note + +`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. + +::: + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/nexus/developer-experience.mdx b/docs/develop/typescript/nexus/developer-experience.mdx new file mode 100644 index 0000000000..ab05630527 --- /dev/null +++ b/docs/develop/typescript/nexus/developer-experience.mdx @@ -0,0 +1,476 @@ +--- +id: developer-experience +slug: /develop/typescript/nexus/developer-experience +title: Nexus Developer Experience - TypeScript SDK feature guide +sidebar_label: Nexus Developer Experience +description: Build a Nexus Service in TypeScript with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. +toc_max_heading_level: 4 +tags: + - Nexus + - TypeScript SDK +--- + +import { CaptionedImage } from '@site/src/components'; + +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. + +:::tip + +New to Nexus? Start with the [Nexus TypeScript Quickstart](/develop/typescript/nexus/quickstart). + +::: + +This page shows how to do the following: + +- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) +- [Create caller and handler Namespaces](#create-caller-handler-namespaces) +- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) +- [Define the Nexus Service contract](#define-nexus-service-contract) +- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) +- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) +- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) + +:::note + +This documentation uses source code derived from the +[TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). + +::: + +## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} + +Prerequisites: + +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/typescript/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (`v1.3.0` or higher recommended) +- [Install the latest Temporal TypeScript SDK](https://learn.temporal.io/getting_started/typescript/dev_environment/#add-temporal-typescript-sdk-dependencies) + (`v1.23.0` or higher recommended) + +The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. + +``` +temporal server start-dev +``` + +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. + +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. + +## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} + +Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. + +``` +temporal operator namespace create --namespace my-target-namespace +temporal operator namespace create --namespace my-caller-namespace +``` + +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. + +## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} + +After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. + +``` +temporal operator nexus endpoint create \ + --name my-nexus-endpoint-name \ + --target-namespace my-target-namespace \ + --target-task-queue my-handler-task-queue +``` + +You can also use the Web UI to create the Namespaces and Nexus endpoint. + +## Define the Nexus Service contract {/* #define-nexus-service-contract */} + +Defining a clear contract for the Nexus Service is crucial for smooth communication. + +In this example, there is a service module that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. + +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. + +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. + +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. + +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} + +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). +Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Its `start` function +receives three things: a context, a Client, and the Operation input. What you do with the Client decides what backs the +Operation: + +- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `startWorkflow` or `startActivity` on the Client, or `update` on a handle from + `getWorkflowHandle`. The handler returns as soon as that Execution has started, and the Operation stays open until the + Execution finishes, which may be days later. Its result is delivered to the caller through the Nexus completion + callback. This is what lets an Operation outlive the + [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. + +### Develop a Synchronous Nexus Operation handler + +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +```ts +import * as nexus from 'nexus-rpc'; +import * as temporalNexus from '@temporalio/nexus'; +import { helloService, EchoInput, EchoOutput } from '../api'; + +export const helloServiceHandler = nexus.serviceHandler(helloService, { + echo: new temporalNexus.TemporalOperationHandler({ + start: async (ctx, client, input) => { + return temporalNexus.TemporalOperationResult.sync({ message: input.message }); + }, + }), +}); +``` + +### Use the Temporal Client for Signals, Queries, and Updates + +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use `signalWithStartWorkflow` to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). The handler receives an `AbortSignal` on +`ctx.abortSignal` that fires when the deadline is exceeded. Pass it to Temporal Client calls so they are canceled if +the timeout is reached. + +Updates are the exception. Do not wait for one inside the handler. Start it with `update` on a handle from +`getWorkflowHandle` and it backs the Operation. The handler returns straight away, and the Operation completes when the +Update does, however long it takes. + +The [nexus-messaging](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. + +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.client` rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: + +```ts +function workflowIdForUser(userId: string): string { + return `GreetingWorkflow_for_${userId}`; +} + +export const nexusGreetingServiceHandler = nexus.serviceHandler(nexusGreetingService, { + getLanguages: new temporalNexus.TemporalOperationHandler({ + async start(_ctx, client, input: GetLanguagesInput) { + const handle = client.client.workflow.getHandle(workflowIdForUser(input.userId)); + const result = await handle.query(getLanguagesQuery); + return temporalNexus.TemporalOperationResult.sync(result); + }, + }), +}); +``` + +There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/callerpattern) and [on-demand pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/ondemandpattern). +The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. + +### Develop an Asynchronous Nexus Operation handler to start a Workflow + +Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value +is delivered to the caller as the Operation's result. + +```ts +export const helloServiceHandler = nexus.serviceHandler(helloService, { + hello: new temporalNexus.TemporalOperationHandler({ + start: async (ctx, client, input) => + client.startWorkflow(helloWorkflow, { + args: [input], + + // Workflow IDs should typically be business-meaningful IDs and are used to dedupe workflow starts. + workflowId: `hello-${input.name}-${input.language}`, + + // Task queue defaults to the task queue this Operation is handled on. + }), + }), +}); +``` + +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. + +:::tip RESOURCES + +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. + +::: + +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, spread the pieces of the +input across the `args` array: + +```ts +client.startWorkflow(helloWorkflow, { + args: [input.name, input.language], + workflowId: `hello-${input.name}-${input.language}`, +}); +``` + +### Register a Nexus Service in a Worker + +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus +Service handler in a Worker. + + +[nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) +```ts +import { Worker, NativeConnection } from '@temporalio/worker'; +import { helloServiceHandler } from './handler'; + +// ... + const namespace = 'my-target-namespace'; + const serviceTaskQueue = 'my-handler-task-queue'; + const worker = await Worker.create({ + connection, + namespace, + taskQueue: serviceTaskQueue, + workflowsPath: require.resolve('./workflows'), + nexusServices: [helloServiceHandler], + }); +``` + + +## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} + +To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use +`@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. You will need to provide +the Nexus Endpoint name, which you registered previously in +[Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). + + + +[nexus-hello/src/caller/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/workflows.ts) + +```ts +import * as wf from "@temporalio/workflow"; +import { helloService, LanguageCode } from "../service/api"; + +const HELLO_SERVICE_ENDPOINT = "hello-service-endpoint-name"; + +export async function helloCallerWorkflow(name: string, language: LanguageCode): Promise { + const nexusClient = wf.createNexusServiceClient({ + service: helloService, + endpoint: HELLO_SERVICE_ENDPOINT, + }); + + const helloResult = await nexusClient.executeOperation( + "hello", + { name, language }, + { scheduleToCloseTimeout: "10s" } + ); + + return helloResult.message; +} +``` + + + +### Register the caller Workflow in a Worker and start the caller Workflow + +This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, +as usual. Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) +for reference. + +- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) + shows how to register the caller Workflow in a Worker and run the Worker. +- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) + shows how to use a Temporal Client to execute the sample caller Workflow. + + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} + +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. + +1. Run `npm run start.service` to start the Worker that will be serving the Nexus Operation handlers and its associated +Workflows. That Worker connects to the `my-target-namespace` namespace. + +2. In another shell, run `npm run start.caller` to start the Worker that will be serving the Caller Workflows. That +Worker connects to the `my-caller-namespace` namespace. + +3. In a third shell, `npm run workflow` to start an instance of the caller Workflows. + +Example output: + +```bash +Echo message: This message is from the client +Hello message: Hello, Temporal! +``` + +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} + +Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within +Cancellation Scopes. Requesting cancellation of a Cancellation Scope results in requesting cancellation for all +cancellable operations owned by that scope. The Workflow itself defines the root Cancellation Scope. Requesting +cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that +workflow, including Nexus Operations. + +To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new +Cancellation Scope, and start the Nexus Operation from within that scope. An example demonstrating this can be found at +our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). + +Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow +or other resources backing the operation may choose to ignore the cancellation request. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. + +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. + +## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} + +This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to +create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and +handler Workers to their respective Temporal Cloud Namespaces. + +### Install the latest `tcld` CLI and generate certificates + +To install the latest version of the `tcld` CLI, run the following command (on macOS): + +``` +brew install temporalio/brew/tcld +``` + +If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: + +``` +tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key +``` + +These certificates will be valid for one year. + +### Create caller and handler Namespaces + +Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. +If you already have these Namespaces, you don't need to do this. + +``` +tcld login + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 + +tcld namespace create \ + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 +``` + +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). + +### Create a Nexus Endpoint to route requests from caller to handler + +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. + +``` +tcld nexus endpoint create \ + --name \ + --target-task-queue my-handler-task-queue \ + --target-namespace \ + --allow-namespace \ + --description-file description.md +``` + +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. + +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +## Observability + +### Web UI + +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: + + + +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: + + + +### Temporal CLI + +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: + +``` +temporal workflow describe -w +``` + +Nexus events are included in the caller's Event history: + +``` +temporal workflow show -w +``` + +For **asynchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationStarted` +- `NexusOperationCompleted` + +For **synchronous Nexus Operations** the following are reported in the caller's history: + +- `NexusOperationScheduled` +- `NexusOperationCompleted` + +:::note + +`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. + +::: + +## Learn more + +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). +- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/sidebars.js b/sidebars.js index 095eec559c..3dc096ad4a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -106,6 +106,7 @@ const developDotnetCategory = { items: [ 'develop/dotnet/nexus/quickstart', 'develop/dotnet/nexus/feature-guide', + 'develop/dotnet/nexus/developer-experience', 'develop/dotnet/nexus/standalone-operations', ], }, @@ -258,6 +259,7 @@ const developGoCategory = { items: [ 'develop/go/nexus/quickstart', 'develop/go/nexus/feature-guide', + 'develop/go/nexus/developer-experience', 'develop/go/nexus/standalone-operations', ], }, @@ -665,6 +667,7 @@ const developPythonCategory = { items: [ 'develop/python/nexus/quickstart', 'develop/python/nexus/feature-guide', + 'develop/python/nexus/developer-experience', 'develop/python/nexus/standalone-operations', ], }, @@ -1077,6 +1080,7 @@ const developTypeScriptCategory = { items: [ 'develop/typescript/nexus/quickstart', 'develop/typescript/nexus/feature-guide', + 'develop/typescript/nexus/developer-experience', 'develop/typescript/nexus/standalone-operations', ], }, From 3b1dd2539f1e4950af995f8ae3d51765a9b016b8 Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Fri, 4 Sep 2026 12:13:09 -0500 Subject: [PATCH 04/10] fold dev experience content into feature guides. keep feature guide metadata --- .../dotnet/nexus/developer-experience.mdx | 549 -------------- docs/develop/dotnet/nexus/feature-guide.mdx | 407 +++++----- .../develop/go/nexus/developer-experience.mdx | 699 ------------------ docs/develop/go/nexus/feature-guide.mdx | 419 +++++------ .../java/nexus/developer-experience.mdx | 696 ----------------- docs/develop/java/nexus/feature-guide.mdx | 325 +++----- .../python/nexus/developer-experience.mdx | 482 ------------ docs/develop/python/nexus/feature-guide.mdx | 372 +++++----- .../typescript/nexus/developer-experience.mdx | 476 ------------ .../typescript/nexus/feature-guide.mdx | 408 +++++----- sidebars.js | 5 - 11 files changed, 824 insertions(+), 4014 deletions(-) delete mode 100644 docs/develop/dotnet/nexus/developer-experience.mdx delete mode 100644 docs/develop/go/nexus/developer-experience.mdx delete mode 100644 docs/develop/java/nexus/developer-experience.mdx delete mode 100644 docs/develop/python/nexus/developer-experience.mdx delete mode 100644 docs/develop/typescript/nexus/developer-experience.mdx diff --git a/docs/develop/dotnet/nexus/developer-experience.mdx b/docs/develop/dotnet/nexus/developer-experience.mdx deleted file mode 100644 index 44f2c88b4b..0000000000 --- a/docs/develop/dotnet/nexus/developer-experience.mdx +++ /dev/null @@ -1,549 +0,0 @@ ---- -id: developer-experience -slug: /develop/dotnet/nexus/developer-experience -title: Nexus Developer Experience - .NET SDK feature guide -sidebar_label: Nexus Developer Experience -description: Build a Nexus Service in .NET with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a single Service contract. -toc_max_heading_level: 4 -tags: - - Nexus - - .NET SDK ---- - -import { CaptionedImage } from '@site/src/components'; - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. - -:::tip - -New to Nexus? Start with the [Nexus .NET Quickstart](/develop/dotnet/nexus/quickstart). - -::: - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -:::note - -This documentation uses source code derived from the -[.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). - -::: - -## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} - -Prerequisites: - -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/dotnet/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) - (v1.3.0 or higher recommended) -- [Install the latest Temporal .NET SDK](https://learn.temporal.io/getting_started/dotnet/dev_environment/#install-the-temporal-net-sdk) - (v1.18.0 or higher recommended) - -The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace nexus-simple-handler-namespace -temporal operator namespace create --namespace nexus-simple-caller-namespace -``` - -`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in -`nexus-simple-caller-namespace` to call that Operation handler. We use different namespaces to demonstrate -cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name nexus-simple-endpoint \ - --target-namespace nexus-simple-handler-namespace \ - --target-task-queue nexus-simple-handler-sample -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {/* #define-nexus-service-contract */} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service package that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. - -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, -runtime validators, and the Service definition itself. - -This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements -the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler -and a Go caller share no code, but they both run off that same service contract - so they interoperate with no -coordination between the teams beyond the contract itself. - -The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates -identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the -`nexgen` README for the file format. - -## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. -Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). -Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. - -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method -`[TemporalOperation]` and the method body itself becomes the start handler, receiving three things: a -`TemporalOperationStartContext`, an `ITemporalNexusClient`, and the Operation input. The Operation the method handles is -matched by method name to the corresponding `[NexusOperation]` method on the Service interface. What you do with the -Client decides what backs the Operation: - -- **Synchronous.** Return `TemporalOperationResult.SyncResult(...)` and the Operation completes during the handler - call. The caller has its result as soon as the call returns. -- **Asynchronous.** Call `StartWorkflowAsync`, `StartActivityAsync`, or `StartWorkflowUpdateAsync` on the Client. The - handler returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, - which may be days later. Its result is delivered to the caller through the Nexus completion callback. This is what - lets an Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous -backing per invocation. - -### Develop a Synchronous Nexus Operation handler - -Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, -and the Operation completes during the call. - -Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole -call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -```csharp -using NexusRpc.Handlers; -using Temporalio.Nexus; - -[NexusServiceHandler(typeof(IHelloService))] -public class HelloService -{ - [TemporalOperation] - public Task> Echo( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - IHelloService.EchoInput input) => - Task.FromResult(TemporalOperationResult.SyncResult( - new(input.Message))); -} -``` - -### Use the Temporal Client for Signals, Queries, and Updates - -A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or -use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the -handler call, so they have to finish inside the -[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -Updates are the exception. Do not wait for one inside the handler. Start it with `StartWorkflowUpdateAsync` and it backs -the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it -takes. - -The [NexusMessaging](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging) -sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an -Operation with a Workflow Update. - -The Client your handler receives is not an ordinary Temporal Client. It propagates -[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the -caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client -through `client.TemporalClient` rather than constructing your own. - -In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs -the identifier it cares about: - -```csharp -private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}"; - -[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)); - return TemporalOperationResult.SyncResult(new()); -} -``` - -There are two examples of messaging through Nexus in the sample code: the [caller pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/CallerPattern) and the [on-demand pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/OnDemandPattern). -The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Call `StartWorkflowAsync` on the Client. The Operation completes when the Workflow returns, and the Workflow's return -value is delivered to the caller as the Operation's result. - -```csharp -[NexusServiceHandler(typeof(IHelloService))] -public class HelloService -{ - [TemporalOperation] - public Task> SayHello( - TemporalOperationStartContext ctx, - ITemporalNexusClient client, - IHelloService.HelloInput input) => - client.StartWorkflowAsync( - (HelloHandlerWorkflow wf) => wf.RunAsync(input), - // Workflow IDs should typically be business meaningful IDs and are used to dedupe - // workflow starts. Task queue defaults to the operation's task queue when omitted. - new() { Id = $"hello-{input.Name}-{input.Language}" }); -} -``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. - -::: - -#### Map a Nexus Operation input to multiple Workflow arguments - -A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them as separate -arguments in the `RunAsync` lambda: - -```csharp -client.StartWorkflowAsync( - (HelloHandlerWorkflow wf) => wf.RunAsync(input.Language, input.Name), - new() { Id = $"hello-{input.Name}-{input.Language}" }); -``` - -### Register a Nexus Service in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus -Service in a Worker. - -[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) - -```csharp -async Task RunHandlerWorkerAsync() -{ - // Run worker until cancelled - logger.LogInformation("Running handler worker"); - using var worker = new TemporalWorker( - await ConnectClientAsync("nexus-simple-handler-namespace"), - new TemporalWorkerOptions(taskQueue: "nexus-simple-handler-sample"). - AddNexusService(new HelloService()). - AddWorkflow()); - try - { - await worker.ExecuteAsync(tokenSource.Token); - } - catch (OperationCanceledException) - { - logger.LogInformation("Handler worker cancelled"); - } -} -``` - -Nexus Service handlers also support dependency injection through the generic-host Worker. See -[Use dependency injection with a Nexus Service handler](/develop/dotnet/nexus/feature-guide#dependency-injection). - -## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} - -Import the Service interface that has the necessary Operation names and input/output types to execute a Nexus Operation -from the caller Workflow: - -[NexusSimple/Caller/EchoCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs) - -```csharp -using Temporalio.Workflows; - -[Workflow] -public class EchoCallerWorkflow -{ - [WorkflowRun] - public async Task RunAsync(string message) - { - var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). - ExecuteNexusOperationAsync(svc => svc.Echo(new(message))); - return output.Message; - } -} -``` - -[NexusSimple/Caller/HelloCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs) - -```csharp -using Temporalio.Workflows; - -[Workflow] -public class HelloCallerWorkflow -{ - [WorkflowRun] - public async Task RunAsync(string name, IHelloService.HelloLanguage language) - { - var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). - ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language))); - return output.Message; - } -} -``` - -### Register the caller Workflow in a Worker - -After developing the caller Workflow, the next step is to register it with a Worker. - -[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) - -```csharp -async Task RunCallerWorkerAsync() -{ - // Run worker until cancelled - logger.LogInformation("Running caller worker"); - using var worker = new TemporalWorker( - await ConnectClientAsync("nexus-simple-caller-namespace"), - new TemporalWorkerOptions(taskQueue: "nexus-simple-caller-sample"). - AddWorkflow(). - AddWorkflow()); - try - { - await worker.ExecuteAsync(tokenSource.Token); - } - catch (OperationCanceledException) - { - logger.LogInformation("Caller worker cancelled"); - } -} -``` - -### Develop a starter to start the caller Workflow - -To initiate the caller Workflow, a starter program is used. - -[NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) - -```csharp -async Task ExecuteCallerWorkflowAsync() -{ - logger.LogInformation("Executing caller echo workflow"); - var client = await ConnectClientAsync("nexus-simple-caller-namespace"); - var result1 = await client.ExecuteWorkflowAsync( - (EchoCallerWorkflow wf) => wf.RunAsync("Nexus Echo πŸ‘‹"), - new(id: "nexus-simple-echo-id", taskQueue: "nexus-simple-caller-sample")); - logger.LogInformation("Workflow result: {Result}", result1); - - logger.LogInformation("Executing caller hello workflow"); - var result2 = await client.ExecuteWorkflowAsync( - (HelloCallerWorkflow wf) => wf.RunAsync("Temporal", IHelloService.HelloLanguage.Es), - new(id: "nexus-simple-hello-id", taskQueue: "nexus-simple-caller-sample")); - logger.LogInformation("Workflow result: {Result}", result2); -} -``` - -## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} - -Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. - -### Run Workers connected to a local development server - -Run the Nexus handler Worker: - -```bash -dotnet run handler-worker -``` - -In another terminal window, run the Nexus caller Worker: - -```bash -dotnet run caller-worker -``` - -### Start a caller Workflow - -With the Workers running, the final step in the local development process is to start a caller Workflow. - -Run the starter: - -```bash -dotnet run caller-workflow -``` - -This will show the two workflows started and their results. - -### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} - -To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only -asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or -other resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter -a terminal state. - -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller -reacts to cancellation: - -- `Abandon` - Do not request cancellation of the operation. -- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type - doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is - done. -- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was - received. Doesn't wait for actual cancellation. -- `WaitCancellationCompleted` - Wait for operation completion. Operation may or may not complete as cancelled. - -The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in -`NexusWorkflowOperationOptions` when starting an operation. - -Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet -been canceled, letting them run to completion. - -It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending -operations to deliver their cancellation requests before exiting the Workflow. - -See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for -reference. - -## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} - -This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to -create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and -handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on MacOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. - -``` -tcld nexus endpoint create \ - --name nexus-simple-endpoint \ - --target-task-queue nexus-simple-handler-sample \ - --target-namespace \ - --allow-namespace \ - --description-file endpoint_description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Event history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index a57d3e0491..11135fdbba 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -13,7 +13,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -21,6 +22,12 @@ New to Nexus? Start with the [Nexus .NET Quickstart](/develop/dotnet/nexus/quick ::: + +:::caution + +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler) and [Nexus Standalone Activity](/nexus/standalone-activity). These APIs are experimental and may change. +::: + This page shows how to do the following: - [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) @@ -34,7 +41,8 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the [.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). +This documentation uses source code derived from the +[.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). ::: @@ -42,8 +50,10 @@ This documentation uses source code derived from the [.NET Nexus sample](https:/ Prerequisites: -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/dotnet/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) (v1.3.0 or higher recommended) -- [Install the latest Temporal .NET SDK](https://learn.temporal.io/getting_started/dotnet/dev_environment/#install-the-temporal-net-sdk) (v1.9.0 or higher) +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/dotnet/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (v1.3.0 or higher recommended) +- [Install the latest Temporal .NET SDK](https://learn.temporal.io/getting_started/dotnet/dev_environment/#install-the-temporal-net-sdk) + (v1.18.0 or higher recommended) The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. @@ -51,9 +61,11 @@ The first step in working with Temporal Nexus involves starting a Temporal serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -64,8 +76,9 @@ temporal operator namespace create --namespace nexus-simple-handler-namespace temporal operator namespace create --namespace nexus-simple-caller-namespace ``` -`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `nexus-simple-caller-namespace` to call that Operation handler. -We use different namespaces to demonstrate cross-Namespace Nexus calls. +`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in +`nexus-simple-caller-namespace` to call that Operation handler. We use different namespaces to demonstrate +cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -84,114 +97,113 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. - -Each [Temporal SDK includes and uses a default Data Converter](/dataconversion). -The default data converter encodes payloads in the following order: Null, Byte array, Protobuf JSON, and JSON. -In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, Protobuf and JSON are common choices. -This example uses .NET classes serialized into JSON. - -[NexusSimple/IHelloService.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/IHelloService.cs) -```csharp -using NexusRpc; - -[NexusService] -public interface IHelloService -{ - static readonly string EndpointName = "nexus-simple-endpoint"; +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. - [NexusOperation] - EchoOutput Echo(EchoInput input); +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. - [NexusOperation] - HelloOutput SayHello(HelloInput input); +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. - public record EchoInput(string Message); - - public record EchoOutput(string Message); - - public record HelloInput(string Name, HelloLanguage Language); - - public record HelloOutput(string Message); - - public enum HelloLanguage - { - En, - Fr, - De, - Es, - Tr, - } -} -``` +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. -They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. -The `Temporalio.Nexus` namespace has utilities to help create Nexus Operations: +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method +`[TemporalOperation]` and the method body itself becomes the start handler, receiving three things: a +`TemporalOperationStartContext`, an `ITemporalNexusClient`, and the Operation input. The Operation the method handles is +matched by method name to the corresponding `[NexusOperation]` method on the Service interface. What you do with the +Client decides what backs the Operation: -- `NexusOperationExecutionContext.Current.TemporalClient` \- Get the Temporal Client that the Worker was initialized with for synchronous handlers backed by - Temporal primitives such as Signals and Queries -- `WorkflowRunOperationHandler.FromHandleFactory` \- Run a Workflow as an asynchronous Nexus Operation +- **Synchronous.** Return `TemporalOperationResult.SyncResult(...)` and the Operation completes during the handler + call. The caller has its result as soon as the call returns. +- **Asynchronous.** Call `StartWorkflowAsync`, `StartActivityAsync`, or `StartWorkflowUpdateAsync` on the Client. The + handler returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, + which may be days later. Its result is delivered to the caller through the Nexus completion callback. This is what + lets an Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -This example starts with a sync Operation handler example using the `OperationHandler.Sync` method, and then shows how to create an async Operation handler that uses `WorkflowRunOperationHandler.FromHandleFactory` to start a handler Workflow from a Nexus Operation. +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. ### Develop a Synchronous Nexus Operation handler -The `OperationHandler.Sync` method is for exposing simple RPC handlers. -Use `NexusOperationExecutionContext.Current.TemporalClient` to get the Temporal Client for signaling, querying, and listing Workflows. -Implementations can also make other calls, but handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking). +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -[NexusSimple/Handler/HelloService.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Handler/HelloService.cs) ```csharp using NexusRpc.Handlers; +using Temporalio.Nexus; [NexusServiceHandler(typeof(IHelloService))] public class HelloService { - [NexusOperationHandler] - public IOperationHandler Echo() => - // This Nexus service operation is a simple sync handler - OperationHandler.Sync( - (ctx, input) => new(input.Message)); - - // ... + [TemporalOperation] + public Task> Echo( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + IHelloService.EchoInput input) => + Task.FromResult(TemporalOperationResult.SyncResult( + new(input.Message))); } ``` ### Use the Temporal Client for Signals, Queries, and Updates -A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow. -You can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update. -All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). Updates should be short-lived to stay within this deadline. +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The [nexus_messaging](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging) sample shows how to create a Nexus Service that uses synchronous operations to send Updates and Queries: +Updates are the exception. Do not wait for one inside the handler. Start it with `StartWorkflowUpdateAsync` and it backs +the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it +takes. -Use `NexusOperationExecutionContext`, like below, to get the Client that the Worker was initialized with. In this example, the Workflow Id is derived from the client Id using the `WorkflowIdForUser` method. This converts a given client Id (in this case, the client is passing in a user Id) to generate a Workflow Id from it. -This way the client only needs the identifier it cares about. +The [NexusMessaging](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. -[NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/CallerPattern/Handler/NexusGreetingService.cs) +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.TemporalClient` rather than constructing your own. + +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: ```csharp private static string WorkflowIdForUser(string userId) => $"GreetingWorkflow_for_{userId}"; -[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)); - }); - ... +[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)); + return TemporalOperationResult.SyncResult(new()); +} ``` There are two examples of messaging through Nexus in the sample code: the [caller pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/CallerPattern) and the [on-demand pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusMessaging/OnDemandPattern). @@ -199,72 +211,54 @@ The caller pattern shows how to send messages to an existing Workflow, while the ### Develop an Asynchronous Nexus Operation handler to start a Workflow -Use the `WorkflowRunOperationHandler.FromHandleFactory` method, which is the easiest way to expose a Workflow as an operation. +Call `StartWorkflowAsync` on the Client. The Operation completes when the Workflow returns, and the Workflow's return +value is delivered to the caller as the Operation's result. -[NexusSimple/Handler/HelloService.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Handler/HelloService.cs) ```csharp - -using NexusRpc.Handlers; -using Temporalio.Nexus; - [NexusServiceHandler(typeof(IHelloService))] public class HelloService { - // ... - - [NexusOperationHandler] - public IOperationHandler SayHello() => - // This Nexus service operation is backed by a workflow run - WorkflowRunOperationHandler.FromHandleFactory( - (WorkflowRunOperationContext context, IHelloService.HelloInput input) => - context.StartWorkflowAsync( - (HelloHandlerWorkflow wf) => wf.RunAsync(input), - // Workflow IDs should typically be business meaningful IDs and are used to - // dedupe workflow starts. For this example, we're using the request ID - // allocated by Temporal when the caller workflow schedules the operation, - // this ID is guaranteed to be stable across retries of this operation. - new() { Id = context.HandlerContext.RequestId })); + [TemporalOperation] + public Task> SayHello( + TemporalOperationStartContext ctx, + ITemporalNexusClient client, + IHelloService.HelloInput input) => + client.StartWorkflowAsync( + (HelloHandlerWorkflow wf) => wf.RunAsync(input), + // Workflow IDs should typically be business meaningful IDs and are used to dedupe + // workflow starts. Task queue defaults to the operation's task queue when omitted. + new() { Id = $"hello-{input.Name}-{input.Language}" }); } ``` -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. ::: #### Map a Nexus Operation input to multiple Workflow arguments -A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes multiple arguments, simply pass in different arguments using `RunAsync`. +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them as separate +arguments in the `RunAsync` lambda: -[NexusMultiArg/Handler/HelloService.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusMultiArg/Handler/HelloService.cs) ```csharp -[NexusServiceHandler(typeof(IHelloService))] -public class HelloService -{ - [NexusOperationHandler] - public IOperationHandler SayHello() => - // This Nexus service operation is backed by a workflow run. For this sample, we are - // altering the parameters to the workflow (in this case expanding to two parameters). - WorkflowRunOperationHandler.FromHandleFactory( - (WorkflowRunOperationContext context, IHelloService.HelloInput input) => - context.StartWorkflowAsync( - (HelloHandlerWorkflow wf) => wf.RunAsync(input.Language, input.Name), - // Workflow IDs should typically be business meaningful IDs and are used to - // dedupe workflow starts. For this example, we're using the request ID - // allocated by Temporal when the caller workflow schedules the operation, - // this ID is guaranteed to be stable across retries of this operation. - new() { Id = context.HandlerContext.RequestId })); -} +client.StartWorkflowAsync( + (HelloHandlerWorkflow wf) => wf.RunAsync(input.Language, input.Name), + new() { Id = $"hello-{input.Name}-{input.Language}" }); ``` ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus Service in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus +Service in a Worker. [NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + ```csharp async Task RunHandlerWorkerAsync() { @@ -286,55 +280,16 @@ async Task RunHandlerWorkerAsync() } ``` -### Use dependency injection with a Nexus Service handler {/* #dependency-injection */} - -Nexus Service handlers support dependency injection through the [Temporalio.Extensions.Hosting](https://github.com/temporalio/sdk-dotnet/tree/main/src/Temporalio.Extensions.Hosting) generic-host Worker. -Register the handler on the Worker with `AddScopedNexusService`, and the container injects the handler's constructor dependencies. -Use `AddSingletonNexusService` or `AddTransientNexusService` for singleton or transient lifetimes instead, mirroring `AddScopedActivities` / `AddSingletonActivities` / `AddTransientActivities`. - -For a complete, runnable example, see the [NexusDependencyInjection sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusDependencyInjection). - -[NexusDependencyInjection/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusDependencyInjection/Program.cs) -```csharp -IHost host = Host.CreateDefaultBuilder(args) - .ConfigureServices(ctx => - ctx. - // Add the dependency that will be injected into the Nexus Service handler - AddScoped(). - // Add the worker - AddHostedTemporalWorker(handlerTaskQueue). - ConfigureOptions(options => options.ClientOptions = LoadConnectOptions()). - // Add the Nexus Service handler at the scoped level - AddScopedNexusService()) - .Build(); -await host.RunAsync(); -``` - -The handler receives its dependencies through its constructor. -The container creates a new scoped handler instance and its scoped dependencies for each Operation invocation; it does not cache them between invocations: - -[NexusDependencyInjection/Handler/GreetingServiceHandler.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusDependencyInjection/Handler/GreetingServiceHandler.cs) -```csharp -[NexusServiceHandler(typeof(IGreetingService))] -public class GreetingServiceHandler -{ - private readonly IGreetingClient greetingClient; - - // The dependency is injected by the container - public GreetingServiceHandler(IGreetingClient greetingClient) => this.greetingClient = greetingClient; - - [NexusOperationHandler] - public IOperationHandler SayHello() => - OperationHandler.Sync( - (ctx, input) => greetingClient.GetGreetingAsync(input.Name)); -} -``` +Nexus Service handlers also support dependency injection through the generic-host Worker. See +[Use dependency injection with a Nexus Service handler](/develop/dotnet/nexus/feature-guide#dependency-injection). ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -Import the Service API package that has the necessary service and operation names and input/output types to execute a Nexus Operation from the caller Workflow: +Import the Service interface that has the necessary Operation names and input/output types to execute a Nexus Operation +from the caller Workflow: [NexusSimple/Caller/EchoCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs) + ```csharp using Temporalio.Workflows; @@ -352,6 +307,7 @@ public class EchoCallerWorkflow ``` [NexusSimple/Caller/HelloCallerWorkflow.workflow.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs) + ```csharp using Temporalio.Workflows; @@ -368,56 +324,12 @@ public class HelloCallerWorkflow } ``` -### Set Nexus Operation timeouts - -Nexus Operations support [three types of timeouts](/nexus/operations#timeouts) that control how long the caller is willing to wait at different stages of the Operation lifecycle. -Set these timeouts in `NexusWorkflowOperationOptions` when calling `ExecuteNexusOperationAsync`. - -#### Schedule-to-Close timeout - -The [Schedule-to-Close timeout](/nexus/operations#schedule-to-close-timeout) limits the total duration of the Operation from when it is scheduled to when it completes. -The Nexus Machinery automatically retries failed requests until this timeout is exceeded. - -```csharp -var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). - ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language)), new NexusWorkflowOperationOptions - { - ScheduleToCloseTimeout = TimeSpan.FromMinutes(10), - }); -``` - -#### Schedule-to-Start timeout - -The [Schedule-to-Start timeout](/nexus/operations#schedule-to-start-timeout) limits how long the caller will wait for the Operation to be started by the handler. -If not set, no Schedule-to-Start timeout is enforced. - -```csharp -var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). - ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language)), new NexusWorkflowOperationOptions - { - ScheduleToStartTimeout = TimeSpan.FromMinutes(2), - }); -``` - -#### Start-to-Close timeout - -The [Start-to-Close timeout](/nexus/operations#start-to-close-timeout) limits how long the caller will wait for an asynchronous Operation to complete after it has been started. -This timeout only applies to asynchronous Operations. -If not set, no Start-to-Close timeout is enforced. - -```csharp -var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). - ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language)), new NexusWorkflowOperationOptions - { - StartToCloseTimeout = TimeSpan.FromMinutes(5), - }); -``` - ### Register the caller Workflow in a Worker After developing the caller Workflow, the next step is to register it with a Worker. [NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + ```csharp async Task RunCallerWorkerAsync() { @@ -444,6 +356,7 @@ async Task RunCallerWorkerAsync() To initiate the caller Workflow, a starter program is used. [NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) + ```csharp async Task ExecuteCallerWorkflowAsync() { @@ -494,24 +407,33 @@ This will show the two workflows started and their results. ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. -The Workflow or other resources backing the operation may choose to ignore the cancellation request. -If ignored, the operation may enter a terminal state. +To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only +asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or +other resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter +a terminal state. -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller reacts to cancellation: +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller +reacts to cancellation: - `Abandon` - Do not request cancellation of the operation. -- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is done. -- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was received. Doesn't wait for actual cancellation. +- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type + doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is + done. +- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was + received. Doesn't wait for actual cancellation. - `WaitCancellationCompleted` - Wait for operation completion. Operation may or may not complete as cancelled. -The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in `NexusWorkflowOperationOptions` when starting an operation. +The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in +`NexusWorkflowOperationOptions` when starting an operation. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. -Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. -It's okay to leave operations running in some use cases. -To ensure cancellations are delivered, wait for all pending operations to finish before exiting the Workflow. +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. -See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for reference. +See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for +reference. ## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} @@ -579,11 +501,13 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. @@ -610,31 +534,30 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: ``` temporal workflow describe -w @@ -665,6 +588,8 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/go/nexus/developer-experience.mdx b/docs/develop/go/nexus/developer-experience.mdx deleted file mode 100644 index ec4e9c3a90..0000000000 --- a/docs/develop/go/nexus/developer-experience.mdx +++ /dev/null @@ -1,699 +0,0 @@ ---- -id: developer-experience -slug: /develop/go/nexus/developer-experience -title: Nexus Developer Experience - Go SDK feature guide -sidebar_label: Nexus Developer Experience -description: Build a Nexus Service in Go with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. -toc_max_heading_level: 4 -tags: - - Nexus - - Go SDK ---- - -import { CaptionedImage } from '@site/src/components'; - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. - -:::tip - -New to Nexus? Start with the [Nexus Go Quickstart](/develop/go/nexus/quickstart). - -::: - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -:::note - -This documentation uses source code derived from the -[Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). - -::: - -## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} - -Prerequisites: - -- [Install the latest Temporal CLI](/develop/run-a-development-server) (v1.3.0 or higher recommended) -- [Install the latest Temporal Go SDK](/develop/go/set-up-your-local-go) (v1.48.0 or higher recommended) - -The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to -call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {/* #define-nexus-service-contract */} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service package that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. - -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, -runtime validators, and the Service definition itself. - -This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements -the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler -and a Go caller share no code, but they both run off that same service contract - so they interoperate with no -coordination between the teams beyond the contract itself. - -The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates -identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the -`nexgen` README for the file format. - -## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. -Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). -Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. - -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the -Operation input. What you do with the Client decides what backs the Operation: - -- **Synchronous.** Return `temporalnexus.NewSyncResult(...)` and the Operation completes during the handler call. The - caller has its result as soon as the call returns. -- **Asynchronous.** Call `temporalnexus.StartWorkflow`, `temporalnexus.StartActivity`, or - `temporalnexus.StartUpdateWorkflow` with the Client. The handler returns as soon as that Execution has started, and - the Operation stays open until the Execution finishes, which may be days later. Its result is delivered to the caller - through the Nexus completion callback. This is what lets an Operation outlive the - [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous -backing per invocation. - -### Develop a Synchronous Nexus Operation handler - -Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, -and the Operation completes during the call. - -Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole -call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -```go -var EchoOperation = temporalnexus.MustNewTemporalOperation( - temporalnexus.TemporalOperationOptions[service.EchoInput, service.EchoOutput]{ - Name: service.EchoOperationName, - Start: func( - ctx context.Context, - nc temporalnexus.NexusClient, - input service.EchoInput, - options temporalnexus.StartTemporalOperationOptions, - ) (temporalnexus.TemporalOperationResult[service.EchoOutput], error) { - return temporalnexus.NewSyncResult(service.EchoOutput(input)), nil - }, - }) -``` - -### Use the Temporal Client for Signals, Queries, and Updates - -A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or -use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the -handler call, so they have to finish inside the -[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -Updates are the exception. Do not wait for one inside the handler. Start it with `temporalnexus.StartUpdateWorkflow` and -it backs the Operation. The handler returns straight away, and the Operation completes when the Update does, however -long it takes. - -The [nexus-messaging](https://github.com/temporalio/samples-go/tree/main/nexus-messaging) -sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an -Operation with a Workflow Update. - -The Client your handler receives is not an ordinary Temporal Client. It propagates -[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the -caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client -through `nc.GetWorkflowClient()` rather than constructing your own. - -In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs -the identifier it cares about: - -```go -var ApproveOperation = temporalnexus.MustNewTemporalOperation( - temporalnexus.TemporalOperationOptions[service.ApproveInput, service.ApproveOutput]{ - Name: service.ApproveOperationName, - Start: func( - ctx context.Context, - nc temporalnexus.NexusClient, - input service.ApproveInput, - options temporalnexus.StartTemporalOperationOptions, - ) (temporalnexus.TemporalOperationResult[service.ApproveOutput], error) { - err := nc.GetWorkflowClient().SignalWorkflow( - ctx, GetWorkflowID(input.UserID), "", service.ApproveSignalName, input) - if err != nil { - return temporalnexus.TemporalOperationResult[service.ApproveOutput]{}, err - } - return temporalnexus.NewSyncResult(service.ApproveOutput{}), nil - }, - }) -``` - -There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/ondemandpattern/). -The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Call `temporalnexus.StartWorkflow` with the Client. The Operation completes when the Workflow returns, and the -Workflow's return value is delivered to the caller as the Operation's result. - -```go -var HelloOperation = temporalnexus.MustNewTemporalOperation( - temporalnexus.TemporalOperationOptions[service.HelloInput, service.HelloOutput]{ - Name: service.HelloOperationName, - Start: func( - ctx context.Context, - nc temporalnexus.NexusClient, - input service.HelloInput, - options temporalnexus.StartTemporalOperationOptions, - ) (temporalnexus.TemporalOperationResult[service.HelloOutput], error) { - return temporalnexus.StartWorkflow(ctx, nc, client.StartWorkflowOptions{ - ID: service.HelloWorkflowID(input), - // Task queue defaults to the task queue this operation is handled on. - }, HelloHandlerWorkflow, input) - }, - }) -``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. - -::: - -#### Map a Nexus Operation input to multiple Workflow arguments - -A Nexus Operation can only take one input parameter. `temporalnexus.StartWorkflow` is typed for a Workflow that takes a -single argument, so to start a Workflow that takes several, use `temporalnexus.StartUntypedWorkflow` and pass the -arguments after the Workflow function: - -```go -return temporalnexus.StartUntypedWorkflow[service.HelloOutput](ctx, nc, client.StartWorkflowOptions{ - ID: service.HelloWorkflowID(input), -}, HelloHandlerWorkflow, input.Name, input.Language) -``` - -### Register a Nexus Service in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus -Service in a Worker. - - -[nexus/handler/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/worker/main.go) -```go -package main - -import ( - "log" - "os" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - - "github.com/nexus-rpc/sdk-go/nexus" - "github.com/temporalio/samples-go/nexus/handler" - "github.com/temporalio/samples-go/nexus/options" - "github.com/temporalio/samples-go/nexus/service" -) - -const ( - taskQueue = "my-handler-task-queue" -) - -func main() { - // The client and worker are heavyweight objects that should be created once per process. - clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) - if err != nil { - log.Fatalf("Invalid arguments: %v", err) - } - c, err := client.Dial(clientOptions) - if err != nil { - log.Fatalln("Unable to create client", err) - } - defer c.Close() - - w := worker.New(c, taskQueue, worker.Options{}) - service := nexus.NewService(service.HelloServiceName) - err = service.Register(handler.EchoOperation, handler.HelloOperation) - if err != nil { - log.Fatalln("Unable to register operations", err) - } - w.RegisterNexusService(service) - w.RegisterWorkflow(handler.HelloHandlerWorkflow) - - err = w.Run(worker.InterruptCh()) - if err != nil { - log.Fatalln("Unable to start worker", err) - } -} -``` - - -## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} - -Import the Service API package that has the necessary service and operation names and input/output types to execute a -Nexus Operation from the caller Workflow: - - -[nexus/caller/workflows.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/workflows.go) -```go -package caller - -import ( - "github.com/temporalio/samples-go/nexus/service" - "go.temporal.io/sdk/workflow" -) - -const ( - TaskQueue = "my-caller-workflow-task-queue" - endpointName = "my-nexus-endpoint-name" -) - -func EchoCallerWorkflow(ctx workflow.Context, message string) (string, error) { - c := workflow.NewNexusClient(endpointName, service.HelloServiceName) - - fut := c.ExecuteOperation(ctx, service.EchoOperationName, service.EchoInput{Message: message}, workflow.NexusOperationOptions{}) - - var res service.EchoOutput - if err := fut.Get(ctx, &res); err != nil { - return "", err - } - - return res.Message, nil -} - -func HelloCallerWorkflow(ctx workflow.Context, name string, language service.Language) (string, error) { - c := workflow.NewNexusClient(endpointName, service.HelloServiceName) - - fut := c.ExecuteOperation(ctx, service.HelloOperationName, service.HelloInput{Name: name, Language: language}, workflow.NexusOperationOptions{}) - var res service.HelloOutput - - // Optionally wait for the operation to be started. NexusOperationExecution will contain the operation token in - // case this operation is asynchronous, which is a handle that can be used to perform additional actions like - // cancelling an operation. - var exec workflow.NexusOperationExecution - if err := fut.GetNexusOperationExecution().Get(ctx, &exec); err != nil { - return "", err - } - if err := fut.Get(ctx, &res); err != nil { - return "", err - } - - return res.Message, nil -} - -``` - - -### Register the caller Workflow in a Worker - -After developing the caller Workflow, the next step is to register it with a Worker. - - -[nexus/caller/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/worker/main.go) -```go -package main - -import ( - "log" - "os" - - "github.com/temporalio/samples-go/nexus/caller" - "github.com/temporalio/samples-go/nexus/options" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" -) - -func main() { - // The client and worker are heavyweight objects that should be created once per process. - clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) - if err != nil { - log.Fatalf("Invalid arguments: %v", err) - } - c, err := client.Dial(clientOptions) - if err != nil { - log.Fatalln("Unable to create client", err) - } - defer c.Close() - - w := worker.New(c, caller.TaskQueue, worker.Options{}) - - w.RegisterWorkflow(caller.EchoCallerWorkflow) - w.RegisterWorkflow(caller.HelloCallerWorkflow) - - err = w.Run(worker.InterruptCh()) - if err != nil { - log.Fatalln("Unable to start worker", err) - } -} -``` - - -### Develop a starter to start the caller Workflow - -To initiate the caller Workflow, a starter program is used. - - -[nexus/caller/starter/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/starter/main.go) -```go -package main - -import ( - "context" - "log" - "os" - "time" - - "go.temporal.io/sdk/client" - - "github.com/temporalio/samples-go/nexus/caller" - "github.com/temporalio/samples-go/nexus/options" - "github.com/temporalio/samples-go/nexus/service" -) - -func main() { - clientOptions, err := options.ParseClientOptionFlags(os.Args[1:]) - if err != nil { - log.Fatalf("Invalid arguments: %v", err) - } - c, err := client.Dial(clientOptions) - if err != nil { - log.Fatalln("Unable to create client", err) - } - defer c.Close() - runWorkflow(c, caller.EchoCallerWorkflow, "Nexus Echo πŸ‘‹") - runWorkflow(c, caller.HelloCallerWorkflow, "Nexus", service.ES) -} - -func runWorkflow(c client.Client, workflow interface{}, args ...interface{}) { - ctx := context.Background() - workflowOptions := client.StartWorkflowOptions{ - ID: "nexus_hello_caller_workflow_" + time.Now().Format("20060102150405"), - TaskQueue: caller.TaskQueue, - } - - wr, err := c.ExecuteWorkflow(ctx, workflowOptions, workflow, args...) - if err != nil { - log.Fatalln("Unable to execute workflow", err) - } - log.Println("Started workflow", "WorkflowID", wr.GetID(), "RunID", wr.GetRunID()) - - // Synchronously wait for the workflow completion. - var result string - err = wr.Get(context.Background(), &result) - if err != nil { - log.Fatalln("Unable get workflow result", err) - } - log.Println("Workflow result:", result) -} -``` - - -## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} - -Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. - -### Run Workers connected to a local development server - -Run the Nexus handler Worker: - -```bash -cd handler -go run ./worker \ - -target-host localhost:7233 \ - -namespace my-target-namespace -``` - -In another terminal window, run the Nexus caller Worker: - -```bash -cd caller -go run ./worker \ - -target-host localhost:7233 \ - -namespace my-caller-namespace -``` - -### Start a caller Workflow - -With the Workers running, the final step in the local development process is to start a caller Workflow. - -Run the starter: - -```bash -cd caller -go run ./starter \ - -target-host localhost:7233 \ - -namespace my-caller-namespace -``` - -This will result in: - -``` -2024/10/04 19:57:40 Workflow result: Nexus Echo πŸ‘‹ -2024/10/04 19:57:40 Started workflow WorkflowID nexus_hello_caller_workflow_20240723195740 RunID c9789128-2fcd-4083-829d-95e43279f6d7 -2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ -``` - -### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} - -To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. This -returns a new context and a function that, when called, cancels the context and any SDK method that was passed this -context. The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it -succeeds, fails, times out, or is canceled. - -Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or -other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter -a terminal state. - -Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet -been canceled, letting them run to completion. - -It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending -operations to deliver their cancellation requests before exiting the Workflow. - -See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) -for reference. - -## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} - -This section assumes you are already familiar with -[how to connect a Worker to Temporal Cloud](/develop/go/client/temporal-client#connect-to-temporal-cloud). The same -[source code](https://github.com/temporalio/samples-go/tree/main/nexus) is used in this section, but the `tcld` CLI will -be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the -caller and handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on MacOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. - -``` -tcld nexus endpoint create \ - --name \ - --target-task-queue my-handler-task-queue \ - --target-namespace \ - --allow-namespace \ - --description-file ./nexus/service/description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -### Run Workers connected to Temporal Cloud - -Run the handler Worker: - -``` -cd handler - -go run ./worker \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key' -``` - -Run the caller Worker: - -``` -cd caller - -go run ./worker \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key' -``` - -To connect with an API key instead of mTLS certificates, replace `-client-cert` and `-client-key` with -`-api-key `. - -### Start a caller Workflow in Temporal Cloud - -``` -cd caller - -go run ./starter \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key' -``` - -This will result in: - -``` -2024/10/04 19:57:40 Workflow result: Nexus Echo πŸ‘‹ -2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ -``` - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Event history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index 66365ed25a..e2ed3356d4 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -15,7 +15,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -23,6 +24,13 @@ New to Nexus? Start with the [Nexus Go Quickstart](/develop/go/nexus/quickstart) ::: + +:::caution + +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. + +::: + This page shows how to do the following: - [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) @@ -36,7 +44,8 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the [Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). +This documentation uses source code derived from the +[Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). ::: @@ -45,8 +54,7 @@ This documentation uses source code derived from the [Go Nexus sample](https://g Prerequisites: - [Install the latest Temporal CLI](/develop/run-a-development-server) (v1.3.0 or higher recommended) -- [Install the latest Temporal Go SDK](/develop/go/set-up-your-local-go) - (v1.33.0 or higher recommended) +- [Install the latest Temporal Go SDK](/develop/go/set-up-your-local-go) (v1.48.0 or higher recommended) The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. @@ -54,9 +62,11 @@ The first step in working with Temporal Nexus involves starting a Temporal serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -67,8 +77,8 @@ temporal operator namespace create --namespace my-target-namespace temporal operator namespace create --namespace my-caller-namespace ``` -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to call that Operation handler. -We use different namespaces to demonstrate cross-Namespace Nexus calls. +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -87,107 +97,113 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. +In this example, there is a service package that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. -Each [Temporal SDK includes and uses a default Data Converter](/dataconversion). -The default data converter encodes payloads in the following order: Null, Byte array, Protobuf JSON, and JSON. -In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, Protobuf and JSON are common choices. -This example uses native Go types. +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. - -[nexus/service/api.go](https://github.com/temporalio/samples-go/blob/main/nexus/service/api.go) -```go -// ... -const HelloServiceName = "my-hello-service" +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. -// Echo operation -const EchoOperationName = "echo" - -type EchoInput struct { - Message string -} - -type EchoOutput EchoInput - -``` - +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. -They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. -The `temporalnexus` package has builders to create Nexus Operations and other helpers for authoring Operation handlers: +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the +Operation input. What you do with the Client decides what backs the Operation: -- `NewWorkflowRunOperation` \- Run a Workflow as an asynchronous Nexus Operation -- `GetClient` \- Get the Temporal Client that the Worker was initialized with for synchronous handlers backed by - Temporal primitives such as Signals and Queries +- **Synchronous.** Return `temporalnexus.NewSyncResult(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `temporalnexus.StartWorkflow`, `temporalnexus.StartActivity`, or + `temporalnexus.StartUpdateWorkflow` with the Client. The handler returns as soon as that Execution has started, and + the Operation stays open until the Execution finishes, which may be days later. Its result is delivered to the caller + through the Nexus completion callback. This is what lets an Operation outlive the + [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -This tutorial starts with a sync Operation handler example using the `nexus.NewSyncOperation` method, and then shows how to create an async Operation handler that uses `NewWorkflowRunOperation` to start a handler Workflow from a Nexus Operation. +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. ### Develop a Synchronous Nexus Operation handler -The `nexus.NewSyncOperation` builder function is for exposing simple RPC handlers. -Use `temporalnexus.GetClient(ctx)` to get the Temporal Client for signaling, querying, and listing Workflows. -Implementations can also make other calls, but handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking). - - -[nexus/handler/app.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/app.go) -```go -// ... - -import ( - "context" - "fmt" - - "github.com/nexus-rpc/sdk-go/nexus" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/temporalnexus" - "go.temporal.io/sdk/workflow" - - "github.com/temporalio/samples-go/nexus/service" -) +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. -// NewSyncOperation is a meant for exposing simple RPC handlers. -var EchoOperation = nexus.NewSyncOperation(service.EchoOperationName, func(ctx context.Context, input service.EchoInput, options nexus.StartOperationOptions) (service.EchoOutput, error) { - // Use temporalnexus.GetClient to get the client that the worker was initialized with to perform client calls - // such as signaling, querying, and listing workflows. Implementations are free to make arbitrary calls to other - // services or databases, or perform simple computations such as this one. - return service.EchoOutput(input), nil -}) +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). +```go +var EchoOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.EchoInput, service.EchoOutput]{ + Name: service.EchoOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.EchoInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.EchoOutput], error) { + return temporalnexus.NewSyncResult(service.EchoOutput(input)), nil + }, + }) ``` - ### Use the Temporal Client for Signals, Queries, and Updates -A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow. -You can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update. -All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The ctx provided to the handler is automatically set with this deadline, so passing it directly to Temporal Client calls will correctly propagate the timeout. -Updates should be short-lived to stay within this deadline. +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The [nexus_messaging](https://github.com/temporalio/samples-go/tree/main/nexus-messaging) sample shows how to create a Nexus Service that uses synchronous operations to send Updates and Queries. +Updates are the exception. Do not wait for one inside the handler. Start it with `temporalnexus.StartUpdateWorkflow` and +it backs the Operation. The handler returns straight away, and the Operation completes when the Update does, however +long it takes. -Use the Nexus library, as shown below, to get the Client that the Worker was initialized with. In this example, the Workflow Id is derived from the client Id, with the `GetWorkflowID` method. This converts a given client Id (in this case, the client is passing in a user Id) to generate a Workflow Id from it. -This way the client only needs the identifier it cares about. +The [nexus-messaging](https://github.com/temporalio/samples-go/tree/main/nexus-messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. -[nexus-messaging/callerpattern/handler/app.go](https://github.com/temporalio/samples-go/blob/main/nexus-messaging/callerpattern/handler/app.go) +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `nc.GetWorkflowClient()` rather than constructing your own. -```go -func GetWorkflowID(userID string) string { - return WorkflowIDPrefix + userID -} +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: -var GetLanguagesOperation = nexus.NewSyncOperation(service.GetLanguagesOperationName, func(ctx context.Context, input service.GetLanguagesInput, options nexus.StartOperationOptions) (service.GetLanguagesOutput, error) { - c := temporalnexus.GetClient(ctx) - workflowID := GetWorkflowID(input.UserID) - ... +```go +var ApproveOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.ApproveInput, service.ApproveOutput]{ + Name: service.ApproveOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.ApproveInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.ApproveOutput], error) { + err := nc.GetWorkflowClient().SignalWorkflow( + ctx, GetWorkflowID(input.UserID), "", service.ApproveSignalName, input) + if err != nil { + return temporalnexus.TemporalOperationResult[service.ApproveOutput]{}, err + } + return temporalnexus.NewSyncResult(service.ApproveOutput{}), nil + }, + }) ``` There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-go/tree/main/nexus-messaging/ondemandpattern/). @@ -195,68 +211,53 @@ The caller pattern shows how to send messages to an existing Workflow, while the ### Develop an Asynchronous Nexus Operation handler to start a Workflow -Use the `NewWorkflowRunOperation` constructor, which is the easiest way to expose a Workflow as an operation. -See alternatives [here](https://pkg.go.dev/go.temporal.io/sdk/temporalnexus). +Call `temporalnexus.StartWorkflow` with the Client. The Operation completes when the Workflow returns, and the +Workflow's return value is delivered to the caller as the Operation's result. - -[nexus/handler/app.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/app.go) ```go -// ... -var HelloOperation = temporalnexus.NewWorkflowRunOperation(service.HelloOperationName, HelloHandlerWorkflow, func(ctx context.Context, input service.HelloInput, options nexus.StartOperationOptions) (client.StartWorkflowOptions, error) { - return client.StartWorkflowOptions{ - // Workflow IDs should typically be business meaningful IDs and are used to dedupe workflow starts. - // For this example, use a business ID derived from the greeting input so repeated operations - // for the same name and language resolve to the same workflow. - ID: service.HelloWorkflowID(input), - // Task queue defaults to the task queue this operation is handled on. - }, nil -}) - +var HelloOperation = temporalnexus.MustNewTemporalOperation( + temporalnexus.TemporalOperationOptions[service.HelloInput, service.HelloOutput]{ + Name: service.HelloOperationName, + Start: func( + ctx context.Context, + nc temporalnexus.NexusClient, + input service.HelloInput, + options temporalnexus.StartTemporalOperationOptions, + ) (temporalnexus.TemporalOperationResult[service.HelloOutput], error) { + return temporalnexus.StartWorkflow(ctx, nc, client.StartWorkflowOptions{ + ID: service.HelloWorkflowID(input), + // Task queue defaults to the task queue this operation is handled on. + }, HelloHandlerWorkflow, input) + }, + }) ``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. -For the `HelloOperation`, `input.ID` is passed as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. ::: #### Map a Nexus Operation input to multiple Workflow arguments -A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes multiple arguments use -`NewWorkflowRunOperationWithOptions` or `MustNewWorkflowRunOperationWithOptions`. +A Nexus Operation can only take one input parameter. `temporalnexus.StartWorkflow` is typed for a Workflow that takes a +single argument, so to start a Workflow that takes several, use `temporalnexus.StartUntypedWorkflow` and pass the +arguments after the Workflow function: - -[nexus-multiple-arguments/handler/app.go](https://github.com/temporalio/samples-go/blob/main/nexus-multiple-arguments/handler/app.go) ```go -var HelloOperation = temporalnexus.MustNewWorkflowRunOperationWithOptions(temporalnexus.WorkflowRunOperationOptions[service.HelloInput, service.HelloOutput]{ - Name: service.HelloOperationName, - Handler: func(ctx context.Context, input service.HelloInput, options nexus.StartOperationOptions) (temporalnexus.WorkflowHandle[service.HelloOutput], error) { - return temporalnexus.ExecuteUntypedWorkflow[service.HelloOutput]( - ctx, - options, - client.StartWorkflowOptions{ - // Workflow IDs should typically be business meaningful IDs and are used to dedupe workflow starts. - // For this example, use a business ID derived from the greeting input so repeated operations - // for the same name and language resolve to the same workflow. - ID: service.HelloWorkflowID(input), - }, - HelloHandlerWorkflow, - input.Name, - input.Language, - ) - }, -}) - +return temporalnexus.StartUntypedWorkflow[service.HelloOutput](ctx, nc, client.StartWorkflowOptions{ + ID: service.HelloWorkflowID(input), +}, HelloHandlerWorkflow, input.Name, input.Language) ``` - ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus Service in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus +Service in a Worker. [nexus/handler/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/worker/main.go) @@ -311,7 +312,8 @@ func main() { ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -Import the Service API package that has the necessary service and operation names and input/output types to execute a Nexus Operation from the caller Workflow: +Import the Service API package that has the necessary service and operation names and input/output types to execute a +Nexus Operation from the caller Workflow: [nexus/caller/workflows.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/workflows.go) @@ -364,45 +366,6 @@ func HelloCallerWorkflow(ctx workflow.Context, name string, language service.Lan ``` -### Set Nexus Operation timeouts - -Nexus Operations support [three types of timeouts](/nexus/operations#timeouts) that control how long the caller is willing to wait at different stages of the Operation lifecycle. -Set these timeouts in `NexusOperationOptions` when calling `ExecuteOperation`. - -#### Schedule-to-Close timeout - -The [Schedule-to-Close timeout](/nexus/operations#schedule-to-close-timeout) limits the total duration of the Operation from when it is scheduled to when it completes. -The Nexus Machinery automatically retries failed requests until this timeout is exceeded. - -```go -fut := c.ExecuteOperation(ctx, service.HelloOperationName, service.HelloInput{Name: name, Language: language}, workflow.NexusOperationOptions{ - ScheduleToCloseTimeout: 10 * time.Minute, -}) -``` - -#### Schedule-to-Start timeout - -The [Schedule-to-Start timeout](/nexus/operations#schedule-to-start-timeout) limits how long the caller will wait for the Operation to be started by the handler. -If not set, no Schedule-to-Start timeout is enforced. - -```go -fut := c.ExecuteOperation(ctx, service.HelloOperationName, service.HelloInput{Name: name, Language: language}, workflow.NexusOperationOptions{ - ScheduleToStartTimeout: 2 * time.Minute, -}) -``` - -#### Start-to-Close timeout - -The [Start-to-Close timeout](/nexus/operations#start-to-close-timeout) limits how long the caller will wait for an asynchronous Operation to complete after it has been started. -This timeout only applies to asynchronous Operations. -If not set, no Start-to-Close timeout is enforced. - -```go -fut := c.ExecuteOperation(ctx, service.HelloOperationName, service.HelloInput{Name: name, Language: language}, workflow.NexusOperationOptions{ - StartToCloseTimeout: 5 * time.Minute, -}) -``` - ### Register the caller Workflow in a Worker After developing the caller Workflow, the next step is to register it with a Worker. @@ -450,7 +413,7 @@ func main() { ### Develop a starter to start the caller Workflow -To initiate the caller Workflow, a starter program is required. +To initiate the caller Workflow, a starter program is used. [nexus/caller/starter/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/starter/main.go) @@ -510,13 +473,13 @@ func runWorkflow(c client.Client, workflow interface{}, args ...interface{}) { ## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} -Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter. +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. ### Run Workers connected to a local development server Run the Nexus handler Worker: -``` +```bash cd handler go run ./worker \ -target-host localhost:7233 \ @@ -525,7 +488,7 @@ go run ./worker \ In another terminal window, run the Nexus caller Worker: -``` +```bash cd caller go run ./worker \ -target-host localhost:7233 \ @@ -538,7 +501,7 @@ With the Workers running, the final step in the local development process is to Run the starter: -``` +```bash cd caller go run ./starter \ -target-host localhost:7233 \ @@ -555,19 +518,23 @@ This will result in: ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. -This returns a new context and a function that, when called, cancels the context and any SDK method that was passed this context. -The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it succeeds, fails, times out, or is canceled. +To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. This +returns a new context and a function that, when called, cancels the context and any SDK method that was passed this +context. The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it +succeeds, fails, times out, or is canceled. + +Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or +other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter +a terminal state. -Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. -The Workflow or other resources backing the operation may choose to ignore the cancelation request. -If ignored, the operation may enter a terminal state. +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. -Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. -It's okay to leave operations running in some use cases. -To ensure cancelations are delivered, wait for all pending operations to finish before exiting the Workflow. +It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. -See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) for reference. +See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) +for reference. ## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} @@ -637,11 +604,13 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. @@ -670,9 +639,13 @@ tcld nexus endpoint create \ The `--allow-namespace` flag adds caller Namespaces that can use the Nexus Endpoint to its allowlist. -Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. -### Run Workers Connected to Temporal Cloud with TLS certificates +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). + +### Run Workers connected to Temporal Cloud Run the handler Worker: @@ -698,7 +671,10 @@ go run ./worker \ -client-key 'path/to/your/ca.key' ``` -### Start a caller Workflow +To connect with an API key instead of mTLS certificates, replace `-client-cert` and `-client-key` with +`-api-key `. + +### Start a caller Workflow in Temporal Cloud ``` cd caller @@ -717,71 +693,24 @@ This will result in: 2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ ``` -### Run Workers Connected to Temporal Cloud with API keys - -[View the source code](https://github.com/temporalio/samples-go/tree/main/nexus) in the context of the rest of the application code. - -Run the handler Worker: - -``` -cd handler - -go run ./worker \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -api-key -``` - -Run the caller Worker: - -``` -cd caller - -go run ./worker \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -api-key -``` - -### Start a caller Workflow - -``` -cd caller - -go run ./starter \ - -target-host .tmprl.cloud:7233 \ - -namespace \ - -api-key -``` - -This will result in: - -``` -2024/10/04 19:57:40 Workflow result: Nexus Echo πŸ‘‹ -2024/10/04 19:57:40 Workflow result: Β‘Hola! Nexus πŸ‘‹ -``` - ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: ``` temporal workflow describe -w @@ -812,6 +741,8 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/java/nexus/developer-experience.mdx b/docs/develop/java/nexus/developer-experience.mdx deleted file mode 100644 index c34157ffc3..0000000000 --- a/docs/develop/java/nexus/developer-experience.mdx +++ /dev/null @@ -1,696 +0,0 @@ ---- -id: developer-experience -slug: /develop/java/nexus/developer-experience -title: Nexus Developer Experience - Java SDK feature guide -sidebar_label: Nexus Developer Experience -description: Build a Nexus Service in Java with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. -toc_max_heading_level: 4 -tags: - - Nexus - - Java SDK ---- - -import { CaptionedImage } from '@site/src/components'; - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. - -:::tip - -New to Nexus? Start with the [Nexus Java Quickstart](/develop/java/nexus/quickstart). - -::: - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -:::note - -This documentation uses source code derived from the -[Java Nexus sample](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexus). - -::: - -## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} - -Prerequisites: - -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/java/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) - (v1.3.0 or higher recommended) -- [Install the latest Temporal Java SDK](https://learn.temporal.io/getting_started/java/dev_environment/#add-temporal-java-sdk-dependencies) - (v1.28.0 or higher recommended) - -The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to -call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {/* #define-nexus-service-contract */} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service package that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. - -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, -runtime validators, and the Service definition itself. - -This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements -the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler -and a Go caller share no code, but they both run off that same service contract - so they interoperate with no -coordination between the teams beyond the contract itself. - -The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates -identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the -`nexgen` README for the file format. - -## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. -Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). -Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. - -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `TemporalOperationHandler.create(...)` hands your start handler three things: a context, a Client, and the -Operation input. What you do with the Client decides what backs the Operation: - -- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The - caller has its result as soon as the call returns. -- **Asynchronous.** Call `startWorkflow`, `startActivity`, or `startWorkflowUpdate` on the Client. The handler returns - as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be days - later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an Operation - outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous -backing per invocation. - -### Develop a Synchronous Nexus Operation handler - -Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, -and the Operation completes during the call. - -Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole -call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -```java -@ServiceImpl(service = SampleNexusService.class) -public class SampleNexusServiceImpl { - - @OperationImpl - public OperationHandler echo() { - return TemporalOperationHandler.create( - (ctx, client, input) -> - TemporalOperationResult.sync(new SampleNexusService.EchoOutput(input.getMessage()))); - } -} -``` - -### Use the Temporal Client for Signals, Queries, and Updates - -A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or -use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the -handler call, so they have to finish inside the -[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -Updates are the exception. Do not wait for one inside the handler. Start it with `startWorkflowUpdate` and it backs the -Operation. The handler returns straight away, and the Operation completes when the Update does, however long it takes. - -The [nexus_messaging](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging) -sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an -Operation with a Workflow Update. - -The Client your handler receives is not an ordinary Temporal Client. It propagates -[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the -caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client -through it rather than constructing your own. - -In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs -the identifier it cares about: - -```java -@OperationImpl -public OperationHandler approve() { - return TemporalOperationHandler.create( - (ctx, client, input) -> { - client - .getWorkflowClient() - .newWorkflowStub(GreetingWorkflow.class, "GreetingWorkflow_for_" + input.getUserId()) - .approve(input); - return TemporalOperationResult.sync(new ApproveOutput()); - }); -} -``` - -There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/). -The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return -value is delivered to the caller as the Operation's result. - -```java -@OperationImpl -public OperationHandler hello() { - return TemporalOperationHandler.create( - (ctx, client, input) -> - client.startWorkflow( - HelloHandlerWorkflow.class, - HelloHandlerWorkflow::hello, - input, - WorkflowOptions.newBuilder() - .setWorkflowId( - String.format( - "hello-%s-%s", - input.getName(), input.getLanguage().name().toLowerCase(Locale.ROOT))) - .build())); -} -``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. - -::: - -#### Map a Nexus Operation input to multiple Workflow arguments - -A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass the arguments -directly to `startWorkflow` between the method reference and the Workflow options: - -```java -@OperationImpl -public OperationHandler hello() { - return TemporalOperationHandler.create( - (ctx, client, input) -> - client.startWorkflow( - HelloHandlerWorkflow.class, - HelloHandlerWorkflow::hello, - input.getName(), - input.getLanguage(), - WorkflowOptions.newBuilder() - .setWorkflowId("hello-" + input.getName()) - .build())); -} -``` - -### Register a Nexus Service in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus -Service in a Worker. - - - -[core/src/main/java/io/temporal/samples/nexus/handler/HandlerWorker.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/handler/HandlerWorker.java) - -```java -package io.temporal.samples.nexus.handler; - -import io.temporal.client.WorkflowClient; -import io.temporal.samples.nexus.options.ClientOptions; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; - -public class HandlerWorker { - public static final String DEFAULT_TASK_QUEUE_NAME = "my-handler-task-queue"; - - public static void main(String[] args) { - WorkflowClient client = ClientOptions.getWorkflowClient(args); - - WorkerFactory factory = WorkerFactory.newInstance(client); - - Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); - worker.registerWorkflowImplementationTypes(HelloHandlerWorkflowImpl.class); - worker.registerNexusServiceImplementation(new SampleNexusServiceImpl()); - - factory.start(); - } -} -``` - - - -## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} - -Import the Service API package that has the necessary service and operation names and input/output types to execute a -Nexus Operation from the caller Workflow: - - - -[core/src/main/java/io/temporal/samples/nexus/caller/EchoCallerWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/EchoCallerWorkflowImpl.java) - -```java -package io.temporal.samples.nexus.caller; - -import io.temporal.samples.nexus.service.SampleNexusService; -import io.temporal.workflow.NexusOperationOptions; -import io.temporal.workflow.NexusServiceOptions; -import io.temporal.workflow.Workflow; -import java.time.Duration; - -public class EchoCallerWorkflowImpl implements EchoCallerWorkflow { - SampleNexusService sampleNexusService = - Workflow.newNexusServiceStub( - SampleNexusService.class, - NexusServiceOptions.newBuilder() - .setOperationOptions( - NexusOperationOptions.newBuilder() - .setScheduleToCloseTimeout(Duration.ofSeconds(10)) - .build()) - .build()); - - @Override - public String echo(String message) { - return sampleNexusService.echo(new SampleNexusService.EchoInput(message)).getMessage(); - } -} -``` - - - - - -[core/src/main/java/io/temporal/samples/nexus/caller/HelloCallerWorkflowImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/HelloCallerWorkflowImpl.java) - -```java -package io.temporal.samples.nexus.caller; - -import io.temporal.samples.nexus.service.SampleNexusService; -import io.temporal.workflow.NexusOperationHandle; -import io.temporal.workflow.NexusOperationOptions; -import io.temporal.workflow.NexusServiceOptions; -import io.temporal.workflow.Workflow; -import java.time.Duration; - -public class HelloCallerWorkflowImpl implements HelloCallerWorkflow { - SampleNexusService sampleNexusService = - Workflow.newNexusServiceStub( - SampleNexusService.class, - NexusServiceOptions.newBuilder() - .setOperationOptions( - NexusOperationOptions.newBuilder() - .setScheduleToCloseTimeout(Duration.ofSeconds(10)) - .build()) - .build()); - - @Override - public String hello(String message, SampleNexusService.Language language) { - NexusOperationHandle handle = - Workflow.startNexusOperation( - sampleNexusService::hello, new SampleNexusService.HelloInput(message, language)); - // Optionally wait for the operation to be started. NexusOperationExecution will contain the - // operation token in case this operation is asynchronous. - handle.getExecution().get(); - return handle.getResult().get().getMessage(); - } -} -``` - - - -### Register the caller Workflow in a Worker - -After developing the caller Workflow, the next step is to register it with a Worker. - - - -[core/src/main/java/io/temporal/samples/nexus/caller/CallerWorker.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/CallerWorker.java) - -```java -package io.temporal.samples.nexus.caller; - -import io.temporal.client.WorkflowClient; -import io.temporal.samples.nexus.options.ClientOptions; -import io.temporal.worker.Worker; -import io.temporal.worker.WorkerFactory; -import io.temporal.worker.WorkflowImplementationOptions; -import io.temporal.workflow.NexusServiceOptions; -import java.util.Collections; - -public class CallerWorker { - public static final String DEFAULT_TASK_QUEUE_NAME = "my-caller-workflow-task-queue"; - - public static void main(String[] args) { - WorkflowClient client = ClientOptions.getWorkflowClient(args); - - WorkerFactory factory = WorkerFactory.newInstance(client); - - Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME); - worker.registerWorkflowImplementationTypes( - WorkflowImplementationOptions.newBuilder() - .setNexusServiceOptions( - Collections.singletonMap( - "SampleNexusService", - NexusServiceOptions.newBuilder().setEndpoint("my-nexus-endpoint-name").build())) - .build(), - EchoCallerWorkflowImpl.class, - HelloCallerWorkflowImpl.class); - - factory.start(); - } -} -``` - - - -### Develop a starter to start the caller Workflow - -To initiate the caller Workflow, a starter program is used. - - - -[core/src/main/java/io/temporal/samples/nexus/caller/CallerStarter.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/caller/CallerStarter.java) - -```java -package io.temporal.samples.nexus.caller; - -import io.temporal.api.common.v1.WorkflowExecution; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowOptions; -import io.temporal.samples.nexus.options.ClientOptions; -import io.temporal.samples.nexus.service.SampleNexusService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CallerStarter { - private static final Logger logger = LoggerFactory.getLogger(CallerStarter.class); - - public static void main(String[] args) { - WorkflowClient client = ClientOptions.getWorkflowClient(args); - - WorkflowOptions workflowOptions = - WorkflowOptions.newBuilder().setTaskQueue(CallerWorker.DEFAULT_TASK_QUEUE_NAME).build(); - EchoCallerWorkflow echoWorkflow = - client.newWorkflowStub(EchoCallerWorkflow.class, workflowOptions); - WorkflowExecution execution = WorkflowClient.start(echoWorkflow::echo, "Nexus Echo πŸ‘‹"); - logger.info( - "Started EchoCallerWorkflow workflowId: {} runId: {}", - execution.getWorkflowId(), - execution.getRunId()); - logger.info("Workflow result: {}", echoWorkflow.echo("Nexus Echo πŸ‘‹")); - HelloCallerWorkflow helloWorkflow = - client.newWorkflowStub(HelloCallerWorkflow.class, workflowOptions); - execution = WorkflowClient.start(helloWorkflow::hello, "Nexus", SampleNexusService.Language.EN); - logger.info( - "Started HelloCallerWorkflow workflowId: {} runId: {}", - execution.getWorkflowId(), - execution.getRunId()); - logger.info("Workflow result: {}", helloWorkflow.hello("Nexus", SampleNexusService.Language.ES)); - } -} -``` - - - -## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} - -Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. - -### Run Workers connected to a local development server - -Run the Nexus handler Worker: - -```bash -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \ - --args="-target-host localhost:7233 -namespace my-target-namespace" -``` - -In another terminal window, run the Nexus caller Worker: - -```bash -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \ - --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -### Start a caller Workflow - -With the Workers running, the final step in the local development process is to start a caller Workflow. - -Run the starter: - -```bash -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \ - --args="-target-host localhost:7233 -namespace my-caller-namespace" -``` - -This will result in: - -``` -[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba -[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo πŸ‘‹ -[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc -[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Β‘Hola! Nexus πŸ‘‹ -``` - -### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} - -To cancel a Nexus Operation from within a Workflow, create a `CancellationScope` using the -`Workflow.newCancellationScope` API. `Workflow.newCancellationScope` takes a `Runnable`. Any SDK methods started in this -runnable, such as Nexus operations, will be associated with this scope. `Workflow.newCancellationScope` returns a new -scope that, when the `cancel()` method is called, cancels the context and any SDK method that was started in the scope. -The promise returned by `Workflow.startNexusOperation` is resolved when the operation finishes, whether it succeeds, -fails, times out, or is canceled. - -Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or -other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter -a terminal state. - -When a Nexus operation is started the caller can specify different cancellation types that will control how the caller -reacts to cancellation: - -- `ABANDON` - Do not request cancellation of the operation. -- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type - doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is - done. -- `WAIT_REQUESTED` - Request cancellation of the operation and wait for confirmation that the request was received. - Doesn't wait for actual cancellation. -- `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. - -The default is `WAIT_COMPLETED`. Users can set a different option on the `NexusServiceOptions` by calling -`.setCancellationType()` on `NexusServiceOptions.Builder`. - -Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet -been canceled, letting them run to completion. - -It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending -operations to deliver their cancellation requests before exiting the Workflow. - -See the -[Nexus cancelation sample](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexuscancellation) -for reference. - -## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} - -This section assumes you are already familiar with -[how connect a Worker to Temporal Cloud](/develop/java/client/temporal-client#start-workflow-execution). The same -[source code](https://github.com/temporalio/samples-go/tree/main/nexus) is used in this section, but the `tcld` CLI will -be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the -caller and handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on MacOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. - -``` -tcld nexus endpoint create \ - --name \ - --target-task-queue my-handler-task-queue \ - --target-namespace \ - --allow-namespace \ - --description-file ./core/src/main/java/io/temporal/samples/nexus/service/description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -### Run Workers Connected to Temporal Cloud - -Run the handler Worker: - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \ - --args="-target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key'" -``` - -Run the caller Worker: - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \ - --args="-target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key'" -``` - -### Start a caller Workflow - -``` -./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \ - --args="-target-host .tmprl.cloud:7233 \ - -namespace \ - -client-cert 'path/to/your/ca.pem' \ - -client-key 'path/to/your/ca.key'" -``` - -This will result in: - -``` -[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba -[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo πŸ‘‹ -[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc -[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Β‘Hola! Nexus πŸ‘‹ -``` - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Event history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/java/nexus/feature-guide.mdx b/docs/develop/java/nexus/feature-guide.mdx index 731a3ecd25..a148b8681d 100644 --- a/docs/develop/java/nexus/feature-guide.mdx +++ b/docs/develop/java/nexus/feature-guide.mdx @@ -23,6 +23,13 @@ New to Nexus? Start with the [Nexus Java Quickstart](/develop/java/nexus/quickst ::: + +:::caution + +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. + +::: + This page shows how to do the following: - [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) @@ -94,99 +101,21 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -Each [Temporal SDK includes and uses a default Data Converter](/dataconversion). The default -data converter encodes payloads in the following order: Null, Byte array, Protobuf JSON, and JSON. In a polyglot -environment, that is where more than one language and SDK is being used to develop a Temporal solution, Protobuf and -JSON are common choices. This example uses Java classes serialized into JSON. - - - -[core/src/main/java/io/temporal/samples/nexus/service/NexusService.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/service/NexusService.java) - -```java -@Service -public interface SampleNexusService { - enum Language { - EN, - FR, - DE, - ES, - TR - } - - class HelloInput { - private final String name; - private final Language language; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public HelloInput( - @JsonProperty("name") String name, @JsonProperty("language") Language language) { - this.name = name; - this.language = language; - } - - @JsonProperty("name") - public String getName() { - return name; - } - - @JsonProperty("language") - public Language getLanguage() { - return language; - } - } - - class HelloOutput { - private final String message; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public HelloOutput(@JsonProperty("message") String message) { - this.message = message; - } +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. - @JsonProperty("message") - public String getMessage() { - return message; - } - } - - class EchoInput { - private final String message; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public EchoInput(@JsonProperty("message") String message) { - this.message = message; - } - - @JsonProperty("message") - public String getMessage() { - return message; - } - } - - class EchoOutput { - private final String message; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public EchoOutput(@JsonProperty("message") String message) { - this.message = message; - } +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. - @JsonProperty("message") - public String getMessage() { - return message; - } - } - - @Operation - HelloOutput hello(HelloInput input); - - @Operation - EchoOutput echo(EchoInput input); -} -``` - - +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} @@ -198,75 +127,74 @@ Use an asynchronous Nexus Operation when latency or availability is uncertain, t Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. -The `io.temporal.nexus.*` packages have utilities to help create Nexus Operations: +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `TemporalOperationHandler.create(...)` hands your start handler three things: a context, a Client, and the +Operation input. What you do with the Client decides what backs the Operation: -- `Nexus.getOperationContext().getWorkflowClient()` \- Get the Temporal Client that the Worker was initialized with for - synchronous handlers backed by Temporal primitives such as Signals and Queries -- `WorkflowRunOperation.fromWorkflowMethod` \- Run a Workflow as an asynchronous Nexus Operation +- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `startWorkflow`, `startActivity`, or `startWorkflowUpdate` on the Client. The handler returns + as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be days + later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an Operation + outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -This example starts with a sync Operation handler example using the `OperationHandler.sync` method, and then shows how -to create an async Operation handler that uses `WorkflowRunOperation.fromWorkflowMethod` to start a handler Workflow -from a Nexus Operation. +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. ### Develop a Synchronous Nexus Operation handler -The `OperationHandler.sync` method is for exposing simple RPC handlers. Use -`Nexus.getOperationContext().getWorkflowClient(ctx)` to get the Temporal Client for signaling, querying, and listing -Workflows. Implementations can also make other calls, but handlers should be reliable to avoid tripping the -[circuit breaker](/nexus/operations#circuit-breaking). +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. -{/* SNIPSTART samples-java-nexus-handler {"selectedLines": ["1-16", "43"]} */} -[core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java) +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). ```java -// To create a service implementation, annotate the class with @ServiceImpl and provide the -// interface that the service implements. The service implementation class should have methods that -// return OperationHandler that correspond to the operations defined in the service interface. @ServiceImpl(service = SampleNexusService.class) public class SampleNexusServiceImpl { + @OperationImpl public OperationHandler echo() { - // OperationHandler.sync is a meant for exposing simple RPC handlers. - return OperationHandler.sync( - // The method is for making arbitrary short calls to other services or databases, or - // perform simple computations such as this one. Users can also access a workflow client by - // calling - // Nexus.getOperationContext().getWorkflowClient(ctx) to make arbitrary calls such as - // signaling, querying, or listing workflows. - (ctx, details, input) -> new SampleNexusService.EchoOutput(input.getMessage())); + return TemporalOperationHandler.create( + (ctx, client, input) -> + TemporalOperationResult.sync(new SampleNexusService.EchoOutput(input.getMessage()))); } -// ... } ``` -{/* SNIPEND */} - ### Use the Temporal Client for Signals, Queries, and Updates -A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow. You -can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update. -All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). Updates -should be short-lived to stay within this deadline. +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The [nexus_messaging](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging) sample shows how to create a Nexus Service that uses synchronous operations to send Updates and Queries. +Updates are the exception. Do not wait for one inside the handler. Start it with `startWorkflowUpdate` and it backs the +Operation. The handler returns straight away, and the Operation completes when the Update does, however long it takes. -Use the Nexus library, as shown below, to get the Client that the Worker was initialized with. In this example, the Workflow Id is derived from the client Id, with the "getWorkflowId" method. This converts a given client Id (in this case, the client is passing in a user Id) to generate a Workflow Id from it. -This way the client only needs the identifier it cares about. +The [nexus_messaging](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. -[nexusmessaging/callerpattern/handler/NexusGreetingServiceImpl.java](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/handler/NexusGreetingServiceImpl.java) -```java -static final String WORKFLOW_ID_PREFIX = "GreetingWorkflow_for_"; +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through it rather than constructing your own. - public static String getWorkflowId(String userId) { - return WORKFLOW_ID_PREFIX + userId; - } +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: - private GreetingWorkflow getWorkflowStub(String userId) { - return Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub(GreetingWorkflow.class, getWorkflowId(userId)); - } - ... +```java +@OperationImpl +public OperationHandler approve() { + return TemporalOperationHandler.create( + (ctx, client, input) -> { + client + .getWorkflowClient() + .newWorkflowStub(GreetingWorkflow.class, "GreetingWorkflow_for_" + input.getUserId()) + .approve(input); + return TemporalOperationResult.sync(new ApproveOutput()); + }); +} ``` There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-java/tree/main/core/src/main/java/io/temporal/samples/nexusmessaging/ondemandpattern/). @@ -274,46 +202,27 @@ The caller pattern shows how to send messages to an existing Workflow, while the ### Develop an Asynchronous Nexus Operation handler to start a Workflow -Use the `WorkflowRunOperation.fromWorkflowMethod` method, which is the easiest way to expose a Workflow as an operation. - - - -[core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java) +Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return +value is delivered to the caller as the Operation's result. ```java -// To create a service implementation, annotate the class with @ServiceImpl and provide the -// interface that the service implements. The service implementation class should have methods that -// return OperationHandler that correspond to the operations defined in the service interface. -@ServiceImpl(service = SampleNexusService.class) -public class SampleNexusServiceImpl { -// ... - @OperationImpl - public OperationHandler hello() { - // Use the WorkflowRunOperation.fromWorkflowMethod constructor, which is the easiest - // way to expose a workflow as an operation. To expose a workflow with a different input - // parameters then the operation or from an untyped stub, use the - // WorkflowRunOperation.fromWorkflowHandler constructor and the appropriate constructor method - // on WorkflowHandle. - return WorkflowRunOperation.fromWorkflowMethod( - (ctx, details, input) -> - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub( - HelloHandlerWorkflow.class, - // Workflow IDs should typically be business meaningful IDs and are used to - // dedupe workflow starts. - // For this example, we're using the request ID allocated by Temporal when - // the - // caller workflow schedules - // the operation, this ID is guaranteed to be stable across retries of this - // operation. - // - // Task queue defaults to the task queue this operation is handled on. - WorkflowOptions.newBuilder().setWorkflowId(details.getRequestId()).build()) +@OperationImpl +public OperationHandler hello() { + return TemporalOperationHandler.create( + (ctx, client, input) -> + client.startWorkflow( + HelloHandlerWorkflow.class, + HelloHandlerWorkflow::hello, + input, + WorkflowOptions.newBuilder() + .setWorkflowId( + String.format( + "hello-%s-%s", + input.getName(), input.getLanguage().name().toLowerCase(Locale.ROOT))) + .build())); +} ``` - - Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract. @@ -326,67 +235,25 @@ Conflict-Policy of Use-Existing. #### Map a Nexus Operation input to multiple Workflow arguments -A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes -multiple arguments use the `WorkflowRunOperation.fromWorkflowHandle` method. - - - -[core/src/main/java/io/temporal/samples/nexusmultipleargs/handler/NexusServiceImpl.java](https://github.com/temporalio/samples-java/blob/nexus-snip-sync/core/src/main/java/io/temporal/samples/nexusmultipleargs/handler/NexusServiceImpl.java) +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass the arguments +directly to `startWorkflow` between the method reference and the Workflow options: ```java -// To create a service implementation, annotate the class with @ServiceImpl and provide the -// interface that the service implements. The service implementation class should have methods that -// return OperationHandler that correspond to the operations defined in the service interface. -@ServiceImpl(service = SampleNexusService.class) -public class SampleNexusServiceImpl { - @OperationImpl - public OperationHandler echo() { - // OperationHandler.sync is a meant for exposing simple RPC handlers. - return OperationHandler.sync( - // The method is for making arbitrary short calls to other services or databases, or - // perform simple computations such as this one. Users can also access a workflow client by - // calling - // Nexus.getOperationContext().getWorkflowClient(ctx) to make arbitrary calls such as - // signaling, querying, or listing workflows. - (ctx, details, input) -> new SampleNexusService.EchoOutput(input.getMessage())); - } - - @OperationImpl - public OperationHandler hello() { - // If the operation input parameters are different from the workflow input parameters, - // use the WorkflowRunOperation.fromWorkflowHandler constructor and the appropriate constructor - // method on WorkflowHandle to map the Nexus input to the workflow parameters. - return WorkflowRunOperation.fromWorkflowHandle( - (ctx, details, input) -> - WorkflowHandle.fromWorkflowMethod( - Nexus.getOperationContext() - .getWorkflowClient() - .newWorkflowStub( - HelloHandlerWorkflow.class, - // Workflow IDs should typically be business meaningful IDs and are used - // to - // dedupe workflow starts. - // For this example, we're using the request ID allocated by Temporal - // when - // the - // caller workflow schedules - // the operation, this ID is guaranteed to be stable across retries of - // this - // operation. - // - // Task queue defaults to the task queue this operation is handled on. - WorkflowOptions.newBuilder() - .setWorkflowId(details.getRequestId()) - .build()) - ::hello, - input.getName(), - input.getLanguage())); - } +@OperationImpl +public OperationHandler hello() { + return TemporalOperationHandler.create( + (ctx, client, input) -> + client.startWorkflow( + HelloHandlerWorkflow.class, + HelloHandlerWorkflow::hello, + input.getName(), + input.getLanguage(), + WorkflowOptions.newBuilder() + .setWorkflowId("hello-" + input.getName()) + .build())); } ``` - - ### Register a Nexus Service in a Worker After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus diff --git a/docs/develop/python/nexus/developer-experience.mdx b/docs/develop/python/nexus/developer-experience.mdx deleted file mode 100644 index ab37841647..0000000000 --- a/docs/develop/python/nexus/developer-experience.mdx +++ /dev/null @@ -1,482 +0,0 @@ ---- -id: developer-experience -slug: /develop/python/nexus/developer-experience -title: Nexus Developer Experience - Python SDK feature guide -sidebar_label: Nexus Developer Experience -description: Build a Nexus Service in Python with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. -toc_max_heading_level: 4 -tags: - - Nexus - - Python SDK ---- - -import { CaptionedImage } from '@site/src/components'; - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. - -:::tip - -New to Nexus? Start with the [Nexus Python Quickstart](/develop/python/nexus/quickstart). - -::: - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -:::note - -This documentation uses source code derived from the -[Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). - -::: - -## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} - -Prerequisites: - -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/python/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) - (`v1.3.0` or higher recommended) -- [Install the latest Temporal Python SDK](https://learn.temporal.io/getting_started/python/dev_environment/#add-temporal-python-sdk-dependencies) - (`v1.32.0` or higher recommended) - -The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to -call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {/* #define-nexus-service-contract */} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service module that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. - -You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, -runtime validators, and the Service definition itself. - -This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements -the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler -and a Go caller share no code, but they both run off that same service contract - so they interoperate with no -coordination between the teams beyond the contract itself. - -The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates -identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the -`nexgen` README for the file format. - -## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. -Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). -Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. - -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). The -`@nexus.temporal_operation` decorator hands your start method three things: a context, a Client, and the Operation -input. What you do with the Client decides what backs the Operation: - -- **Synchronous.** Return `nexus.TemporalOperationResult.sync(...)` and the Operation completes during the handler call. - The caller has its result as soon as the call returns. -- **Asynchronous.** Call `start_workflow`, `start_activity`, or `start_workflow_update` on the Client. The handler - returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be - days later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an - Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous -backing per invocation. - -### Develop a Synchronous Nexus Operation handler - -Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, -and the Operation completes during the call. - -Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole -call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -```python -import nexusrpc -from temporalio import nexus - - -@nexusrpc.handler.service_handler(service=MyNexusService) -class MyNexusServiceHandler: - @nexus.temporal_operation - async def echo( - self, - _ctx: nexus.TemporalStartOperationContext, - client: nexus.TemporalNexusClient, - input: EchoInput, - ) -> nexus.TemporalOperationResult[EchoOutput]: - return nexus.TemporalOperationResult.sync(EchoOutput(message=input.message)) -``` - -### Use the Temporal Client for Signals, Queries, and Updates - -A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or -use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the -handler call, so they have to finish inside the -[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -Updates are the exception. Do not wait for one inside the handler. Start it with `start_workflow_update` and it backs -the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it -takes. - -The [nexus_messaging](https://github.com/temporalio/samples-python/tree/main/nexus_messaging) -sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an -Operation with a Workflow Update. - -The Client your handler receives is not an ordinary Temporal Client. It propagates -[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the -caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client -through `client.client` rather than constructing your own. - -In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs -the identifier it cares about: - -```python -@nexusrpc.handler.service_handler(service=NexusGreetingService) -class NexusGreetingServiceHandler: - def _get_workflow_handle( - self, client: Client, user_id: str - ) -> WorkflowHandle[GreetingWorkflow, str]: - return client.get_workflow_handle_for( - GreetingWorkflow.run, f"GreetingWorkflow_for_{user_id}" - ) - - @nexus.temporal_operation - async def approve( - self, - _ctx: nexus.TemporalStartOperationContext, - client: nexus.TemporalNexusClient, - input: ApproveInput, - ) -> nexus.TemporalOperationResult[ApproveOutput]: - await self._get_workflow_handle(client.client, input.user_id).signal( - GreetingWorkflow.approve, input - ) - return nexus.TemporalOperationResult.sync(ApproveOutput()) -``` - -There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/ondemandpattern/). -The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Call `start_workflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value -is delivered to the caller as the Operation's result. - -```python -@nexusrpc.handler.service_handler(service=MyNexusService) -class MyNexusServiceHandler: - @nexus.temporal_operation - async def hello( - self, - _ctx: nexus.TemporalStartOperationContext, - client: nexus.TemporalNexusClient, - input: HelloInput, - ) -> nexus.TemporalOperationResult[HelloOutput]: - return await client.start_workflow( - HelloHandlerWorkflow.run, - input, - id=f"hello-{input.name}-{input.language}", - ) -``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. - -::: - -#### Map a Nexus Operation input to multiple Workflow arguments - -A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them to -`start_workflow` as `args` instead of a single positional argument: - -```python -return await client.start_workflow( - HelloHandlerWorkflow.run, - args=[input.name, input.language], - id=f"hello-{input.name}-{input.language}", -) -``` - -### Register a Nexus Service in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus -Service handler in a Worker. At this stage you can pass any arguments you need to your service handler's `__init__` -method. - -[hello_nexus/handler/worker.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/worker.py) - -```python -async def main(): - client = await Client.connect("localhost:7233", namespace=NAMESPACE) - worker = Worker( - client, - task_queue=TASK_QUEUE, - workflows=[HelloHandlerWorkflow], - nexus_service_handlers=[MyNexusServiceHandler()], - ) - await worker.run() -``` - -## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} - -To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation -input/output types: - -[hello_nexus/caller/workflows.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/workflows.py) - -```python -from temporalio import workflow - -with workflow.unsafe.imports_passed_through(): - from hello_nexus.service import MyInput, MyNexusService, MyOutput - - -@workflow.defn -class CallerWorkflow: - @workflow.run - async def run(self, name: str) -> tuple[MyOutput, MyOutput]: - nexus_client = workflow.create_nexus_client( - service=MyNexusService, - endpoint=NEXUS_ENDPOINT, - ) - # Start the nexus operation and wait for the result in one go, using execute_operation. - wf_result = await nexus_client.execute_operation( - MyNexusService.my_workflow_run_operation, - MyInput(name), - ) - # Alternatively, you can use start_operation to obtain the operation handle and - # then `await` the handle to obtain the result. - sync_operation_handle = await nexus_client.start_operation( - MyNexusService.my_sync_operation, - MyInput(name), - ) - sync_result = await sync_operation_handle - return sync_result, wf_result -``` - -### Register the caller Workflow in a Worker and start the caller Workflow - -After developing the caller Workflow, the next step is to register it with a Worker. - -Finally, the caller Workflow must be started using `client.start_workflow()` or `client.execute_workflow()`. - -These steps are the same as for any normal Workflow. The Python sample combines them in a single application. -See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for -reference. - -## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} - -In one terminal, run the Temporal worker in the handler namespace: -``` -uv run handler/worker.py -``` - -In another terminal, run the Temporal worker in the caller namespace and start the caller workflow: -``` -uv run caller/app.py -``` - -### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} - -To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous -operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or other -resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter a -terminal state. - -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller -reacts to cancellation: - -- `ABANDON` - Do not request cancellation of the operation. -- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type - doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is - done. -- `WAIT_REQUESTED` - Request cancellation of the operation and wait for confirmation that the request was received. - Doesn't wait for actual cancellation. -- `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. - -The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an -operation. - -Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet -been canceled, letting them run to completion. - -It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending -operations to deliver their cancellation requests before exiting the Workflow. - -See the [Nexus cancellation sample](https://github.com/temporalio/samples-python/tree/main/nexus_cancel) for reference. - -## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} - -This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to -create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and -handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on macOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. - -``` -tcld nexus endpoint create \ - --name \ - --target-task-queue my-handler-task-queue \ - --target-namespace \ - --allow-namespace \ - --description-file hello_nexus/endpoint_description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Event history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index 440e20b1b4..d92027b73b 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -14,7 +14,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus +Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -22,6 +23,13 @@ New to Nexus? Start with the [Nexus Python Quickstart](/develop/python/nexus/qui ::: + +:::caution + +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. + +::: + This page shows how to do the following: - [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) @@ -30,15 +38,13 @@ This page shows how to do the following: - [Define the Nexus Service contract](#define-nexus-service-contract) - [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) - [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Understand exceptions in Nexus Operations](#exceptions-in-nexus-operations) -- [Cancel a Nexus Operation](#canceling-a-nexus-operation) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) - [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) -
- :::note -This documentation uses source code derived from the [Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). +This documentation uses source code derived from the +[Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). ::: @@ -46,8 +52,10 @@ This documentation uses source code derived from the [Python Nexus sample](https Prerequisites: -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/python/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) (`v1.3.0` or higher recommended) -- [Install the latest Temporal Python SDK](https://learn.temporal.io/getting_started/python/dev_environment/#add-temporal-python-sdk-dependencies) (`v1.14.1` or higher) +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/python/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (`v1.3.0` or higher recommended) +- [Install the latest Temporal Python SDK](https://learn.temporal.io/getting_started/python/dev_environment/#add-temporal-python-sdk-dependencies) + (`v1.32.0` or higher recommended) The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. @@ -55,9 +63,11 @@ The first step in working with Temporal Nexus involves starting a Temporal Serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -68,8 +78,8 @@ temporal operator namespace create --namespace my-target-namespace temporal operator namespace create --namespace my-caller-namespace ``` -For this example, `my-target-namespace` will contain the Nexus Operation handler, and you will use a Workflow in `my-caller-namespace` to call that Operation handler. -We use different namespaces to demonstrate cross-Namespace Nexus calls. +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -88,183 +98,173 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. - -Each [Temporal SDK includes and uses a default Data Converter](/dataconversion). -The default data converter encodes payloads in the following order: Null, Byte array, Protobuf JSON, and JSON. -In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, Protobuf and JSON are common choices. -This example uses Python dataclasses serialized into JSON. +In this example, there is a service module that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. -[hello_nexus/service.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/service.py) - -```python -from dataclasses import dataclass - -import nexusrpc +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. -@dataclass -class MyInput: - name: str +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. - -@dataclass -class MyOutput: - message: str - - -@nexusrpc.service -class MyNexusService: - my_sync_operation: nexusrpc.Operation[MyInput, MyOutput] - my_workflow_run_operation: nexusrpc.Operation[MyInput, MyOutput] -``` - -## Develop a Nexus Service handler and Operation handlers {/* #develop-nexus-service-operation-handlers */} +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. -They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors (for example: worker timeouts), blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. -The `nexusrpc.handler` and `temporalio.nexus` modules have utilities to help create Nexus Operations: +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). The +`@nexus.temporal_operation` decorator hands your start method three things: a context, a Client, and the Operation +input. What you do with the Client decides what backs the Operation: -- `nexusrpc.handler.sync_operation` - Create a synchronous operation handler -- `nexus.workflow_run_operation` - Create an asynchronous operation handler that starts a Workflow +- **Synchronous.** Return `nexus.TemporalOperationResult.sync(...)` and the Operation completes during the handler call. + The caller has its result as soon as the call returns. +- **Asynchronous.** Call `start_workflow`, `start_activity`, or `start_workflow_update` on the Client. The handler + returns as soon as that Execution has started, and the Operation stays open until the Execution finishes, which may be + days later. Its result is delivered to the caller through the Nexus completion callback. This is what lets an + Operation outlive the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). + +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. ### Develop a Synchronous Nexus Operation handler -The `@nexusrpc.handler.sync_operation` decorator is for exposing simple RPC handlers. +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. -[hello_nexus/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/service_handler.py) +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). ```python import nexusrpc +from temporalio import nexus + @nexusrpc.handler.service_handler(service=MyNexusService) class MyNexusServiceHandler: - @nexusrpc.handler.sync_operation - async def my_sync_operation( - self, ctx: nexusrpc.handler.StartOperationContext, input: MyInput - ) -> MyOutput: - return MyOutput(message=f"Hello {input.name} from sync operation!") + @nexus.temporal_operation + async def echo( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: EchoInput, + ) -> nexus.TemporalOperationResult[EchoOutput]: + return nexus.TemporalOperationResult.sync(EchoOutput(message=input.message)) ``` - -A synchronous operation handler must return quickly (less than `10s`). -Implementations can also make other calls, but handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking). - ### Use the Temporal Client for Signals, Queries, and Updates -A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow. -You can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update. -All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). Updates should be short-lived to stay within this deadline. +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use Signal-With-Start to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The [nexus_messaging](https://github.com/temporalio/samples-python/tree/main/nexus_messaging) sample shows how to create a Nexus Service that uses synchronous operations to send Updates and Queries. +Updates are the exception. Do not wait for one inside the handler. Start it with `start_workflow_update` and it backs +the Operation. The handler returns straight away, and the Operation completes when the Update does, however long it +takes. -Use `nexus.client()` to get the Client that the Worker was initialized with. In this example, the Workflow Id is derived from the client Id, with the "get_workflow_id" method. This takes a given client Id (in this case, the client is passing in a user ID) to generate a Workflow Id from it. -This way the client only needs the identifier it cares about. +The [nexus_messaging](https://github.com/temporalio/samples-python/tree/main/nexus_messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. -[nexus_messaging/callerpattern/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/main/nexus_messaging/callerpattern/handler/service_handler.py) +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.client` rather than constructing your own. -```python -from temporalio import nexus - -def get_workflow_id(user_id: str) -> str: - return f"{WORKFLOW_ID_PREFIX}{user_id}" +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: +```python @nexusrpc.handler.service_handler(service=NexusGreetingService) class NexusGreetingServiceHandler: - def _get_workflow_handle( - self, user_id: str + self, client: Client, user_id: str ) -> WorkflowHandle[GreetingWorkflow, str]: - return nexus.client().get_workflow_handle_for( - GreetingWorkflow.run, get_workflow_id(user_id) + return client.get_workflow_handle_for( + GreetingWorkflow.run, f"GreetingWorkflow_for_{user_id}" ) - ... + @nexus.temporal_operation + async def approve( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: ApproveInput, + ) -> nexus.TemporalOperationResult[ApproveOutput]: + await self._get_workflow_handle(client.client, input.user_id).signal( + GreetingWorkflow.approve, input + ) + return nexus.TemporalOperationResult.sync(ApproveOutput()) ``` -There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-python/blob/main/nexus_messaging/callerpattern/) and [on demand pattern](https://github.com/temporalio/samples-python/blob/main/nexus_messaging/ondemandpattern/). +There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/callerpattern/) and [on-demand pattern](https://github.com/temporalio/samples-python/tree/main/nexus_messaging/ondemandpattern/). The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. -In addition to `nexus.client()`, you can use `nexus.info()` to access information about the currently-executing Nexus Operation including its Task Queue. - - ### Develop an Asynchronous Nexus Operation handler to start a Workflow -Use the `@nexus.workflow_run_operation` decorator, which is the easiest way to expose a Workflow as an operation. - -[hello_nexus/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/service_handler.py) +Call `start_workflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value +is delivered to the caller as the Operation's result. ```python -import nexusrpc -from temporalio import nexus - @nexusrpc.handler.service_handler(service=MyNexusService) class MyNexusServiceHandler: - @nexus.workflow_run_operation - async def my_workflow_run_operation( - self, ctx: nexus.WorkflowRunOperationContext, input: MyInput - ) -> nexus.WorkflowHandle[MyOutput]: - return await ctx.start_workflow( - WorkflowStartedByNexusOperation.run, + @nexus.temporal_operation + async def hello( + self, + _ctx: nexus.TemporalStartOperationContext, + client: nexus.TemporalNexusClient, + input: HelloInput, + ) -> nexus.TemporalOperationResult[HelloOutput]: + return await client.start_workflow( + HelloHandlerWorkflow.run, input, - id=str(uuid.uuid4()), + id=f"hello-{input.name}-{input.language}", ) ``` -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. ::: #### Map a Nexus Operation input to multiple Workflow arguments -A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes multiple arguments use the `ctx.start_workflow` method. - - -[nexus_multiple_args/handler/service_handler.py](https://github.com/temporalio/samples-python/blob/main/nexus_multiple_args/handler/service_handler.py) -```py -@nexusrpc.handler.service_handler(service=MyNexusService) -class MyNexusServiceHandler: - """ - Service handler that demonstrates multiple argument handling in Nexus operations. - """ - - # This is a nexus operation that is backed by a Temporal workflow. - # The key feature here is that it demonstrates how to map a single input object - # (HelloInput) to a workflow that takes multiple individual arguments. - @nexus.workflow_run_operation - async def hello( - self, ctx: nexus.WorkflowRunOperationContext, input: HelloInput - ) -> nexus.WorkflowHandle[HelloOutput]: - """ - Start a workflow with multiple arguments unpacked from the input object. - """ - return await ctx.start_workflow( - HelloHandlerWorkflow.run, - args=[ - input.name, # First argument: name - input.language, # Second argument: language - ], - id=f"hello-multi-args-{input.name}-{input.language}", - ) - +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, pass them to +`start_workflow` as `args` instead of a single positional argument: +```python +return await client.start_workflow( + HelloHandlerWorkflow.run, + args=[input.name, input.language], + id=f"hello-{input.name}-{input.language}", +) ``` - +### Register a Nexus Service in a Worker -### Register your Nexus Service handler in a Worker {/* #register-a-nexus-service-in-a-worker */} - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus Service handler in a Worker. -At this stage you can pass any arguments you need to your service handler's `__init__` method. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus +Service handler in a Worker. At this stage you can pass any arguments you need to your service handler's `__init__` +method. [hello_nexus/handler/worker.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/worker.py) @@ -274,7 +274,7 @@ async def main(): worker = Worker( client, task_queue=TASK_QUEUE, - workflows=[WorkflowStartedByNexusOperation], + workflows=[HelloHandlerWorkflow], nexus_service_handlers=[MyNexusServiceHandler()], ) await worker.run() @@ -282,7 +282,8 @@ async def main(): ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation input/output types: +To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation +input/output types: [hello_nexus/caller/workflows.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/workflows.py) @@ -292,6 +293,7 @@ from temporalio import workflow with workflow.unsafe.imports_passed_through(): from hello_nexus.service import MyInput, MyNexusService, MyOutput + @workflow.defn class CallerWorkflow: @workflow.run @@ -319,39 +321,50 @@ class CallerWorkflow: After developing the caller Workflow, the next step is to register it with a Worker. -Finally, the caller Workflow must be started using `client.start_workflow()` or `client.execute_workflow()` +Finally, the caller Workflow must be started using `client.start_workflow()` or `client.execute_workflow()`. -These steps are the same as for any normal Workflow. -The Python sample combines them in a single application. -See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for reference. +These steps are the same as for any normal Workflow. The Python sample combines them in a single application. +See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for +reference. -## Exceptions in Nexus operations {/* #exceptions-in-nexus-operations */} +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} -Temporal provides general guidance on [Errors in Nexus operations](/references/failures#errors-in-nexus-operations). -In Python, there are three Nexus-specific exception classes: +In one terminal, run the Temporal worker in the handler namespace: +``` +uv run handler/worker.py +``` -- [`nexusrpc.OperationError`](https://nexus-rpc.github.io/sdk-python/nexusrpc.OperationError.html): this is the exception type you should raise in a Nexus operation to indicate that it has failed according to its own application logic and should not be retried. -- [`nexusrpc.HandlerError`](https://nexus-rpc.github.io/sdk-python/nexusrpc.HandlerError.html): you can raise this exception type in a Nexus operation with a specific [HandlerErrorType](https://nexus-rpc.github.io/sdk-python/nexusrpc.HandlerErrorType.html). The error will be marked retryable or non-retryable according to the type, following the [Nexus spec](https://github.com/nexus-rpc/api/blob/main/SPEC.md#predefined-handler-errors). The non-retryable handler error types are `BAD_REQUEST`, `UNAUTHENTICATED`, `UNAUTHORIZED`, `NOT_FOUND`, `NOT_IMPLEMENTED`; the retryable types are `RESOURCE_EXHAUSTED`, `INTERNAL`, `UNAVAILABLE`, `UPSTREAM_TIMEOUT`. -- [`temporalio.exceptions.NexusOperationError`](https://python.temporal.io/temporalio.exceptions.NexusOperationError.html): this is the error raised inside a Workflow when a Nexus operation fails for any reason. Use the `__cause__` attribute on the exception to access the cause chain. +In another terminal, run the Temporal worker in the caller namespace and start the caller workflow: +``` +uv run caller/app.py +``` -## Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. -The Workflow or other resources backing the operation may choose to ignore the cancellation request. -If ignored, the operation may enter a terminal state. +To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous +operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or other +resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter a +terminal state. -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller reacts to cancellation: +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller +reacts to cancellation: - `ABANDON` - Do not request cancellation of the operation. -- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is done. -- `WAIT_REQUESTED` Request cancellation of the operation and wait for confirmation that the request was received. Doesn't wait for actual cancellation. +- `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type + doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is + done. +- `WAIT_REQUESTED` - Request cancellation of the operation and wait for confirmation that the request was received. + Doesn't wait for actual cancellation. - `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. -The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an operation. +The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an +operation. + +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. -Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. -It's okay to leave operations running in some use cases. -To ensure cancellations are delivered, wait for all pending operations to finish before exiting the Workflow. +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. See the [Nexus cancellation sample](https://github.com/temporalio/samples-python/tree/main/nexus_cancel) for reference. @@ -407,27 +420,29 @@ temporal cloud namespace create \ tcld login tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 ```
-Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. @@ -454,31 +469,30 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: ``` temporal workflow describe -w @@ -509,6 +523,8 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/nexus/developer-experience.mdx b/docs/develop/typescript/nexus/developer-experience.mdx deleted file mode 100644 index ab05630527..0000000000 --- a/docs/develop/typescript/nexus/developer-experience.mdx +++ /dev/null @@ -1,476 +0,0 @@ ---- -id: developer-experience -slug: /develop/typescript/nexus/developer-experience -title: Nexus Developer Experience - TypeScript SDK feature guide -sidebar_label: Nexus Developer Experience -description: Build a Nexus Service in TypeScript with the pre-release APIs - the Temporal Operation Handler, Activity-backed Operations, and a generated Service contract. -toc_max_heading_level: 4 -tags: - - Nexus - - TypeScript SDK ---- - -import { CaptionedImage } from '@site/src/components'; - -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. - -:::tip - -New to Nexus? Start with the [Nexus TypeScript Quickstart](/develop/typescript/nexus/quickstart). - -::: - -This page shows how to do the following: - -- [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) -- [Create caller and handler Namespaces](#create-caller-handler-namespaces) -- [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint) -- [Define the Nexus Service contract](#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) - -:::note - -This documentation uses source code derived from the -[TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). - -::: - -## Run the Temporal Development Server with Nexus enabled {/* #run-the-temporal-nexus-development-server */} - -Prerequisites: - -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/typescript/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) - (`v1.3.0` or higher recommended) -- [Install the latest Temporal TypeScript SDK](https://learn.temporal.io/getting_started/typescript/dev_environment/#add-temporal-typescript-sdk-dependencies) - (`v1.23.0` or higher recommended) - -The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. - -``` -temporal server start-dev -``` - -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. - -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. - -## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} - -Before setting up Nexus endpoints, create separate Namespaces for the caller and handler. - -``` -temporal operator namespace create --namespace my-target-namespace -temporal operator namespace create --namespace my-caller-namespace -``` - -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to -call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. - -## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} - -After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests. - -``` -temporal operator nexus endpoint create \ - --name my-nexus-endpoint-name \ - --target-namespace my-target-namespace \ - --target-task-queue my-handler-task-queue -``` - -You can also use the Web UI to create the Namespaces and Nexus endpoint. - -## Define the Nexus Service contract {/* #define-nexus-service-contract */} - -Defining a clear contract for the Nexus Service is crucial for smooth communication. - -In this example, there is a service module that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. - -You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, -runtime validators, and the Service definition itself. - -This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements -the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler -and a Go caller share no code, but they both run off that same service contract - so they interoperate with no -coordination between the teams beyond the contract itself. - -The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates -identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the -`nexgen` README for the file format. - -## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. -Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). -Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. - -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Its `start` function -receives three things: a context, a Client, and the Operation input. What you do with the Client decides what backs the -Operation: - -- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The - caller has its result as soon as the call returns. -- **Asynchronous.** Call `startWorkflow` or `startActivity` on the Client, or `update` on a handle from - `getWorkflowHandle`. The handler returns as soon as that Execution has started, and the Operation stays open until the - Execution finishes, which may be days later. Its result is delivered to the caller through the Nexus completion - callback. This is what lets an Operation outlive the - [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous -backing per invocation. - -### Develop a Synchronous Nexus Operation handler - -Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, -and the Operation completes during the call. - -Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole -call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -```ts -import * as nexus from 'nexus-rpc'; -import * as temporalNexus from '@temporalio/nexus'; -import { helloService, EchoInput, EchoOutput } from '../api'; - -export const helloServiceHandler = nexus.serviceHandler(helloService, { - echo: new temporalNexus.TemporalOperationHandler({ - start: async (ctx, client, input) => { - return temporalNexus.TemporalOperationResult.sync({ message: input.message }); - }, - }), -}); -``` - -### Use the Temporal Client for Signals, Queries, and Updates - -A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or -use `signalWithStartWorkflow` to make sure the Workflow exists before the Signal arrives. Those calls complete during the -handler call, so they have to finish inside the -[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). The handler receives an `AbortSignal` on -`ctx.abortSignal` that fires when the deadline is exceeded. Pass it to Temporal Client calls so they are canceled if -the timeout is reached. - -Updates are the exception. Do not wait for one inside the handler. Start it with `update` on a handle from -`getWorkflowHandle` and it backs the Operation. The handler returns straight away, and the Operation completes when the -Update does, however long it takes. - -The [nexus-messaging](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging) -sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an -Operation with a Workflow Update. - -The Client your handler receives is not an ordinary Temporal Client. It propagates -[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the -caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client -through `client.client` rather than constructing your own. - -In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs -the identifier it cares about: - -```ts -function workflowIdForUser(userId: string): string { - return `GreetingWorkflow_for_${userId}`; -} - -export const nexusGreetingServiceHandler = nexus.serviceHandler(nexusGreetingService, { - getLanguages: new temporalNexus.TemporalOperationHandler({ - async start(_ctx, client, input: GetLanguagesInput) { - const handle = client.client.workflow.getHandle(workflowIdForUser(input.userId)); - const result = await handle.query(getLanguagesQuery); - return temporalNexus.TemporalOperationResult.sync(result); - }, - }), -}); -``` - -There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/callerpattern) and [on-demand pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/ondemandpattern). -The caller pattern shows how to send messages to an existing Workflow, while the on-demand pattern shows how to start a Workflow through Nexus and then send Signals to it. - -### Develop an Asynchronous Nexus Operation handler to start a Workflow - -Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value -is delivered to the caller as the Operation's result. - -```ts -export const helloServiceHandler = nexus.serviceHandler(helloService, { - hello: new temporalNexus.TemporalOperationHandler({ - start: async (ctx, client, input) => - client.startWorkflow(helloWorkflow, { - args: [input], - - // Workflow IDs should typically be business-meaningful IDs and are used to dedupe workflow starts. - workflowId: `hello-${input.name}-${input.language}`, - - // Task queue defaults to the task queue this Operation is handled on. - }), - }), -}); -``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. - -:::tip RESOURCES - -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. - -::: - -#### Map a Nexus Operation input to multiple Workflow arguments - -A Nexus Operation can only take one input parameter. To start a Workflow that takes several, spread the pieces of the -input across the `args` array: - -```ts -client.startWorkflow(helloWorkflow, { - args: [input.name, input.language], - workflowId: `hello-${input.name}-${input.language}`, -}); -``` - -### Register a Nexus Service in a Worker - -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus -Service handler in a Worker. - - -[nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) -```ts -import { Worker, NativeConnection } from '@temporalio/worker'; -import { helloServiceHandler } from './handler'; - -// ... - const namespace = 'my-target-namespace'; - const serviceTaskQueue = 'my-handler-task-queue'; - const worker = await Worker.create({ - connection, - namespace, - taskQueue: serviceTaskQueue, - workflowsPath: require.resolve('./workflows'), - nexusServices: [helloServiceHandler], - }); -``` - - -## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} - -To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use -`@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. You will need to provide -the Nexus Endpoint name, which you registered previously in -[Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). - - - -[nexus-hello/src/caller/workflows.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/workflows.ts) - -```ts -import * as wf from "@temporalio/workflow"; -import { helloService, LanguageCode } from "../service/api"; - -const HELLO_SERVICE_ENDPOINT = "hello-service-endpoint-name"; - -export async function helloCallerWorkflow(name: string, language: LanguageCode): Promise { - const nexusClient = wf.createNexusServiceClient({ - service: helloService, - endpoint: HELLO_SERVICE_ENDPOINT, - }); - - const helloResult = await nexusClient.executeOperation( - "hello", - { name, language }, - { scheduleToCloseTimeout: "10s" } - ); - - return helloResult.message; -} -``` - - - -### Register the caller Workflow in a Worker and start the caller Workflow - -This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, -as usual. Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) -for reference. - -- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) - shows how to register the caller Workflow in a Worker and run the Worker. -- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) - shows how to use a Temporal Client to execute the sample caller Workflow. - - -## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} - -Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. - -1. Run `npm run start.service` to start the Worker that will be serving the Nexus Operation handlers and its associated -Workflows. That Worker connects to the `my-target-namespace` namespace. - -2. In another shell, run `npm run start.caller` to start the Worker that will be serving the Caller Workflows. That -Worker connects to the `my-caller-namespace` namespace. - -3. In a third shell, `npm run workflow` to start an instance of the caller Workflows. - -Example output: - -```bash -Echo message: This message is from the client -Hello message: Hello, Temporal! -``` - -### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} - -Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within -Cancellation Scopes. Requesting cancellation of a Cancellation Scope results in requesting cancellation for all -cancellable operations owned by that scope. The Workflow itself defines the root Cancellation Scope. Requesting -cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that -workflow, including Nexus Operations. - -To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new -Cancellation Scope, and start the Nexus Operation from within that scope. An example demonstrating this can be found at -our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). - -Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow -or other resources backing the operation may choose to ignore the cancellation request. - -Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet -been canceled, letting them run to completion. - -It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending -operations to deliver their cancellation requests before exiting the Workflow. - -## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} - -This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The `tcld` CLI is used to -create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and -handler Workers to their respective Temporal Cloud Namespaces. - -### Install the latest `tcld` CLI and generate certificates - -To install the latest version of the `tcld` CLI, run the following command (on macOS): - -``` -brew install temporalio/brew/tcld -``` - -If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below: - -``` -tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key -``` - -These certificates will be valid for one year. - -### Create caller and handler Namespaces - -Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. -If you already have these Namespaces, you don't need to do this. - -``` -tcld login - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 - -tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 -``` - -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). - -### Create a Nexus Endpoint to route requests from caller to handler - -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. - -``` -tcld nexus endpoint create \ - --name \ - --target-task-queue my-handler-task-queue \ - --target-namespace \ - --allow-namespace \ - --description-file description.md -``` - -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. - -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). - -## Observability - -### Web UI - -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: - - - -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - - - -### Temporal CLI - -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: - -``` -temporal workflow describe -w -``` - -Nexus events are included in the caller's Event history: - -``` -temporal workflow show -w -``` - -For **asynchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationStarted` -- `NexusOperationCompleted` - -For **synchronous Nexus Operations** the following are reported in the caller's history: - -- `NexusOperationScheduled` -- `NexusOperationCompleted` - -:::note - -`NexusOperationStarted` isn't reported in the caller's history for synchronous operations. - -::: - -## Learn more - -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). -- Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/nexus/feature-guide.mdx b/docs/develop/typescript/nexus/feature-guide.mdx index 891270c394..b87d57e614 100644 --- a/docs/develop/typescript/nexus/feature-guide.mdx +++ b/docs/develop/typescript/nexus/feature-guide.mdx @@ -11,7 +11,7 @@ tags: import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import { CaptionedImage } from "@site/src/components"; +import { CaptionedImage } from '@site/src/components'; Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. @@ -21,6 +21,13 @@ New to Nexus? Start with the [Nexus TypeScript Quickstart](/develop/typescript/n ::: + +:::caution + +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. + +::: + This page shows how to do the following: - [Run a development Temporal Service with Nexus enabled](#run-the-temporal-nexus-development-server) @@ -29,15 +36,13 @@ This page shows how to do the following: - [Define the Nexus Service contract](#define-nexus-service-contract) - [Develop a Nexus Service and Operation handlers](#develop-nexus-service-operation-handlers) - [Develop a caller Workflow that uses a Nexus Service](#develop-caller-workflow-nexus-service) -- [Understand exceptions in Nexus Operations](#exceptions-in-nexus-operations) -- [Cancel a Nexus Operation](#canceling-a-nexus-operation) +- [Make Nexus calls across Namespaces with a development Server](#nexus-calls-across-namespaces-dev-server) - [Make Nexus calls across Namespaces in Temporal Cloud](#nexus-calls-across-namespaces-temporal-cloud) -
- :::note -This documentation uses source code derived from the [TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). +This documentation uses source code derived from the +[TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). ::: @@ -45,8 +50,10 @@ This documentation uses source code derived from the [TypeScript Nexus sample](h Prerequisites: -- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/typescript/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) (`v1.3.0` or higher recommended) -- [Install the latest Temporal TypeScript SDK](https://learn.temporal.io/getting_started/typescript/dev_environment/#add-temporal-typescript-sdk-dependencies) (`v1.12.3` or higher) +- [Install the latest Temporal CLI](https://learn.temporal.io/getting_started/typescript/dev_environment/#set-up-a-local-temporal-service-for-development-with-temporal-cli) + (`v1.3.0` or higher recommended) +- [Install the latest Temporal TypeScript SDK](https://learn.temporal.io/getting_started/typescript/dev_environment/#add-temporal-typescript-sdk-dependencies) + (`v1.23.0` or higher recommended) The first step in working with Temporal Nexus involves starting a Temporal Server with Nexus enabled. @@ -54,9 +61,11 @@ The first step in working with Temporal Nexus involves starting a Temporal Serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. +It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server +should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -67,8 +76,8 @@ temporal operator namespace create --namespace my-target-namespace temporal operator namespace create --namespace my-caller-namespace ``` -For this example, `my-target-namespace` will contain the Nexus Operation handler, and you will use a Workflow in `my-caller-namespace` to call that Operation handler. -We use different namespaces to demonstrate cross-Namespace Nexus calls. +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to +call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -87,136 +96,111 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. +In this example, there is a service module that describes the Service and Operation names along with input/output types +for caller Workflows to use the Nexus Endpoint. -Each [Temporal SDK includes and uses a default Data Converter](/dataconversion). -The default data converter encodes payloads in the following order: Null, Byte array, and JSON. -In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, JSON is a common choice. -This example uses plain TypeScript objects, serialized into JSON. +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). +You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +runtime validators, and the Service definition itself. -Note: By default, the TypeScript SDK [does not support Protobuf JSON encoding](https://typescript.temporal.io/api/interfaces/common.PayloadConverter). If passing Protobuf payloads use the [ProtobufJsonPayloadConverter](https://typescript.temporal.io/api/classes/protobufs.ProtobufJsonPayloadConverter) instead. +This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements +the Service, the caller invokes its Operations, and neither hand-writes a request or response type. A Python handler +and a Go caller share no code, but they both run off that same service contract - so they interoperate with no +coordination between the teams beyond the contract itself. - -[nexus-hello/src/api.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/api.ts) -```ts -import * as nexus from 'nexus-rpc'; +The generated validators check every payload against the contract, when a value is parsed off the wire and again when +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +identically in every language, which is what lets a caller and a handler written in different languages trust the same +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +`nexgen` README for the file format. -export const helloService = nexus.service('hello', { - /** - * Return the input message, unmodified. In the present sample, this Operation - * will be implemented using the Synchronous Nexus Operation handler syntax. - */ - echo: nexus.operation(), - - /** - * Return a salutation message, in the requested language. In the present sample, - * this Operation will be implemented by starting the `helloWorkflow` Workflow. - */ - hello: nexus.operation(), -}); +## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} -export interface EchoInput { - message: string; -} - -export interface EchoOutput { - message: string; -} - -export interface HelloInput { - name: string; - language: LanguageCode; -} - -export interface HelloOutput { - message: string; -} - -export type LanguageCode = 'en' | 'fr' | 'de' | 'es' | 'tr'; -``` - - -## Develop a Nexus Service handler and Operation handlers {/* #develop-nexus-service-operation-handlers */} - -A Nexus Service handler is defined using the `nexus-rpc`'s [`serviceHandler`](https://nexus-rpc.github.io/sdk-typescript/functions/serviceHandler.html) function. {/* Added */} -Nexus Service handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -A Service handler must provide Operation handlers for each Operation declared by the Service. {/* Added */} -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. -They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying +Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive +retryable errors, blocking all Operations from the caller to that Endpoint. + +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Its `start` function +receives three things: a context, a Client, and the Operation input. What you do with the Client decides what backs the +Operation: -The `@temporalio/nexus` package provides utilities to help create Nexus Operations that interact with a Temporal namespace: {/* Extended */} +- **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The + caller has its result as soon as the call returns. +- **Asynchronous.** Call `startWorkflow` or `startActivity` on the Client, or `update` on a handle from + `getWorkflowHandle`. The handler returns as soon as that Execution has started, and the Operation stays open until the + Execution finishes, which may be days later. Its result is delivered to the caller through the Nexus completion + callback. This is what lets an Operation outlive the + [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -- `WorkflowRunOperationHandler` - Create an asynchronous operation handler that starts a Workflow. -- `getClient()` - Get a Temporal Client connected using the same `NativeConnection` as the present Temporal Worker. - It can be used to implement synchronous handlers backed by Temporal primitives such as Signals and Queries. +A handler can perform any number of synchronous side effects, such as sending a Signal, but at most one asynchronous +backing per invocation. ### Develop a Synchronous Nexus Operation handler -Simple RPC handlers can be implemented as synchronous Nexus Operation handlers, which is defined in TypeScript as a simple async function. {/* sync operation vs async func is very confusing in this context */} -Use `getClient()` from `@temporalio/nexus` to get the Temporal Client for signaling, querying, and listing Workflows. -Implementations can also make other calls, but handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking). +Return a synchronous result when the Operation can answer immediately. The handler computes the answer and returns it, +and the Operation completes during the call. + +Handlers should be reliable to avoid tripping the [circuit breaker](/nexus/operations#circuit-breaking), and the whole +call has to finish inside the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). - -[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) ```ts import * as nexus from 'nexus-rpc'; -// ... -import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; -// ... +import * as temporalNexus from '@temporalio/nexus'; +import { helloService, EchoInput, EchoOutput } from '../api'; + export const helloServiceHandler = nexus.serviceHandler(helloService, { - echo: async (ctx, input: EchoInput): Promise => { - // A simple async function can be used to defined a Synchronous Nexus Operation. - // This is often sufficient for Operations that simply make arbitrary short calls to - // other services or databases, or that perform simple computations such as this one. - // - // You may also access a Temporal Client by calling `temporalNexus.getClient()`. - // That Client can be used to make arbitrary calls, such as signaling, querying, - // or listing workflows. - return input; - }, -// ... + echo: new temporalNexus.TemporalOperationHandler({ + start: async (ctx, client, input) => { + return temporalNexus.TemporalOperationResult.sync({ message: input.message }); + }, + }), }); ``` - ### Use the Temporal Client for Signals, Queries, and Updates -A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow. -You can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update. -All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). -The handler receives an AbortSignal via `ctx.abortSignal` that is triggered when the deadline is exceeded -β€” pass it to Temporal Client calls to ensure they are canceled if the timeout is reached. -Updates should be short-lived to stay within this deadline. +A common pattern is to reach a Workflow that is already running. Query it or Signal it from a synchronous Operation, or +use `signalWithStartWorkflow` to make sure the Workflow exists before the Signal arrives. Those calls complete during the +handler call, so they have to finish inside the +[Nexus request timeout](/cloud/limits#nexus-operation-request-timeout). The handler receives an `AbortSignal` on +`ctx.abortSignal` that fires when the deadline is exceeded. Pass it to Temporal Client calls so they are canceled if +the timeout is reached. -The handler context also exposes `ctx.requestDeadline` as an optional `Date`, representing the time by which the current request must complete. -Note that this is the deadline for the current _request_, not the overall operation. -Use it to make decisions about whether to start work that may not finish in time, or to set timeouts on downstream calls. +Updates are the exception. Do not wait for one inside the handler. Start it with `update` on a handle from +`getWorkflowHandle` and it backs the Operation. The handler returns straight away, and the Operation completes when the +Update does, however long it takes. -The [nexus_messaging](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging) sample shows how to create a Nexus Service that uses synchronous operations to send Updates and Queries. +The [nexus-messaging](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging) +sample shows a Nexus Service that Queries and Signals a running Workflow from synchronous Operations, and backs an +Operation with a Workflow Update. -Use the Nexus library, as shown below, to get the Client that the Worker was initialized with. In this example, the Workflow Id is derived from the client Id, with the "workflowIdForUser" method. This converts a given client Id (in this case, the client is passing in a user ID) into a Workflow Id. -This way the client only needs the identifier it cares about. +The Client your handler receives is not an ordinary Temporal Client. It propagates +[bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so the +caller-side and handler-side Executions are connected in the UI without wiring anything. Reach the Workflow Client +through `client.client` rather than constructing your own. -[nexus-messaging/src/callerpattern/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-messaging/src/callerpattern/service/handler.ts) +In this example the Workflow Id is derived from an identifier carried in the Operation input, so the caller only needs +the identifier it cares about: ```ts -import * as temporalNexus from '@temporalio/nexus'; - function workflowIdForUser(userId: string): string { return `GreetingWorkflow_for_${userId}`; } export const nexusGreetingServiceHandler = nexus.serviceHandler(nexusGreetingService, { - getLanguages: async (ctx, input: GetLanguagesInput) => { - const client = temporalNexus.getClient(); - const handle = client.workflow.getHandle(workflowIdForUser(input.userId)); - return await handle.query(getLanguagesQuery); - }, - - ... + getLanguages: new temporalNexus.TemporalOperationHandler({ + async start(_ctx, client, input: GetLanguagesInput) { + const handle = client.client.workflow.getHandle(workflowIdForUser(input.userId)); + const result = await handle.query(getLanguagesQuery); + return temporalNexus.TemporalOperationResult.sync(result); + }, + }), +}); ``` There are two examples of messaging through Nexus in the sample code, [caller pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/callerpattern) and [on-demand pattern](https://github.com/temporalio/samples-typescript/tree/main/nexus-messaging/src/ondemandpattern). @@ -224,56 +208,51 @@ The caller pattern shows how to send messages to an existing Workflow, while the ### Develop an Asynchronous Nexus Operation handler to start a Workflow -Use `@temporalio/nexus`'s `WorkflowRunOperationHandler` helper class to easily expose a Temporal Workflow as a Nexus Operation. -Note that even though a Nexus operation can only take one input parameter, if you need to pass -multiple arguments through to the workflow, you can do so by using multiple properties of the input object, and placing them in -the array provided to the `args` option when calling `startWorkflow`. +Call `startWorkflow` on the Client. The Operation completes when the Workflow returns, and the Workflow's return value +is delivered to the caller as the Operation's result. - -[nexus-hello/src/service/handler.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/handler.ts) ```ts -import * as nexus from 'nexus-rpc'; -import * as temporalNexus from '@temporalio/nexus'; -import { helloService, EchoInput, EchoOutput, HelloInput, HelloOutput } from '../api'; -import { helloWorkflow } from './workflows'; - -// ... export const helloServiceHandler = nexus.serviceHandler(helloService, { -// ... - hello: new temporalNexus.WorkflowRunOperationHandler( - // WorkflowRunOperationHandler takes a function that receives the Operation's context and input. - // That function can be used to validate and/or transform the input before passing it to - // the Workflow, as well as to customize various Workflow start options as appropriate. - // Call temporalNexus.startWorkflow() to actually start the Workflow from inside the - // WorkflowRunOperationHandler's delegate function. - async (ctx, input: HelloInput) => { - return await temporalNexus.startWorkflow(ctx, helloWorkflow, { + hello: new temporalNexus.TemporalOperationHandler({ + start: async (ctx, client, input) => + client.startWorkflow(helloWorkflow, { args: [input], // Workflow IDs should typically be business-meaningful IDs and are used to dedupe workflow starts. - // For this example, the workflow handles the greeting request for a given person and language pair. - workflowId: workflowIdForHello(input), + workflowId: `hello-${input.name}-${input.language}`, // Task queue defaults to the task queue this Operation is handled on. - }); - }, - ), + }), + }), }); ``` - -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. -In general, the ID should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID +should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a +Conflict-Policy of Use-Existing. ::: -### Register your Nexus Service handler in a Worker +#### Map a Nexus Operation input to multiple Workflow arguments + +A Nexus Operation can only take one input parameter. To start a Workflow that takes several, spread the pieces of the +input across the `args` array: + +```ts +client.startWorkflow(helloWorkflow, { + args: [input.name, input.language], + workflowId: `hello-${input.name}-${input.language}`, +}); +``` + +### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus Service handler in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus +Service handler in a Worker. [nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) @@ -296,8 +275,10 @@ import { helloServiceHandler } from './handler'; ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use `@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. -You will need to provide the Nexus Endpoint name, which you registered previously in [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). +To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use +`@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. You will need to provide +the Nexus Endpoint name, which you registered previously in +[Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). @@ -329,37 +310,55 @@ export async function helloCallerWorkflow(name: string, language: LanguageCode): ### Register the caller Workflow in a Worker and start the caller Workflow -This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, as usual. -Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) for reference. +This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, +as usual. Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) +for reference. + +- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) + shows how to register the caller Workflow in a Worker and run the Worker. +- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) + shows how to use a Temporal Client to execute the sample caller Workflow. + + +## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} -- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) shows how to register the caller Workflow in a Worker and run the Worker. -- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) shows how to use a Temporal Client to execute the sample caller Workflow. +Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app. -## Exceptions in Nexus operations {/* #exceptions-in-nexus-operations */} +1. Run `npm run start.service` to start the Worker that will be serving the Nexus Operation handlers and its associated +Workflows. That Worker connects to the `my-target-namespace` namespace. -Temporal provides general guidance on [Errors in Nexus operations](/references/failures#errors-in-nexus-operations). -In TypeScript, there are three Nexus-specific exception classes: +2. In another shell, run `npm run start.caller` to start the Worker that will be serving the Caller Workflows. That +Worker connects to the `my-caller-namespace` namespace. + +3. In a third shell, `npm run workflow` to start an instance of the caller Workflows. + +Example output: + +```bash +Echo message: This message is from the client +Hello message: Hello, Temporal! +``` -- `nexus-rpc`'s [`OperationError`](https://nexus-rpc.github.io/sdk-typescript/classes/OperationError.html): this is the exception type you should throw in a Nexus operation to indicate that it has failed according to its own application logic and should not be retried. -- `nexus-rpc`'s [`HandlerError`](https://nexus-rpc.github.io/sdk-typescript/classes/HandlerError.html): you can throw this exception type in a Nexus operation with a specific [HandlerErrorType](https://nexus-rpc.github.io/sdk-typescript/types/HandlerErrorType.html). The error will be marked as either retryable or non-retryable according to the type, following the [Nexus spec](https://github.com/nexus-rpc/api/blob/main/SPEC.md#predefined-handler-errors). The non-retryable handler error types are `BAD_REQUEST`, `UNAUTHENTICATED`, `UNAUTHORIZED`, `NOT_FOUND`, `NOT_IMPLEMENTED`; the retryable types are `RESOURCE_EXHAUSTED`, `INTERNAL`, `UNAVAILABLE`, `UPSTREAM_TIMEOUT`. -- `@temporalio/nexus`'s [`NexusOperationFailure`](https://typescript.temporal.io/api/classes/common.NexusOperationFailure): this is the error thrown inside a Workflow when a Nexus operation fails for any reason. Use the `cause` attribute on the exception to access the cause chain. +### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -## Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} +Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within +Cancellation Scopes. Requesting cancellation of a Cancellation Scope results in requesting cancellation for all +cancellable operations owned by that scope. The Workflow itself defines the root Cancellation Scope. Requesting +cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that +workflow, including Nexus Operations. -Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within Cancellation Scopes. -Requesting cancellation of a Cancellation Scope results in requesting cancellation for all cancellable operations owned by that scope. -The Workflow itself defines the root Cancellation Scope. -Requesting cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that workflow, including Nexus Operations. +To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new +Cancellation Scope, and start the Nexus Operation from within that scope. An example demonstrating this can be found at +our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). -To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new Cancellation Scope, and start the Nexus Operation from within that scope. -An example demonstrating this can be found at our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). +Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow +or other resources backing the operation may choose to ignore the cancellation request. -Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. -The Workflow or other resources backing the operation may choose to ignore the cancellation request. +Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet +been canceled, letting them run to completion. -Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. -It's okay to leave operations running in some use cases. -To ensure cancellations are delivered, wait for all pending operations to finish before exiting the Workflow. +It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending +operations to deliver their cancellation requests before exiting the Workflow. ## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} @@ -413,27 +412,29 @@ temporal cloud namespace create \ tcld login tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 tcld namespace create \ - --namespace \ - --cloud-provider aws \ - --region us-west-2 \ - --ca-certificate-file 'path/to/your/ca.pem' \ - --retention-days 1 + --namespace \ + --cloud-provider aws \ + --region us-west-2 \ + --ca-certificate-file 'path/to/your/ca.pem' \ + --retention-days 1 ``` -Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). +Alternatively, you can create Namespaces through the UI: +[https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the +`--target-namespace`. @@ -460,25 +461,30 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as +described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: +[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and +`NexusOperationCompleted` events in the caller's Event history: -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, +`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks +on the handler Workflow: ``` temporal workflow describe -w @@ -507,38 +513,10 @@ For **synchronous Nexus Operations** the following are reported in the caller's ::: -### OpenTelemetry - -The `@temporalio/interceptors-opentelemetry` package supports Nexus Operations, providing automatic trace context propagation across Nexus boundaries from the caller Workflow to the handler. - -The easiest way to enable it is with the `OpenTelemetryPlugin`, which auto-registers Nexus interceptors alongside Activity and Workflow interceptors: - -```ts -import { OpenTelemetryPlugin } from '@temporalio/interceptors-opentelemetry'; - -const plugin = new OpenTelemetryPlugin({ - resource: myResource, - spanProcessor: mySpanProcessor, -}); - -const worker = await Worker.create({ - // ... - plugins: [plugin], - nexusServices: [myServiceHandler], -}); -``` - -The plugin creates the following spans: - -- **Caller side:** `StartNexusOperation:service/operation` β€” created when the caller Workflow starts a Nexus Operation. -- **Handler side:** `RunStartNexusOperation:service/operation` and `RunCancelNexusOperation:service/operation` β€” created when the handler processes the operation. These spans are children of the caller span, linked via trace context propagated in Nexus request headers. - -See the [interceptors-opentelemetry sample](https://github.com/temporalio/samples-typescript/tree/main/interceptors-opentelemetry) for a complete example. - -For custom interceptor logic beyond tracing (for example, logging, authorization), see [Nexus interceptor registration](/develop/typescript/workers/interceptors#nexus-interceptor-registration). - ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the + [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and + [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/sidebars.js b/sidebars.js index 3dc096ad4a..f310c88f7e 100644 --- a/sidebars.js +++ b/sidebars.js @@ -106,7 +106,6 @@ const developDotnetCategory = { items: [ 'develop/dotnet/nexus/quickstart', 'develop/dotnet/nexus/feature-guide', - 'develop/dotnet/nexus/developer-experience', 'develop/dotnet/nexus/standalone-operations', ], }, @@ -259,7 +258,6 @@ const developGoCategory = { items: [ 'develop/go/nexus/quickstart', 'develop/go/nexus/feature-guide', - 'develop/go/nexus/developer-experience', 'develop/go/nexus/standalone-operations', ], }, @@ -422,7 +420,6 @@ const developJavaCategory = { items: [ 'develop/java/nexus/quickstart', 'develop/java/nexus/feature-guide', - 'develop/java/nexus/developer-experience', 'develop/java/nexus/standalone-operations', ], }, @@ -667,7 +664,6 @@ const developPythonCategory = { items: [ 'develop/python/nexus/quickstart', 'develop/python/nexus/feature-guide', - 'develop/python/nexus/developer-experience', 'develop/python/nexus/standalone-operations', ], }, @@ -1080,7 +1076,6 @@ const developTypeScriptCategory = { items: [ 'develop/typescript/nexus/quickstart', 'develop/typescript/nexus/feature-guide', - 'develop/typescript/nexus/developer-experience', 'develop/typescript/nexus/standalone-operations', ], }, From 30fd8105b5f90cb80368c018e73fafc15c0a2fc8 Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Fri, 4 Sep 2026 13:14:58 -0500 Subject: [PATCH 05/10] Update dependency injection reference in feature guide --- docs/develop/dotnet/nexus/feature-guide.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index 11135fdbba..178849f6b4 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -280,8 +280,7 @@ async Task RunHandlerWorkerAsync() } ``` -Nexus Service handlers also support dependency injection through the generic-host Worker. See -[Use dependency injection with a Nexus Service handler](/develop/dotnet/nexus/feature-guide#dependency-injection). +Nexus Service handlers also support dependency injection through the generic-host Worker. See [NexusDependencyInjection sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusDependencyInjection). ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} From b8df4a2dd13a68581e7ec618564a9a1279fb79db Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Fri, 4 Sep 2026 13:52:35 -0500 Subject: [PATCH 06/10] change admonition to note --- docs/develop/dotnet/nexus/feature-guide.mdx | 3 ++- docs/develop/go/nexus/feature-guide.mdx | 2 +- docs/develop/java/nexus/feature-guide.mdx | 2 +- docs/develop/python/nexus/feature-guide.mdx | 2 +- docs/develop/typescript/nexus/feature-guide.mdx | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index 178849f6b4..eda5b2e531 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -23,9 +23,10 @@ New to Nexus? Start with the [Nexus .NET Quickstart](/develop/dotnet/nexus/quick ::: -:::caution +:::note This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler) and [Nexus Standalone Activity](/nexus/standalone-activity). These APIs are experimental and may change. + ::: This page shows how to do the following: diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index e2ed3356d4..a72b12eb74 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -25,7 +25,7 @@ New to Nexus? Start with the [Nexus Go Quickstart](/develop/go/nexus/quickstart) ::: -:::caution +:::note This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. diff --git a/docs/develop/java/nexus/feature-guide.mdx b/docs/develop/java/nexus/feature-guide.mdx index a148b8681d..1e22e2f02f 100644 --- a/docs/develop/java/nexus/feature-guide.mdx +++ b/docs/develop/java/nexus/feature-guide.mdx @@ -24,7 +24,7 @@ New to Nexus? Start with the [Nexus Java Quickstart](/develop/java/nexus/quickst ::: -:::caution +:::note This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index d92027b73b..5f5b2ac59d 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -24,7 +24,7 @@ New to Nexus? Start with the [Nexus Python Quickstart](/develop/python/nexus/qui ::: -:::caution +:::note This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. diff --git a/docs/develop/typescript/nexus/feature-guide.mdx b/docs/develop/typescript/nexus/feature-guide.mdx index b87d57e614..0113dd210a 100644 --- a/docs/develop/typescript/nexus/feature-guide.mdx +++ b/docs/develop/typescript/nexus/feature-guide.mdx @@ -22,7 +22,7 @@ New to Nexus? Start with the [Nexus TypeScript Quickstart](/develop/typescript/n ::: -:::caution +:::note This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. From 411948feab092ce9653629e8f93276ba758b350e Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Fri, 4 Sep 2026 17:08:10 -0500 Subject: [PATCH 07/10] Restore original line wrapping in Nexus feature guides --- docs/develop/dotnet/nexus/feature-guide.mdx | 96 ++++++++----------- docs/develop/go/nexus/feature-guide.mdx | 85 +++++++--------- docs/develop/python/nexus/feature-guide.mdx | 82 +++++++--------- .../typescript/nexus/feature-guide.mdx | 81 ++++++---------- 4 files changed, 138 insertions(+), 206 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index eda5b2e531..329e933abd 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -13,8 +13,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -42,8 +41,7 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the -[.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). +This documentation uses source code derived from the [.NET Nexus sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusSimple). ::: @@ -62,11 +60,9 @@ The first step in working with Temporal Nexus involves starting a Temporal serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -77,9 +73,8 @@ temporal operator namespace create --namespace nexus-simple-handler-namespace temporal operator namespace create --namespace nexus-simple-caller-namespace ``` -`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in -`nexus-simple-caller-namespace` to call that Operation handler. We use different namespaces to demonstrate -cross-Namespace Nexus calls. +`nexus-simple-handler-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `nexus-simple-caller-namespace` to call that Operation handler. +We use different namespaces to demonstrate cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -98,8 +93,7 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. +In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, @@ -120,12 +114,11 @@ sample contract and the [Definition files](https://github.com/temporalio/nex-gen ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. +They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method `[TemporalOperation]` and the method body itself becomes the start handler, receiving three things: a @@ -232,13 +225,11 @@ public class HelloService } ``` -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. ::: @@ -255,8 +246,7 @@ client.StartWorkflowAsync( ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus -Service in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus Service in a Worker. [NexusSimple/Program.cs](https://github.com/temporalio/samples-dotnet/blob/main/src/NexusSimple/Program.cs) @@ -407,24 +397,18 @@ This will show the two workflows started and their results. ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only -asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or -other resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter -a terminal state. +To cancel a Nexus Operation from within a Workflow, cancel the cancellation token passed to the operation call. Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. +The Workflow or other resources backing the operation may choose to ignore the cancellation request. +If ignored, the operation may enter a terminal state. -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller -reacts to cancellation: +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller reacts to cancellation: - `Abandon` - Do not request cancellation of the operation. -- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type - doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is - done. -- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was - received. Doesn't wait for actual cancellation. +- `TryCancel` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type doesn't guarantee that cancellation is delivered to the operation handler if the caller exits before the delivery is done. +- `WaitCancellationRequested` - Request cancellation of the operation and wait for confirmation that the request was received. Doesn't wait for actual cancellation. - `WaitCancellationCompleted` - Wait for operation completion. Operation may or may not complete as cancelled. -The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in -`NexusWorkflowOperationOptions` when starting an operation. +The default is `WaitCancellationCompleted`. Users can set a different option for `CancellationType` in `NexusWorkflowOperationOptions` when starting an operation. Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet been canceled, letting them run to completion. @@ -432,8 +416,7 @@ been canceled, letting them run to completion. It's okay to leave operations running in some use cases. To ensure cancellations are delivered, wait for all pending operations to deliver their cancellation requests before exiting the Workflow. -See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for -reference. +See the [Nexus cancellation sample](https://github.com/temporalio/samples-dotnet/tree/main/src/NexusCancellation) for reference. ## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} @@ -501,13 +484,11 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. @@ -534,30 +515,31 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: ``` temporal workflow describe -w @@ -588,8 +570,6 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index a72b12eb74..644ba3b07c 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -15,8 +15,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -44,8 +43,7 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the -[Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). +This documentation uses source code derived from the [Go Nexus sample](https://github.com/temporalio/samples-go/tree/main/nexus). ::: @@ -62,11 +60,9 @@ The first step in working with Temporal Nexus involves starting a Temporal serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -77,8 +73,8 @@ temporal operator namespace create --namespace my-target-namespace temporal operator namespace create --namespace my-caller-namespace ``` -`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to -call that Operation handler. We use different namespaces to demonstrate cross-Namespace Nexus calls. +`my-target-namespace` will contain the Nexus Operation handler, and we will use a Workflow in `my-caller-namespace` to call that Operation handler. +We use different namespaces to demonstrate cross-Namespace Nexus calls. ## Create a Nexus Endpoint to route requests from caller to handler {/* #create-nexus-endpoint */} @@ -97,8 +93,7 @@ You can also use the Web UI to create the Namespaces and Nexus endpoint. Defining a clear contract for the Nexus Service is crucial for smooth communication. -In this example, there is a service package that describes the Service and Operation names along with input/output types -for caller Workflows to use the Nexus Endpoint. +In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, @@ -119,12 +114,11 @@ sample contract and the [Definition files](https://github.com/temporalio/nex-gen ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. -Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can invoke underlying -Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. +Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. +They can invoke underlying Temporal primitives such as a Query, Signal, or Update using the Temporal SDK Client, or run other reliable code. Use a synchronous Nexus Operation only when its complete execution path is highly reliable, has predictably low latency, and finishes well within the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout). Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. -Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive -retryable errors, blocking all Operations from the caller to that Endpoint. +Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the Operation input. What you do with the Client decides what backs the Operation: @@ -237,8 +231,7 @@ should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. ::: @@ -256,8 +249,7 @@ return temporalnexus.StartUntypedWorkflow[service.HelloOutput](ctx, nc, client.S ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus -Service in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus Service in a Worker. [nexus/handler/worker/main.go](https://github.com/temporalio/samples-go/blob/main/nexus/handler/worker/main.go) @@ -312,8 +304,7 @@ func main() { ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -Import the Service API package that has the necessary service and operation names and input/output types to execute a -Nexus Operation from the caller Workflow: +Import the Service API package that has the necessary service and operation names and input/output types to execute a Nexus Operation from the caller Workflow: [nexus/caller/workflows.go](https://github.com/temporalio/samples-go/blob/main/nexus/caller/workflows.go) @@ -518,14 +509,13 @@ This will result in: ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. This -returns a new context and a function that, when called, cancels the context and any SDK method that was passed this -context. The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it -succeeds, fails, times out, or is canceled. +To cancel a Nexus Operation from within a Workflow, create a Go context using the `workflow.WithCancel` API. +This returns a new context and a function that, when called, cancels the context and any SDK method that was passed this context. +The future returned by `NexusClient.ExecuteOperation` is resolved when the operation finishes, whether it succeeds, fails, times out, or is canceled. -Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or -other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter -a terminal state. +Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. +The Workflow or other resources backing the operation may choose to ignore the cancelation request. +If ignored, the operation may enter a terminal state. Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet been canceled, letting them run to completion. @@ -533,8 +523,7 @@ been canceled, letting them run to completion. It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending operations to deliver their cancellation requests before exiting the Workflow. -See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) -for reference. +See the [Nexus cancelation sample](https://github.com/temporalio/samples-go/tree/main/nexus-cancelation) for reference. ## Make Nexus calls across Namespaces in Temporal Cloud {/* #nexus-calls-across-namespaces-temporal-cloud */} @@ -604,13 +593,11 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. @@ -642,8 +629,7 @@ The `--allow-namespace` flag adds caller Namespaces that can use the Nexus Endpo The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ### Run Workers connected to Temporal Cloud @@ -697,20 +683,23 @@ This will result in: ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: ``` temporal workflow describe -w @@ -741,8 +730,6 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index 5f5b2ac59d..d71b95bea6 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -14,8 +14,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import { CaptionedImage } from '@site/src/components'; -Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus -Endpoint, a Nexus Service contract, and Nexus Operations. +Use [Temporal Nexus](/evaluate/nexus) to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. :::tip @@ -43,8 +42,7 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the -[Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). +This documentation uses source code derived from the [Python Nexus sample](https://github.com/temporalio/samples-python/tree/main/hello_nexus). ::: @@ -63,11 +61,9 @@ The first step in working with Temporal Nexus involves starting a Temporal Serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -237,13 +233,11 @@ class MyNexusServiceHandler: ) ``` -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. ::: @@ -262,9 +256,8 @@ return await client.start_workflow( ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus -Service handler in a Worker. At this stage you can pass any arguments you need to your service handler's `__init__` -method. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus Service handler in a Worker. +At this stage you can pass any arguments you need to your service handler's `__init__` method. [hello_nexus/handler/worker.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/handler/worker.py) @@ -282,8 +275,7 @@ async def main(): ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation -input/output types: +To execute a Nexus Operation from the caller Workflow, import the necessary service definition and operation input/output types: [hello_nexus/caller/workflows.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/workflows.py) @@ -323,9 +315,9 @@ After developing the caller Workflow, the next step is to register it with a Wor Finally, the caller Workflow must be started using `client.start_workflow()` or `client.execute_workflow()`. -These steps are the same as for any normal Workflow. The Python sample combines them in a single application. -See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for -reference. +These steps are the same as for any normal Workflow. +The Python sample combines them in a single application. +See [hello_nexus/caller/app.py](https://github.com/temporalio/samples-python/blob/main/hello_nexus/caller/app.py) for reference. ## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} @@ -341,13 +333,11 @@ uv run caller/app.py ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous -operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow or other -resources backing the operation may choose to ignore the cancellation request. If ignored, the operation may enter a -terminal state. +To cancel a Nexus Operation from within a Workflow, call `handle.cancel()` on the operation handle. Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. +The Workflow or other resources backing the operation may choose to ignore the cancellation request. +If ignored, the operation may enter a terminal state. -When a Nexus operation is started, the caller can specify different cancellation types that control how the caller -reacts to cancellation: +When a Nexus operation is started, the caller can specify different cancellation types that control how the caller reacts to cancellation: - `ABANDON` - Do not request cancellation of the operation. - `TRY_CANCEL` - Initiate a cancellation request and immediately report cancellation to the caller. Note that this type @@ -357,8 +347,7 @@ reacts to cancellation: Doesn't wait for actual cancellation. - `WAIT_COMPLETED` - Wait for operation completion. Operation may or may not complete as cancelled. -The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an -operation. +The default is `WAIT_COMPLETED`. Users can set a different option for `cancellation_type` when starting or executing an operation. Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet been canceled, letting them run to completion. @@ -436,13 +425,11 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). +Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/Namespaces](https://cloud.temporal.io/Namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. @@ -469,30 +456,31 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: - + -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: - + ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: ``` temporal workflow describe -w @@ -523,8 +511,6 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). diff --git a/docs/develop/typescript/nexus/feature-guide.mdx b/docs/develop/typescript/nexus/feature-guide.mdx index 0113dd210a..16965bab54 100644 --- a/docs/develop/typescript/nexus/feature-guide.mdx +++ b/docs/develop/typescript/nexus/feature-guide.mdx @@ -41,8 +41,7 @@ This page shows how to do the following: :::note -This documentation uses source code derived from the -[TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). +This documentation uses source code derived from the [TypeScript Nexus sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-hello). ::: @@ -61,11 +60,9 @@ The first step in working with Temporal Nexus involves starting a Temporal Serve temporal server start-dev ``` -This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. -It uses an in-memory database, so do not use it for real use cases. +This command automatically starts the Temporal development server with the Web UI, and creates the `default` Namespace. It uses an in-memory database, so do not use it for real use cases. -The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server -should now be available for client connections on `localhost:7233`. +The Temporal Web UI should now be accessible at [http://localhost:8233](http://localhost:8233), and the Temporal Server should now be available for client connections on `localhost:7233`. ## Create caller and handler Namespaces {/* #create-caller-handler-namespaces */} @@ -227,13 +224,12 @@ export const helloServiceHandler = nexus.serviceHandler(helloService, { }); ``` -Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID -should be passed in the Operation input as part of the Nexus Service contract. +Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. +In general, the ID should be passed in the Operation input as part of the Nexus Service contract. :::tip RESOURCES -[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a -Conflict-Policy of Use-Existing. +[Attach multiple Nexus callers to a handler Workflow](/nexus/operations#attaching-multiple-nexus-callers) with a Conflict-Policy of Use-Existing. ::: @@ -251,8 +247,7 @@ client.startWorkflow(helloWorkflow, { ### Register a Nexus Service in a Worker -After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus -Service handler in a Worker. +After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register your Nexus Service handler in a Worker. [nexus-hello/src/service/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/service/worker.ts) @@ -275,10 +270,8 @@ import { helloServiceHandler } from './handler'; ## Develop a caller Workflow that uses the Nexus Service {/* #develop-caller-workflow-nexus-service */} -To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use -`@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. You will need to provide -the Nexus Endpoint name, which you registered previously in -[Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). +To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use `@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service. +You will need to provide the Nexus Endpoint name, which you registered previously in [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint). @@ -310,14 +303,11 @@ export async function helloCallerWorkflow(name: string, language: LanguageCode): ### Register the caller Workflow in a Worker and start the caller Workflow -This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, -as usual. Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) -for reference. +This Workflow can be registered with a Worker and started using `client.startWorkflow()` or `client.executeWorkflow()`, as usual. +Refer to the [complete TypeScript sample](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello) for reference. -- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) - shows how to register the caller Workflow in a Worker and run the Worker. -- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) - shows how to use a Temporal Client to execute the sample caller Workflow. +- [nexus-hello/src/caller/worker.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/caller/worker.ts) shows how to register the caller Workflow in a Worker and run the Worker. +- [nexus-hello/src/starter.ts](https://github.com/temporalio/samples-typescript/blob/main/nexus-hello/src/starter.ts) shows how to use a Temporal Client to execute the sample caller Workflow. ## Make Nexus calls across Namespaces with a development Server {/* #nexus-calls-across-namespaces-dev-server */} @@ -341,18 +331,16 @@ Hello message: Hello, Temporal! ### Canceling a Nexus Operation {/* #canceling-a-nexus-operation */} -Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within -Cancellation Scopes. Requesting cancellation of a Cancellation Scope results in requesting cancellation for all -cancellable operations owned by that scope. The Workflow itself defines the root Cancellation Scope. Requesting -cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that -workflow, including Nexus Operations. +Nexus Operations, just like other cancellable APIs provided by the `@temporalio/workflow` package, execute within Cancellation Scopes. +Requesting cancellation of a Cancellation Scope results in requesting cancellation for all cancellable operations owned by that scope. +The Workflow itself defines the root Cancellation Scope. +Requesting cancellation of the Workflow therefore propagates the cancellation request to all cancellable operations started by that workflow, including Nexus Operations. -To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new -Cancellation Scope, and start the Nexus Operation from within that scope. An example demonstrating this can be found at -our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). +To provide more granular control over cancellation of a specific Nexus Operation, you may explicitly create a new Cancellation Scope, and start the Nexus Operation from within that scope. +An example demonstrating this can be found at our [nexus cancellation sample](https://github.com/temporalio/samples-typescript/tree/main/nexus-cancellation). -Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. The Workflow -or other resources backing the operation may choose to ignore the cancellation request. +Only asynchronous operations can be canceled in Nexus, since cancellation is sent using an operation token. +The Workflow or other resources backing the operation may choose to ignore the cancellation request. Once the caller Workflow completes, the caller's Nexus Machinery stops attempting to cancel operations that have not yet been canceled, letting them run to completion. @@ -428,13 +416,11 @@ tcld namespace create \ -Alternatively, you can create Namespaces through the UI: -[https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). +Alternatively, you can create Namespaces through the UI: [https://cloud.temporal.io/namespaces](https://cloud.temporal.io/namespaces). ### Create a Nexus Endpoint to route requests from caller to handler -To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the -`--target-namespace`. +To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the `--target-namespace`. @@ -461,30 +447,25 @@ tcld nexus endpoint create \ -The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as -described in Runtime Access Control. +The `--allow-namespace` is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control. -Alternatively, you can create a Nexus Endpoint through the UI: -[https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). +Alternatively, you can create a Nexus Endpoint through the UI: [https://cloud.temporal.io/nexus](https://cloud.temporal.io/nexus). ## Observability ### Web UI -A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and -`NexusOperationCompleted` events in the caller's Event history: +A synchronous Nexus Operation will surface in the caller Workflow as follows, with just `NexusOperationScheduled` and `NexusOperationCompleted` events in the caller's Event history: -An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, -`NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: +An asynchronous Nexus Operation will surface in the caller Workflow as follows, with `NexusOperationScheduled`, `NexusOperationStarted`, and `NexusOperationCompleted`, in the caller's Event history: ### Temporal CLI -Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks -on the handler Workflow: +Use the `workflow describe` command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow: ``` temporal workflow describe -w @@ -515,8 +496,6 @@ For **synchronous Nexus Operations** the following are reported in the caller's ## Learn more -- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the - [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). -- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and - [Encyclopedia](/nexus). +- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082). +- Learn how Nexus works in the [Nexus deep dive talk](https://www.youtube.com/watch?v=izR9dQ_eIe4) and [Encyclopedia](/nexus). - Deploy Nexus Endpoints in production with [Temporal Cloud](/cloud/nexus). From a036845f5728318915b4f66d5a07073402fc32ef Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Tue, 8 Sep 2026 11:25:48 -0500 Subject: [PATCH 08/10] roeys comments pt 1 --- docs/develop/dotnet/nexus/feature-guide.mdx | 12 ++++++------ docs/develop/go/nexus/feature-guide.mdx | 10 +++++----- docs/develop/java/nexus/feature-guide.mdx | 12 ++++++------ docs/develop/python/nexus/feature-guide.mdx | 10 +++++----- .../develop/typescript/nexus/feature-guide.mdx | 10 +++++----- .../nexus/nexus-code-generator.mdx | 18 +++++++++--------- .../nexus/temporal-operation-handler.mdx | 2 +- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index 329e933abd..7c3304f949 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -95,8 +95,8 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nexgen). +You write the contract once as a JSON or YAML definition file and run `nexgen` against it, and it emits the typed models, runtime validators, and the Service definition itself. This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements @@ -105,10 +105,10 @@ and a Go caller share no code, but they both run off that same service contract coordination between the teams beyond the contract itself. The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow or Activity. A value validates identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nexgen#definition-files) section of the `nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} @@ -121,7 +121,7 @@ Use an asynchronous Nexus Operation when latency or availability is uncertain, t Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method -`[TemporalOperation]` and the method body itself becomes the start handler, receiving three things: a +`[TemporalOperation]`; that method is the Operation handler and receives three things: a `TemporalOperationStartContext`, an `ITemporalNexusClient`, and the Operation input. The Operation the method handles is matched by method name to the corresponding `[NexusOperation]` method on the Service interface. What you do with the Client decides what backs the Operation: diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index 644ba3b07c..d3708acf70 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -95,8 +95,8 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nexgen). +You write the contract once as a JSON or YAML definition file and run `nexgen` against it, and it emits the typed models, runtime validators, and the Service definition itself. This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements @@ -105,10 +105,10 @@ and a Go caller share no code, but they both run off that same service contract coordination between the teams beyond the contract itself. The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow or Activity. A value validates identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nexgen#definition-files) section of the `nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} diff --git a/docs/develop/java/nexus/feature-guide.mdx b/docs/develop/java/nexus/feature-guide.mdx index 1e22e2f02f..950af61bc9 100644 --- a/docs/develop/java/nexus/feature-guide.mdx +++ b/docs/develop/java/nexus/feature-guide.mdx @@ -101,8 +101,8 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +You can hand-write that package, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nexgen). +You write the contract once as a JSON or YAML definition file and run `nexgen` against it, and it emits the typed models, runtime validators, and the Service definition itself. This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements @@ -111,10 +111,10 @@ and a Go caller share no code, but they both run off that same service contract coordination between the teams beyond the contract itself. The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow or Activity. A value validates identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nexgen#definition-files) section of the `nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} @@ -127,7 +127,7 @@ Use an asynchronous Nexus Operation when latency or availability is uncertain, t Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `TemporalOperationHandler.create(...)` hands your start handler three things: a context, a Client, and the +Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `TemporalOperationHandler.create(...)` hands your Operation handler three things: a context, a Client, and the Operation input. What you do with the Client decides what backs the Operation: - **Synchronous.** Return `TemporalOperationResult.sync(...)` and the Operation completes during the handler call. The diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index d71b95bea6..e6be2b57d8 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -97,8 +97,8 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service module that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nexgen). +You write the contract once as a JSON or YAML definition file and run `nexgen` against it, and it emits the typed models, runtime validators, and the Service definition itself. This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements @@ -107,10 +107,10 @@ and a Go caller share no code, but they both run off that same service contract coordination between the teams beyond the contract itself. The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow or Activity. A value validates identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nexgen#definition-files) section of the `nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} diff --git a/docs/develop/typescript/nexus/feature-guide.mdx b/docs/develop/typescript/nexus/feature-guide.mdx index 16965bab54..ada34ba1b1 100644 --- a/docs/develop/typescript/nexus/feature-guide.mdx +++ b/docs/develop/typescript/nexus/feature-guide.mdx @@ -96,8 +96,8 @@ Defining a clear contract for the Nexus Service is crucial for smooth communicat In this example, there is a service module that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint. -You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nex-gen). -You write the contract once as a JSON definition file and run `nexgen` against it, and it emits the typed models, +You can hand-write that module, but the preferred way is to generate it with the [Nexus Code Generator](https://github.com/temporalio/nexgen). +You write the contract once as a JSON or YAML definition file and run `nexgen` against it, and it emits the typed models, runtime validators, and the Service definition itself. This is what makes a Nexus Service polyglot. Both sides generate from the same definition file: the handler implements @@ -106,10 +106,10 @@ and a Go caller share no code, but they both run off that same service contract coordination between the teams beyond the contract itself. The generated validators check every payload against the contract, when a value is parsed off the wire and again when -it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow. A value validates +it is serialized onto it, so bad data is rejected at the boundary rather than reaching your Workflow or Activity. A value validates identically in every language, which is what lets a caller and a handler written in different languages trust the same -contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nex-gen/blob/main/samples/schemas/chat.nexusrpc.yaml) -sample contract and the [Definition files](https://github.com/temporalio/nex-gen#definition-files) section of the +contract. See the [`chat.nexusrpc.yaml`](https://github.com/temporalio/nexgen/blob/main/samples/schemas/chat.nexusrpc.yaml) +sample contract and the [Definition files](https://github.com/temporalio/nexgen#definition-files) section of the `nexgen` README for the file format. ## Develop a Nexus Service and Operation handlers {/* #develop-nexus-service-operation-handlers */} diff --git a/docs/encyclopedia/nexus/nexus-code-generator.mdx b/docs/encyclopedia/nexus/nexus-code-generator.mdx index 55dd4275d5..16d5a5808f 100644 --- a/docs/encyclopedia/nexus/nexus-code-generator.mdx +++ b/docs/encyclopedia/nexus/nexus-code-generator.mdx @@ -16,22 +16,22 @@ import { ReleaseNoteHeader } from '@site/src/components'; APIs are experimental and may be subject to backwards-incompatible changes. -A [Nexus Service](/nexus/services) is called across a team boundary, often by a caller written in a different language and deployed on its own schedule. +A [Nexus Service](/nexus/services) is called across a team boundary, often by a caller written in a different language than the handler implementation and deployed on its own schedule. When each side hand-writes its own request and response types, the two copies drift, and nothing catches it until a call fails. -The Nexus Code Generator, [`nexgen`](https://github.com/temporalio/nex-gen), generates client code for Go, Java, Python, and TypeScript from a schema file that defines the contract. +[`nexgen`](https://github.com/temporalio/nexgen) generates client code for Go, Java, Python, and TypeScript from a schema file that defines the contract. The schema's types are modeled with [JSON Schema 2020-12](https://json-schema.org). Both sides can then use code generated from the same file, which gives data validation and type safety across the languages and helps prevent drift. For each type it emits: -- **A typed model** β€” an idiomatic struct, class, interface, or dataclass, with doc comments carried over from the schema. -- **A runtime validator**, applied when a value is parsed off the wire and again when it is serialized onto it. +- **Typed models** β€” an idiomatic struct, class, interface, or dataclass, with doc comments carried over from the schema. +- **A runtime validator**, automatically applied when a value is parsed off the wire and again when it is serialized onto it. - **A [Nexus Service](/nexus/services) definition**, for a file that declares Services. The handler implements it; the caller uses it to invoke Operations. ## How it works -You write the contract once, as a JSON definition file, and run `nexgen` against it. -The generator emits client code in Go, Java, Python, or TypeScript. +You write the contract once, as a JSON or YAML definition file, and run `nexgen` against it. +The generator emits contract code in Go, Java, Python, or TypeScript as requested. Both sides use that generated code: the handler implements the Service, and the caller invokes its Operations. Because both were generated from the same file, they agree on the contract by construction, and the generated validators enforce it at runtime on every payload. @@ -43,10 +43,10 @@ Generate from it in each language and they interoperate, with no coordination be ## Data validation The generated validators check every payload against the contract, when a value is parsed off the wire and again when it is serialized onto it. -Bad data is rejected at the boundary instead of reaching your Workflow. +Bad data is rejected at the boundary instead of reaching your Workflow or Activity. -Failures aggregate into a single error listing every violation, each naming the offending field and the bound it broke. -A handler maps that to a `BAD_REQUEST` [Nexus error](/nexus/error-handling), so a malformed request tells the caller everything that was wrong in one response. +Failures aggregate into a single error listing every violation, each naming the offending field and the constraint it broke. +A handler maps that to a `BAD_REQUEST` [Nexus handler error](/nexus/error-handling), so a malformed request tells the caller everything that was wrong in one response. A value is validated identically in every language, which is what lets a caller and a handler written in different ones trust the same contract. Keeping that promise is why the supported schema subset is deliberately strict: anything ambiguous, or anything that cannot be expressed the same way everywhere, is rejected at generation time rather than becoming code that validates differently in one language than another. diff --git a/docs/encyclopedia/nexus/temporal-operation-handler.mdx b/docs/encyclopedia/nexus/temporal-operation-handler.mdx index a8b7fcf616..9cf1b4e700 100644 --- a/docs/encyclopedia/nexus/temporal-operation-handler.mdx +++ b/docs/encyclopedia/nexus/temporal-operation-handler.mdx @@ -22,7 +22,7 @@ What runs behind an Operation remains private to the handler, so you can change ## The Nexus-aware Client -The start handler receives a context, the Operation input, and a Client. +The Operation handler receives a context, the Operation input, and a Client. That Client is not an ordinary Temporal Client. It propagates [bidirectional links](/nexus/execution-debugging#bi-directional-linking) and request Ids on every call, so caller-side and handler-side Executions are connected in the UI and in [Event History](/encyclopedia/event-history) without wiring anything. From b1b51d0e71e900b3e93fdcdc8b3d1f54b542cb3f Mon Sep 17 00:00:00 2001 From: Jwahir Sundai Date: Tue, 8 Sep 2026 18:25:16 -0500 Subject: [PATCH 09/10] links, nit, cross language --- docs/develop/dotnet/nexus/feature-guide.mdx | 2 +- docs/develop/go/nexus/feature-guide.mdx | 2 +- docs/develop/java/nexus/feature-guide.mdx | 2 +- docs/develop/python/nexus/feature-guide.mdx | 2 +- docs/develop/typescript/nexus/feature-guide.mdx | 2 +- docs/encyclopedia/nexus/nexus-code-generator.mdx | 5 ++++- docs/encyclopedia/nexus/nexus-services.mdx | 2 ++ docs/encyclopedia/nexus/nexus-standalone-activity.mdx | 2 +- docs/encyclopedia/nexus/temporal-operation-handler.mdx | 2 +- 9 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index 7c3304f949..85b0691dd3 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -24,7 +24,7 @@ New to Nexus? Start with the [Nexus .NET Quickstart](/develop/dotnet/nexus/quick :::note -This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler) and [Nexus Standalone Activity](/nexus/standalone-activity). These APIs are experimental and may change. +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler) and [Nexus Standalone Activity](/nexus/standalone-activity). These APIs are experimental and subject to change. ::: diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index d3708acf70..2da46d00f7 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -26,7 +26,7 @@ New to Nexus? Start with the [Nexus Go Quickstart](/develop/go/nexus/quickstart) :::note -This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and subject to change. ::: diff --git a/docs/develop/java/nexus/feature-guide.mdx b/docs/develop/java/nexus/feature-guide.mdx index 950af61bc9..f12c1bd614 100644 --- a/docs/develop/java/nexus/feature-guide.mdx +++ b/docs/develop/java/nexus/feature-guide.mdx @@ -26,7 +26,7 @@ New to Nexus? Start with the [Nexus Java Quickstart](/develop/java/nexus/quickst :::note -This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and subject to change. ::: diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index e6be2b57d8..386d51e9fd 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -25,7 +25,7 @@ New to Nexus? Start with the [Nexus Python Quickstart](/develop/python/nexus/qui :::note -This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and subject to change. ::: diff --git a/docs/develop/typescript/nexus/feature-guide.mdx b/docs/develop/typescript/nexus/feature-guide.mdx index ada34ba1b1..614ce57021 100644 --- a/docs/develop/typescript/nexus/feature-guide.mdx +++ b/docs/develop/typescript/nexus/feature-guide.mdx @@ -24,7 +24,7 @@ New to Nexus? Start with the [Nexus TypeScript Quickstart](/develop/typescript/n :::note -This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and may change. +This Feature Guide includes the new Nexus developer experience: pre-release APIs for the [Temporal Operation Handler](/nexus/temporal-operation-handler), [Nexus Standalone Activity](/nexus/standalone-activity), and (where supported) the [Nexus Code Generator](/nexus/code-generator). These APIs are experimental and subject to change. ::: diff --git a/docs/encyclopedia/nexus/nexus-code-generator.mdx b/docs/encyclopedia/nexus/nexus-code-generator.mdx index 16d5a5808f..cd64381d63 100644 --- a/docs/encyclopedia/nexus/nexus-code-generator.mdx +++ b/docs/encyclopedia/nexus/nexus-code-generator.mdx @@ -12,7 +12,7 @@ tags: import { ReleaseNoteHeader } from '@site/src/components'; - + APIs are experimental and may be subject to backwards-incompatible changes. @@ -50,3 +50,6 @@ A handler maps that to a `BAD_REQUEST` [Nexus handler error](/nexus/error-handli A value is validated identically in every language, which is what lets a caller and a handler written in different ones trust the same contract. Keeping that promise is why the supported schema subset is deliberately strict: anything ambiguous, or anything that cannot be expressed the same way everywhere, is rejected at generation time rather than becoming code that validates differently in one language than another. + +Three numeric and timestamp edge cases do not yet behave the same way in every language, covering negative zero, very large fractional integers, and nanosecond precision in Python. +See [Known cross-language divergences](https://github.com/temporalio/nexgen#known-cross-language-divergences) in the `nexgen` README for what each language does. diff --git a/docs/encyclopedia/nexus/nexus-services.mdx b/docs/encyclopedia/nexus/nexus-services.mdx index 6ba1a1e9a6..db2c6a0a35 100644 --- a/docs/encyclopedia/nexus/nexus-services.mdx +++ b/docs/encyclopedia/nexus/nexus-services.mdx @@ -21,3 +21,5 @@ Multiple Services can run in the same Worker. Services typically run alongside the Workflows they abstract, or in a dedicated router Worker using the [router-queue pattern](/nexus/patterns#router-queue-pattern). Callers reference a Service by name when executing a Nexus Operation. + +You can hand-write a Service definition, or generate it from a schema file with the [Nexus Code Generator](/nexus/code-generator), which emits the definition along with typed models and runtime validators for each language. diff --git a/docs/encyclopedia/nexus/nexus-standalone-activity.mdx b/docs/encyclopedia/nexus/nexus-standalone-activity.mdx index 05b2fd6ac8..42bbdcb44f 100644 --- a/docs/encyclopedia/nexus/nexus-standalone-activity.mdx +++ b/docs/encyclopedia/nexus/nexus-standalone-activity.mdx @@ -12,7 +12,7 @@ tags: import { ReleaseNoteHeader } from '@site/src/components'; - + APIs are experimental and may be subject to backwards-incompatible changes. diff --git a/docs/encyclopedia/nexus/temporal-operation-handler.mdx b/docs/encyclopedia/nexus/temporal-operation-handler.mdx index 9cf1b4e700..86ac65b6ce 100644 --- a/docs/encyclopedia/nexus/temporal-operation-handler.mdx +++ b/docs/encyclopedia/nexus/temporal-operation-handler.mdx @@ -12,7 +12,7 @@ tags: import { ReleaseNoteHeader } from '@site/src/components'; - + APIs are experimental and may be subject to backwards-incompatible changes. From f6bcd46bc0f958bd932cbed340dad2b9c7885a6b Mon Sep 17 00:00:00 2001 From: Evan Reynolds Date: Thu, 10 Sep 2026 15:46:55 -0700 Subject: [PATCH 10/10] Minor wording tweak --- docs/develop/dotnet/nexus/feature-guide.mdx | 2 +- docs/develop/go/nexus/feature-guide.mdx | 2 +- docs/develop/python/nexus/feature-guide.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/develop/dotnet/nexus/feature-guide.mdx b/docs/develop/dotnet/nexus/feature-guide.mdx index 85b0691dd3..d7c95a2432 100644 --- a/docs/develop/dotnet/nexus/feature-guide.mdx +++ b/docs/develop/dotnet/nexus/feature-guide.mdx @@ -120,7 +120,7 @@ Use a synchronous Nexus Operation only when its complete execution path is highl Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). Mark a method +Every Operation is written with a [Temporal Operation Handler](/nexus/temporal-operation-handler). Mark a method `[TemporalOperation]`; that method is the Operation handler and receives three things: a `TemporalOperationStartContext`, an `ITemporalNexusClient`, and the Operation input. The Operation the method handles is matched by method name to the corresponding `[NexusOperation]` method on the Service interface. What you do with the diff --git a/docs/develop/go/nexus/feature-guide.mdx b/docs/develop/go/nexus/feature-guide.mdx index 2da46d00f7..425cc0a98d 100644 --- a/docs/develop/go/nexus/feature-guide.mdx +++ b/docs/develop/go/nexus/feature-guide.mdx @@ -120,7 +120,7 @@ Use a synchronous Nexus Operation only when its complete execution path is highl Use an asynchronous Nexus Operation when latency or availability is uncertain, the work might exceed the handler deadline, or execution depends on a potentially unreliable service or database. Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the +Every Operation is written with a [Temporal Operation Handler](/nexus/temporal-operation-handler). `temporalnexus.MustNewTemporalOperation(...)` takes a `Start` callback that receives three things: a context, a `NexusClient`, and the Operation input. What you do with the Client decides what backs the Operation: - **Synchronous.** Return `temporalnexus.NewSyncResult(...)` and the Operation completes during the handler call. The diff --git a/docs/develop/python/nexus/feature-guide.mdx b/docs/develop/python/nexus/feature-guide.mdx index 386d51e9fd..7fa2cea50d 100644 --- a/docs/develop/python/nexus/feature-guide.mdx +++ b/docs/develop/python/nexus/feature-guide.mdx @@ -123,7 +123,7 @@ Use an asynchronous Nexus Operation when latency or availability is uncertain, t Handlers should be reliable since the [circuit breaker](/nexus/operations#circuit-breaking) trips after 5 consecutive retryable errors, blocking all Operations from the caller to that Endpoint. -Every Operation is written with [`TemporalOperationHandler`](/nexus/temporal-operation-handler). The +Every Operation is written with a [Temporal Operation Handler](/nexus/temporal-operation-handler). The `@nexus.temporal_operation` decorator hands your start method three things: a context, a Client, and the Operation input. What you do with the Client decides what backs the Operation: