Skip to main content

Temporal Nexus - Go SDK Feature Guide

Use Temporal Nexus to connect durable executions within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations.

This page shows how to do the following:

Run a development server with Nexus enabled

The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled.

Prerequisites:

Start the Temporal Development Server

Start the Temporal Development Server by using the server start-dev command, with system.enableNexus=true. The HTTP port is required for Nexus communications.

temporal server start-dev --http-port 7243 --dynamic-config-value system.enableNexus=true

This command automatically starts the Web UI, creates the default Namespace, and uses an in-memory database.

The Temporal Server should be available on localhost:7233 and the Temporal Web UI should be accessible at http://localhost:8233.

Create caller and 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

Create a Nexus Endpoint to route requests from caller to handler

After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests.

temporal operator nexus endpoint create \
--name myendpoint \
--target-namespace my-target-namespace \
--target-task-queue my-handler-task-queue \
--description-file ./service/description.md

Define the Nexus Service API contract

Defining a clear API contract for the Nexus Service is crucial for smooth communication between services.

View the source code in the context of the rest of the application code.

git clone https://github.com/temporalio/samples-go.git
cd samples-go/nexus

The Nexus Service API contract can be in whatever form works best for your environment. Each Temporal SDK includes and uses a default Data Converter, that encodes payloads in the following order: Null, Byte array, Protobuf JSON, JSON. In a polyglot environment, Protobuf and JSON are common choices, but this example uses native Go types.

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.

package service

const HelloServiceName = "my-hello-service"

// Echo operation
const EchoOperationName = "echo"

type EchoInput struct {
Message string
}

type EchoOutput EchoInput

Develop a 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, execute arbitrary code, and invoke underlying Temporal primitives such as a Workflow, Query, Signal, or Update.

The temporalnexus package has builders to create Nexus Operations:

  • NewSyncOperation - Simple synchronous RPC handlers, such as for Signals
  • NewWorkflowRunOperation - Run a Workflow as an asynchronous Nexus Operation

Start with a NewSyncOperation example, and then use NewWorkflowRunOperation to start a handler Workflow from a Nexus Operation.

Develop a Synchronous Nexus Operation handler

The temporalnexus.NewSyncOperation builder function is for exposing simple RPC handlers. It’s handler func is provided with an SDK client that can be used for signaling, querying, and listing workflows, but implementations are free to make arbitrary calls to other services or databases, or perform simple computations such as this one:

package handler

import (
"go.temporal.io/sdk/temporalnexus"
"github.com/temporalio/samples-go/nexus/service" ...
)

var EchoOperation = temporalnexus.NewSyncOperation( service.EchoOperationName, func(ctx context.Context, c client.Client, input service.EchoInput, options nexus.StartOperationOptions) (service.EchoOutput, error) {
return service.EchoOutput(input), nil
})

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 at https://pkg.go.dev/go.temporal.io/sdk/temporalnexus.

var HelloOperation = temporalnexus.NewWorkflowRunOperation(  service.HelloOperationName,   HelloHandlerWorkflow,  func(ctx context.Context, input service.HelloInput, options nexus.StartOperationOptions) (client.StartWorkflowOptions, error) {
return client.StartWorkflowOptions{
ID: "workflow-type-" + input.CustomerID,
}, nil
})

Workflow IDs should typically be business meaningful IDs and are used to dedupe Workflow starts. Here input.ID is passed as part of the Nexus Service contract, for the HelloOperation.

Register a Nexus Service in a Worker

A Nexus Service and its Operation handlers are usually registered in the same Worker as the underlying Temporal primitives they abstract.

package main

import (
"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/service" ...
)

const (
taskQueue = "my-handler-task-queue"
)

func main() {
// The client and Worker are heavyweight objects that should be created once per process.
c, err := client.Dial(client.Options{})
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)

err = w.Run(worker.InterruptCh())
if err != nil {
log.Fatalln("Unable to start worker", err)
}
}

Develop a caller Workflow that uses the 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:

package caller

import (
"github.com/temporalio/samples-go/nexus/service"
"go.temporal.io/sdk/workflow"
)

const (
TaskQueue = "my-caller-workflow-task-queue"
endpointName = "myendpoint"
)

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
}

Register the caller Workflow in a Worker

After developing the caller Workflow, the next step is to register it with a Worker.

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() {
c, err := client.Dial(client.Options{})
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 required.

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() {
c, err := client.Dial(client.Options{})
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)
}

Make Nexus calls across Namespaces with a dev Server

Run workers connected to a local dev server

In separate terminal window, run the Nexus handler Worker:

cd handler

go run ./worker \
-target-host localhost:7233 \
-namespace my-target-namespace

In another terminal window, run the Nexus caller Worker:

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:

cd caller
go run ./starter \
-target-host localhost:7233 \
-namespace my-caller-namespace

This will result in:

2024/07/23 19:57:40 Workflow result: Nexus Echo 👋
2024/07/23 19:57:40 Workflow result: ¡Hola! Nexus 👋

Make Nexus calls across Namespaces in Temporal Cloud

This section assumes you are already familiar with how connect a Worker to Temporal Cloud. The same source code 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 works to their respective Temporal Cloud Namespaces.

Install latest tcld CLI and generate certificates

First install the latest version of the tcld CLI and generate some certificates for mTLS Worker authentication:

brew install temporalio/brew/tcld
tcld gen ca --org temporal -d 1y --ca-cert ca.pem --ca-key ca.key

Create caller and handler Namespaces

Before deploying to Temporal Cloud, it's necessary to create the appropriate Namespaces for both the caller and handler.

tcld login

tcld namespace create \
--namespace <your-caller-namespace> \
--region us-west-2 \
--ca-certificate-file 'path/to/your/ca.pem' \
--retention-days 1

tcld namespace create \
--namespace <your-target-namespace> \
--region us-west-2 \
--ca-certificate-file 'path/to/your/ca.pem' \
--retention-days 1

Alternatively, you can create Namespace via the UI: 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 <myendpoint> \
--target-task-queue my-handler-task-queue \
--target-namespace <my-target-namespace.account> \
--allow-namespace <my-caller-namespace.account> \
--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 via the UI: https://cloud.temporal.io/nexus

Run Workers Connected to Temporal Cloud

View the source code in the context of the rest of the application code.

Run the handler Worker:

cd handler

go run ./worker \
-target-host <your-target-namespace.account>.tmprl.cloud:7233 \
-namespace <your-target-namespace.account> \
-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 <your-caller-namespace.account>.tmprl.cloud:7233 \
-namespace <your-caller-namespace.account> \
-client-cert 'path/to/your/ca.pem' \
-client-key 'path/to/your/ca.key'

Start a caller Workflow

In order to start the caller Workflow, run the starter.

cd caller

go run ./starter \
-target-host <your-caller-namespace.account>.tmprl.cloud:7233 \
-namespace <your-caller-namespace.account> \
-client-cert 'path/to/your/ca.pem' \
-client-key 'path/to/your/ca.key'

This will result in:

2024/07/23 19:57:40 Workflow result: Nexus Echo 👋
2024/07/23 19:57:40 Workflow result: ¡Hola! Nexus 👋

Observability in Temporal Cloud

A synchronous Nexus Operation will surface in the caller Workflow as follows, with just NexusOperationScheduled and NexusOperationCompleted events in the caller’s Workflow history:

Observability in Temporal Cloud Sync

An asynchronous Nexus Operation will surface in the caller Workflow as follows, with NexusOperationScheduled, NexusOperationStarted, and NexusOperationCompleted, in the caller’s Workflow history:

Observability in Temporal Cloud Async

Observability from the Temporal CLI

Nexus pending operations and callbacks are included in the Workflow description:

temporal workflow describe -w <ID>

Nexus events are included in the caller’s Workflow history:

temporal workflow show -w <ID>

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 is not reported in the caller’s history for synchronous operations.