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 helpers —
Register<Service>NexusService(worker)to wire the service onto a worker, andRegister<Service>Operations(svc)for advanced cases that compose multiple services. - Typed clients — a
<Service>NexusClientinterface with sync (Op) and async (OpAsync) variants per operation. - Per-operation options builders —
New<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.
- Schema
- Go
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.
package greeting
import (
nexusv1 "path/to/gen/example/nexus/v1"
nexusv1temporal "path/to/gen/example/nexus/v1/nexusv1temporal"
"go.temporal.io/sdk/worker"
"go.temporal.io/sdk/workflow"
)
type (
workflows struct{}
helloWorkflow struct {
*workflows
*nexusv1.HelloWorkflowInput
}
)
// Register registers workflows and the Nexus service with a worker
func Register(r worker.Registry) error {
nexusv1.RegisterGreetingServiceWorkflows(r, &workflows{})
return nexusv1temporal.RegisterGreetingServiceNexusService(r)
}
// Hello creates a new hello workflow instance
func (w *workflows) Hello(ctx workflow.Context, input *nexusv1.HelloWorkflowInput) (nexusv1.HelloWorkflow, error) {
return &helloWorkflow{w, input}, nil
}
// Execute implements the hello workflow logic
func (w *helloWorkflow) Execute(ctx workflow.Context) (*nexusv1.HelloOutput, error) {
switch w.Req.Language {
case nexusv1.Language_LANGUAGE_ENGLISH:
return &nexusv1.HelloOutput{Message: "Hello " + w.Req.Name + " 👋"}, nil
case nexusv1.Language_LANGUAGE_SPANISH:
return &nexusv1.HelloOutput{Message: "¡Hola! " + w.Req.Name + " 👋"}, nil
default:
return &nexusv1.HelloOutput{Message: "Hello " + w.Req.Name + " 👋"}, nil
}
}
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.
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:
- Service Provider
- Service Consumer
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)
}
}
package consumer
import (
nexusv1 "path/to/gen/example/nexus/v1"
nexusv1temporal "path/to/gen/example/nexus/v1/nexusv1temporal"
"go.temporal.io/sdk/workflow"
)
type EchoWorkflow struct {
greeting nexusv1temporal.GreetingServiceNexusClient
*nexusv1.EchoWorkflowInput
}
func (w *EchoWorkflow) Execute(ctx workflow.Context) (*nexusv1.EchoOutput, error) {
// Call the greeting service via Nexus
hello, err := w.greeting.Hello(ctx, &nexusv1.HelloInput{
Name: w.Req.GetName(),
Language: w.Req.GetLanguage(),
})
if err != nil {
return nil, err
}
return &nexusv1.EchoOutput{
Message: hello.GetMessage(),
}, nil
}
Creating Nexus Clients
Construct a typed client with New<Service>NexusClient(endpoint) and inject it into the consumer's workflow registration:
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():
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:
plugins:
- plugin: buf.build/cludden/protoc-gen-go-temporal
out: gen
Nexus Endpoint Registration
Register Nexus endpoints to make services discoverable:
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:
- Remove the external plugins. Drop
protoc-gen-go-nexusandprotoc-gen-go-nexus-temporalentries from yourbuf.gen.yaml. The corresponding Go module dependencies are no longer needed. - Update import paths.
path/to/gen/.../nexusv1nexus— removed. The handler is now generated insidenexusv1temporal; no replacement import is needed.path/to/gen/.../nexusv1nexustemporal→path/to/gen/.../nexusv1temporal.
- Client type is now an interface. Replace
*<Service>NexusClient(pointer to struct) with<Service>NexusClient(interface) in field declarations and constructor return types. - Operation options moved to a variadic builder. Replace the trailing
workflow.NexusOperationOptions{}positional argument with the variadic...*<Op>WorkflowOperationOptions. Omit for defaults, or passNew<Op>WorkflowOperationOptions().With...(). - Future API renamed. Replace
.GetTyped(ctx)with.Get(ctx). - Delete user-defined handlers. Any hand-written
<Service>NexusHandlerstruct should be removed — handlers are now fully generated. A deprecated<Service>NexusHandlertype is still emitted as a shim for backward compatibility but should not be referenced in new code. - 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 frombuf.gen.yaml.