Skip to main content

Nexus

Nexus is a Temporal platform feature that enables reliable connectivity between Temporal Applications, promoting modular microservice architectures. This plugin natively generates Nexus services, operations, and typed clients — no external protoc-gen-go-nexus plugins required.

Overview

Nexus allows services to expose operations as well-defined service contracts that other services can reliably invoke. For each service with nexus.enabled = true, the plugin generates:

  • Nexus Service & Operations — workflow-run operations registered against a *nexus.Service, exposing each enabled workflow as a Nexus operation.
  • Registration helpersRegister<Service>NexusService(worker) to wire the service onto a worker, and Register<Service>Operations(svc) for advanced cases that compose multiple services.
  • Typed clients — a <Service>NexusClient interface with sync (Op) and async (OpAsync) variants per operation.
  • Per-operation options buildersNew<Op>WorkflowOperationOptions().With...() for fluent timeout and option configuration.

Schema Definition

Enable Nexus generation for a service by setting nexus.enabled = true. Currently only workflow operations are supported.

greeting.proto
syntax = "proto3";

package example.nexus.v1;

import "temporal/v1/temporal.proto";

service GreetingService {
option (temporal.v1.service) = {
task_queue: "greeting-v1"
nexus: {
enabled: true
name: "example.nexus.v1.GreetingService"
}
};

// generates a friendly greeting based on the input name and language
rpc Hello(HelloInput) returns (HelloOutput) {
option (temporal.v1.workflow) = {
id: 'example.nexus.v1.Hello/${! language.or(throw("language required")) }/${! name.slug() }'
};
}
}

message HelloInput {
string name = 1;
Language language = 2;
}

message HelloOutput {
string message = 1;
}

enum Language {
LANGUAGE_UNSPECIFIED = 0;
LANGUAGE_ENGLISH = 1;
LANGUAGE_SPANISH = 2;
LANGUAGE_FRENCH = 3;
}

nexus.name is optional and defaults to the fully-qualified proto service name.

Service & Operation Options

Service-level (temporal.v1.service.nexus)

  • enabled (bool) — turn on Nexus generation for the service.
  • name (string, optional) — override the fully-qualified Nexus service name. Defaults to the proto service full name.

Workflow-level (temporal.v1.workflow.nexus)

  • disabled (bool) — opt this workflow out of Nexus generation even when the service has Nexus enabled.
  • name (string, optional) — override the Nexus operation name. Defaults to the RPC method name.
example.proto
service GreetingService {
option (temporal.v1.service) = {
task_queue: "greeting-v1"
nexus: {
enabled: true
}
};

// exposed as the "Hello" Nexus operation
rpc Hello(HelloInput) returns (HelloOutput) {
option (temporal.v1.workflow) = {
id: 'example.nexus.v1.Hello/${! name.slug() }'
};
}

// expose under a custom Nexus operation name
rpc Goodbye(GoodbyeInput) returns (GoodbyeOutput) {
option (temporal.v1.workflow) = {
id: 'example.nexus.v1.Goodbye/${! name.slug() }'
nexus: { name: "Farewell" }
};
}

// skip Nexus generation for this workflow
rpc Internal(InternalInput) returns (InternalOutput) {
option (temporal.v1.workflow) = {
id: 'example.nexus.v1.Internal/${! name.slug() }'
nexus: { disabled: true }
};
}
}

Generated Code

The plugin emits the following surface into a <package>v1temporal Go package alongside the base generated types.

Service constants & registration

const GreetingServiceServiceName = "example.nexus.v1.GreetingService"

// RegisterGreetingServiceOperations registers all GreetingService Nexus operations on a service
func RegisterGreetingServiceOperations(svc *nexus.Service) error

// RegisterGreetingServiceNexusService creates the Nexus service, registers its operations, and attaches it to a worker
func RegisterGreetingServiceNexusService(r worker.NexusServiceRegistry) error

Operation definitions (per workflow)

const HelloWorkflowOperationName = "Hello"

// HelloWorkflowOperation is the Nexus workflow-run operation backing GreetingService.Hello
var HelloWorkflowOperation = temporalnexus.MustNewWorkflowRunOperationWithOptions(/* ... */)

Options builder (per workflow)

type HelloWorkflowOperationOptions struct { /* ... */ }

func NewHelloWorkflowOperationOptions() *HelloWorkflowOperationOptions
func (o *HelloWorkflowOperationOptions) WithOptions(opts workflow.NexusOperationOptions) *HelloWorkflowOperationOptions
func (o *HelloWorkflowOperationOptions) WithScheduleToCloseTimeout(d time.Duration) *HelloWorkflowOperationOptions
func (o *HelloWorkflowOperationOptions) Build(ctx workflow.Context) (workflow.NexusOperationOptions, error)

Client interface, future & constructor

type GreetingServiceNexusClient interface {
// generates a friendly greeting based on the input name and language
Hello(ctx workflow.Context, input *HelloInput, options ...*HelloWorkflowOperationOptions) (*HelloOutput, error)
HelloAsync(ctx workflow.Context, input *HelloInput, options ...*HelloWorkflowOperationOptions) HelloWorkflowOperationFuture
}

type HelloWorkflowOperationFuture interface {
Future() workflow.NexusOperationFuture
Get(ctx workflow.Context) (*HelloOutput, error)
}

func NewGreetingServiceNexusClient(endpoint string) GreetingServiceNexusClient

Usage Examples

Exposing a Service

Register workflows and the Nexus service with a worker:

main.go
package main

import (
"log"

nexusv1 "path/to/gen/example/nexus/v1"
nexusv1temporal "path/to/gen/example/nexus/v1/nexusv1temporal"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
)

func main() {
c, err := client.Dial(client.Options{})
if err != nil {
log.Fatal(err)
}
defer c.Close()

w := worker.New(c, nexusv1.GreetingServiceTaskQueue, worker.Options{})

// Register workflows
nexusv1.RegisterGreetingServiceWorkflows(w, &Workflows{})

// Register Nexus service
if err := nexusv1temporal.RegisterGreetingServiceNexusService(w); err != nil {
log.Fatal(err)
}

if err := w.Run(worker.InterruptCh()); err != nil {
log.Fatal(err)
}
}

Creating Nexus Clients

Construct a typed client with New<Service>NexusClient(endpoint) and inject it into the consumer's workflow registration:

workflows.go
func Register(r worker.WorkflowRegistry, endpoint string) error {
greeting := nexusv1temporal.NewGreetingServiceNexusClient(endpoint)
w := &workflows{greeting: greeting}
nexusv1.RegisterEchoServiceWorkflows(r, w)
return nil
}

Asynchronous Operations

Use the *Async variants for fire-and-forget or concurrent operations. The options builder is variadic — pass nothing to use defaults, or build one with New<Op>WorkflowOperationOptions():

async_example.go
func (w *MyWorkflow) Execute(ctx workflow.Context) error {
// Start multiple operations concurrently
future1 := w.greeting.HelloAsync(ctx, &nexusv1.HelloInput{
Name: "Alice", Language: nexusv1.Language_LANGUAGE_ENGLISH,
})

future2 := w.greeting.HelloAsync(ctx, &nexusv1.HelloInput{
Name: "Bob", Language: nexusv1.Language_LANGUAGE_SPANISH,
}, nexusv1temporal.NewHelloWorkflowOperationOptions().
WithScheduleToCloseTimeout(30*time.Second))

// Wait for both operations to complete
result1, err := future1.Get(ctx)
if err != nil {
return err
}

result2, err := future2.Get(ctx)
if err != nil {
return err
}

workflow.GetLogger(ctx).Info("Greetings received", "alice", result1.Message, "bob", result2.Message)
return nil
}

If options validation fails, the error is deferred to .Get(ctx) rather than panicking — the async constructor always returns a usable future.

Configuration

Plugin Configuration

Native Nexus generation requires no plugin flags. Generation is driven entirely by the temporal.v1.service.nexus.enabled proto option, so a minimal buf.gen.yaml is sufficient:

buf.gen.yaml
plugins:
- plugin: buf.build/cludden/protoc-gen-go-temporal
out: gen

Nexus Endpoint Registration

Register Nexus endpoints to make services discoverable:

register_endpoint.go
func registerEndpoint(ctx context.Context, c client.Client, endpointName string) error {
_, err := c.OperatorService().CreateNexusEndpoint(ctx, &operatorservice.CreateNexusEndpointRequest{
Spec: &nexus.EndpointSpec{
Name: endpointName,
Target: &nexus.EndpointTarget{
Variant: &nexus.EndpointTarget_Worker_{
Worker: &nexus.EndpointTarget_Worker{
Namespace: "default",
TaskQueue: nexusv1.GreetingServiceTaskQueue,
},
},
},
},
})
return err
}

Migrating from the legacy Nexus integration

Versions prior to the native integration relied on the external protoc-gen-go-nexus and protoc-gen-go-nexus-temporal plugins from github.com/bergundy/*. The following changes are required when upgrading:

  1. Remove the external plugins. Drop protoc-gen-go-nexus and protoc-gen-go-nexus-temporal entries from your buf.gen.yaml. The corresponding Go module dependencies are no longer needed.
  2. Update import paths.
    • path/to/gen/.../nexusv1nexus — removed. The handler is now generated inside nexusv1temporal; no replacement import is needed.
    • path/to/gen/.../nexusv1nexustemporalpath/to/gen/.../nexusv1temporal.
  3. Client type is now an interface. Replace *<Service>NexusClient (pointer to struct) with <Service>NexusClient (interface) in field declarations and constructor return types.
  4. Operation options moved to a variadic builder. Replace the trailing workflow.NexusOperationOptions{} positional argument with the variadic ...*<Op>WorkflowOperationOptions. Omit for defaults, or pass New<Op>WorkflowOperationOptions().With...().
  5. Future API renamed. Replace .GetTyped(ctx) with .Get(ctx).
  6. Delete user-defined handlers. Any hand-written <Service>NexusHandler struct should be removed — handlers are now fully generated. A deprecated <Service>NexusHandler type is still emitted as a shim for backward compatibility but should not be referenced in new code.
  7. Drop the legacy plugin flags. The following flags are retained only for the deprecated handler path and have no effect on native generation: nexus, nexus-include-service-tags, nexus-exclude-service-tags, nexus-include-operation-tags, nexus-exclude-operation-tags. Remove them from buf.gen.yaml.