Skip to main content

Introduction

The Cadence Go SDK is the official client library for building workflows and activities in Go. It provides a type-safe, idiomatic Go API for defining durable workflows that can run for days, months, or years.

GitHub Repository

Official Cadence Go Client SDK - Star and contribute on GitHub

Installation

Prerequisites

  • Go 1.18 or higher
  • Cadence server running (local or remote)

Add Dependency

go get go.uber.org/cadence

Import Packages

import (
    "go.uber.org/cadence/client"
    "go.uber.org/cadence/worker"
    "go.uber.org/cadence/workflow"
    "go.uber.org/cadence/activity"
)

Quick Start

1. Define a Workflow

Workflows must be deterministic and cannot directly call external services:
package main

import (
    "time"
    "go.uber.org/cadence/workflow"
)

// HelloWorkflow is a simple workflow that calls an activity
func HelloWorkflow(ctx workflow.Context, name string) (string, error) {
    // Configure activity options
    ao := workflow.ActivityOptions{
        ScheduleToStartTimeout: time.Minute,
        StartToCloseTimeout:    time.Minute,
        HeartbeatTimeout:       time.Second * 20,
    }
    ctx = workflow.WithActivityOptions(ctx, ao)

    // Execute activity
    var result string
    err := workflow.ExecuteActivity(ctx, GreetingActivity, name).Get(ctx, &result)
    if err != nil {
        return "", err
    }

    return result, nil
}

2. Define an Activity

Activities perform the actual work and can call external services:
package main

import (
    "context"
    "fmt"
)

// GreetingActivity is an activity that generates a greeting
func GreetingActivity(ctx context.Context, name string) (string, error) {
    // Activities can call external services, databases, etc.
    return fmt.Sprintf("Hello, %s!", name), nil
}

3. Start a Worker

Workers poll for tasks and execute workflows and activities:
package main

import (
    "go.uber.org/cadence/client"
    "go.uber.org/cadence/worker"
    "go.uber.org/zap"
)

func main() {
    // Create Cadence client
    logger, _ := zap.NewDevelopment()
    
    cadenceClient, err := client.NewClient(client.Options{
        HostPort:     "localhost:7933",
        Domain:       "my-domain",
        MetricsScope: nil, // Optional: add metrics
    })
    if err != nil {
        logger.Fatal("Failed to create client", zap.Error(err))
    }
    defer cadenceClient.Close()

    // Configure worker options
    workerOptions := worker.Options{
        Logger: logger,
        MetricsScope: nil,
        MaxConcurrentActivityExecutionSize:     100,
        MaxConcurrentDecisionTaskExecutionSize: 50,
    }

    // Create worker
    cadenceWorker := worker.New(cadenceClient, "my-task-list", workerOptions)

    // Register workflows
    cadenceWorker.RegisterWorkflow(HelloWorkflow)

    // Register activities
    cadenceWorker.RegisterActivity(GreetingActivity)

    // Start worker
    err = cadenceWorker.Start()
    if err != nil {
        logger.Fatal("Failed to start worker", zap.Error(err))
    }

    // Wait indefinitely
    select {}
}

4. Execute a Workflow

Start workflow execution from your application:
package main

import (
    "context"
    "time"
    "go.uber.org/cadence/client"
)

func executeWorkflow() {
    // Create client
    cadenceClient, err := client.NewClient(client.Options{
        HostPort: "localhost:7933",
        Domain:   "my-domain",
    })
    if err != nil {
        panic(err)
    }
    defer cadenceClient.Close()

    // Configure workflow options
    workflowOptions := client.StartWorkflowOptions{
        ID:                           "hello-workflow-1",
        TaskList:                     "my-task-list",
        ExecutionStartToCloseTimeout: time.Minute * 10,
        DecisionTaskStartToCloseTimeout: time.Minute,
    }

    // Start workflow
    we, err := cadenceClient.ExecuteWorkflow(
        context.Background(),
        workflowOptions,
        HelloWorkflow,
        "World",
    )
    if err != nil {
        panic(err)
    }

    // Wait for result
    var result string
    err = we.Get(context.Background(), &result)
    if err != nil {
        panic(err)
    }

    println("Workflow result:", result)
}

Advanced Patterns

Child Workflows

Orchestrate complex processes by composing workflows:
func ParentWorkflow(ctx workflow.Context) error {
    childOptions := workflow.ChildWorkflowOptions{
        ExecutionStartToCloseTimeout: time.Hour,
        TaskList:                     "child-task-list",
    }
    ctx = workflow.WithChildOptions(ctx, childOptions)

    var result string
    err := workflow.ExecuteChildWorkflow(ctx, ChildWorkflow, "input").Get(ctx, &result)
    if err != nil {
        return err
    }

    return nil
}

Signals and Queries

Signals allow external systems to send commands to running workflows:
func SignalableWorkflow(ctx workflow.Context) error {
    var message string
    signalChan := workflow.GetSignalChannel(ctx, "message-signal")
    
    // Wait for signal
    signalChan.Receive(ctx, &message)
    
    workflow.GetLogger(ctx).Info("Received signal", "message", message)
    return nil
}

// Send signal from client
err = cadenceClient.SignalWorkflow(
    context.Background(),
    "workflow-id",
    "run-id",
    "message-signal",
    "Hello from signal",
)

Error Handling and Retries

func RobustWorkflow(ctx workflow.Context) error {
    // Configure retry policy
    retryPolicy := &workflow.RetryPolicy{
        InitialInterval:    time.Second,
        BackoffCoefficient: 2.0,
        MaximumInterval:    time.Minute,
        MaximumAttempts:    5,
        NonRetriableErrorReasons: []string{"invalid-input"},
    }

    ao := workflow.ActivityOptions{
        StartToCloseTimeout: time.Minute * 5,
        RetryPolicy:         retryPolicy,
    }
    ctx = workflow.WithActivityOptions(ctx, ao)

    // Execute activity with automatic retries
    err := workflow.ExecuteActivity(ctx, RiskyActivity).Get(ctx, nil)
    if err != nil {
        // Handle error or let workflow fail
        return err
    }

    return nil
}

Local Activities

Local activities are optimized for short, fast operations:
func WorkflowWithLocalActivity(ctx workflow.Context) error {
    lao := workflow.LocalActivityOptions{
        ScheduleToCloseTimeout: time.Second * 5,
    }
    ctx = workflow.WithLocalActivityOptions(ctx, lao)

    // Execute local activity (no history event)
    var result string
    err := workflow.ExecuteLocalActivity(ctx, FastActivity).Get(ctx, &result)
    if err != nil {
        return err
    }

    return nil
}

Worker Configuration

Tuning Worker Performance

workerOptions := worker.Options{
    Logger:       logger,
    MetricsScope: metricsScope,
    
    // Control concurrent workflow executions
    MaxConcurrentDecisionTaskExecutionSize: 100,
    
    // Control concurrent activity executions
    MaxConcurrentActivityExecutionSize: 200,
    
    // Control poller threads
    MaxConcurrentActivityTaskPollers: 10,
    MaxConcurrentDecisionTaskPollers: 5,
    
    // Enable sticky workflow cache for performance
    EnableStickyExecution: true,
    StickyScheduleToStartTimeout: time.Second * 5,
}

Worker Lifecycle Management

func runWorkerWithGracefulShutdown() {
    cadenceWorker := worker.New(cadenceClient, "my-task-list", workerOptions)
    
    // Register workflows and activities
    cadenceWorker.RegisterWorkflow(MyWorkflow)
    cadenceWorker.RegisterActivity(MyActivity)
    
    // Start worker
    err := cadenceWorker.Start()
    if err != nil {
        panic(err)
    }
    
    // Handle shutdown signals
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
    <-sigChan
    
    // Graceful shutdown
    cadenceWorker.Stop()
}

Testing

Unit Testing Workflows

import (
    "testing"
    "go.uber.org/cadence/testsuite"
)

func TestHelloWorkflow(t *testing.T) {
    testSuite := &testsuite.WorkflowTestSuite{}
    env := testSuite.NewTestWorkflowEnvironment()
    
    // Mock activity
    env.OnActivity(GreetingActivity, mock.Anything, "World").Return("Hello, World!", nil)
    
    // Execute workflow
    env.ExecuteWorkflow(HelloWorkflow, "World")
    
    // Verify workflow completed
    require.True(t, env.IsWorkflowCompleted())
    require.NoError(t, env.GetWorkflowError())
    
    // Verify result
    var result string
    require.NoError(t, env.GetWorkflowResult(&result))
    require.Equal(t, "Hello, World!", result)
}

Sample Repository

Cadence Go Samples

Complete examples including:
  • Hello World workflows
  • Error handling and retries
  • Signal and query patterns
  • Child workflow orchestration
  • Timer and sleep operations
  • Saga pattern implementation

Best Practices

  • Never use time.Now() or rand.Random() directly
  • Use workflow.Now() and workflow.NewRandom()
  • Don’t call external services from workflow code
  • Use activities for all non-deterministic operations
  • Keep activities idempotent when possible
  • Implement heartbeats for long-running activities
  • Use appropriate timeout values
  • Handle transient errors with retry policies
  • Start with default settings and tune based on metrics
  • Monitor queue depth and adjust poller counts
  • Use sticky execution for better performance
  • Implement graceful shutdown handlers
  • Define clear retry policies for activities
  • Use typed errors for better error classification
  • Implement compensation logic for saga patterns
  • Log context information for debugging

Next Steps

Core Concepts

Understand workflows, activities, and task lists

Java Client

Explore the Java SDK for JVM applications

CLI Reference

Manage workflows with the Cadence CLI

Web UI

Monitor workflows visually

Build docs developers (and LLMs) love