Skip to main content

Standalone Activities - Go SDK

SUPPORT, STABILITY, and DEPENDENCY INFO

Temporal Go SDK support for Standalone Activities is at Pre-release.

All APIs are experimental and may be subject to backwards-incompatible changes.

Standalone Activities are Activity Executions that run independently, without being orchestrated by a Workflow. Instead of starting an Activity from within a Workflow Definition using workflow.ExecuteActivity(), you start a Standalone Activity directly from a Temporal Client using client.ExecuteActivity().

The Activity definition and Worker registration are identical to regular Activities, and only the execution path differs.

This page covers the following:

note

This documentation uses source code from the Go sample.

Run the Temporal Development Server with Standalone Activities enabled

Prerequisites:

The first step in running a Standalone Activity involves starting a Temporal server.

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 Server should now be available for client connections on localhost:7233, and the Temporal Web UI should now be accessible at http://localhost:8233. Standalone Activities are available from the nav bar item located towards the top left of the page:

Standalone Activities Web UI nav bar item

Clone the samples-go repository to follow along:

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

Define your Activity

Define your Activity in a shared file so that both the Worker and starter can reference it.

The sample project is structured as follows:

standalone-activity/helloworld/
├── activity.go
├── worker/
│ └── main.go
└── starter/
└── main.go

standalone-activity/helloworld/activity.go

package helloworld

import (
"context"
"go.temporal.io/sdk/activity"
)

func Activity(ctx context.Context, name string) (string, error) {
logger := activity.GetLogger(ctx)
logger.Info("Activity", "name", name)
return "Hello " + name + "!", nil
}

Run a Worker with the Activity registered

Running a Worker for Standalone Activities is the same as running a Worker for Workflow-driven Activities — you create a Worker, register the Activity, and call Run(). The Worker doesn't need to know whether the Activity will be invoked from a Workflow or as a Standalone Activity.

See How to develop a Worker in Go for more details on Worker setup and configuration options.

standalone-activity/helloworld/worker/main.go

package main

import (
"github.com/temporalio/samples-go/standalone-activity/helloworld"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/contrib/envconfig"
"go.temporal.io/sdk/worker"
"log"
)

func main() {
c, err := client.Dial(envconfig.MustLoadDefaultClientOptions())
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()

w := worker.New(c, "standalone-activity-helloworld", worker.Options{})

w.RegisterActivity(helloworld.Activity)

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

To run the Worker:

go run standalone-activity/helloworld/worker/main.go

Execute a Standalone Activity

Use client.ExecuteActivity() to start a Standalone Activity Execution. This is called from application code (for example, a starter program), not from inside a Workflow Definition.

ExecuteActivity returns an ActivityHandle that you can use to get the result, describe, cancel, or terminate the Activity.

The following starter program demonstrates how to execute a Standalone Activity, get its result, list activities, and count activities:

standalone-activity/helloworld/starter/main.go

package main

import (
"context"
"github.com/temporalio/samples-go/standalone-activity/helloworld"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/contrib/envconfig"
"log"
"time"
)

func main() {
c, err := client.Dial(envconfig.MustLoadDefaultClientOptions())
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()

activityOptions := client.StartActivityOptions{
ID: "standalone_activity_helloworld_ActivityID",
TaskQueue: "standalone-activity-helloworld",
ScheduleToCloseTimeout: 10 * time.Second,
}

handle, err := c.ExecuteActivity(context.Background(), activityOptions, helloworld.Activity, "Temporal")
if err != nil {
log.Fatalln("Unable to execute activity", err)
}

log.Println("Started standalone activity", "ActivityID", handle.GetID(), "RunID", handle.GetRunID())

var result string
err = handle.Get(context.Background(), &result)
if err != nil {
log.Fatalln("Unable get standalone activity result", err)
}
log.Println("Activity result:", result)

resp, err := c.ListActivities(context.Background(), client.ListActivitiesOptions{
Query: "TaskQueue = 'standalone-activity-helloworld'",
})
if err != nil {
log.Fatalln("Unable to list activities", err)
}

log.Println("ListActivity results")
for info, err := range resp.Results {
if err != nil {
log.Fatalln("Error iterating activities", err)
}
log.Printf("\tActivityID: %s, Type: %s, Status: %v\n",
info.ActivityID, info.ActivityType, info.Status)
}

resp1, err := c.CountActivities(context.Background(), client.CountActivitiesOptions{
Query: "TaskQueue = 'standalone-activity-helloworld'",
})
if err != nil {
log.Fatalln("Unable to count activities", err)
}

log.Println("Total activities:", resp1.Count)
}

You can pass the Activity as either a function reference or a string Activity type name:

handle, err := c.ExecuteActivity(ctx, options, helloworld.Activity, "arg1")

// Using a string type name
handle, err := c.ExecuteActivity(ctx, options, "Activity", "arg1")

client.StartActivityOptions requires ID, TaskQueue, and at least one of ScheduleToCloseTimeout or StartToCloseTimeout. See StartActivityOptions in the API reference for the full set of options.

To run the starter (in a separate terminal from the Worker):

go run standalone-activity/helloworld/starter/main.go

Get the result of a Standalone Activity

Use ActivityHandle.Get() to block until the Activity completes and retrieve its result. This is analogous to calling Get() on a WorkflowRun.

var result string
err = handle.Get(context.Background(), &result)
if err != nil {
log.Fatalln("Activity failed", err)
}
log.Println("Activity result:", result)

If the Activity completed successfully, the result is deserialized into the provided pointer. If the Activity failed, the failure is returned as an error.

Get a handle to an existing Standalone Activity

Use client.GetActivityHandle() to create a handle to a previously started Standalone Activity. This is analogous to client.GetWorkflow() for Workflow Executions.

Both ActivityID and RunID are required.

handle := c.GetActivityHandle(client.GetActivityHandleOptions{
ActivityID: "my-standalone-activity-id",
RunID: "the-run-id",
})

// Use the handle to get the result, describe, cancel, or terminate
var result string
err := handle.Get(context.Background(), &result)
if err != nil {
log.Fatalln("Unable to get activity result", err)
}

List Standalone Activities

Use client.ListActivities() to list Standalone Activity Executions that match a List Filter query. The result contains an iterator that yields ActivityExecutionInfo entries.

resp, err := c.ListActivities(context.Background(), client.ListActivitiesOptions{
Query: "TaskQueue = 'standalone-activity-helloworld'",
})
if err != nil {
log.Fatalln("Unable to list activities", err)
}

for info, err := range resp.Results {
if err != nil {
log.Fatalln("Error iterating activities", err)
}
log.Printf("ActivityID: %s, Type: %s, Status: %v\n",
info.ActivityID, info.ActivityType, info.Status)
}

The Query field accepts the same List Filter syntax used for Workflow Visibility. For example, "ActivityType = 'Activity' AND Status = 'Running'".

You can also list activities using the Temporal CLI:

temporal activity list

Count Standalone Activities

Use client.CountActivities() to count Standalone Activity Executions that match a List Filter query.

resp, err := c.CountActivities(context.Background(), client.CountActivitiesOptions{
Query: "TaskQueue = 'standalone-activity-helloworld'",
})
if err != nil {
log.Fatalln("Unable to count activities", err)
}

log.Println("Total activities:", resp.Count)

Run Standalone Activities with Temporal Cloud

This section assumes you are already familiar with how to connect a Worker to Temporal Cloud. The same source code is used in this section. The tcld CLI will be used to create a Namespace, and mTLS client certificates will be used to securely connect the Worker to Temporal Cloud.

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 your Namespace

If you don't already have a Namespace, create one for your Standalone Activities:

tcld login

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

Alternatively, you can create a Namespace through the UI: https://cloud.temporal.io/namespaces.

Run a Worker connected to Temporal Cloud with TLS certificates

The sample uses envconfig.MustLoadDefaultClientOptions() to read connection options from environment variables. Set the following environment variables before running the Worker:

export TEMPORAL_ADDRESS=<your-namespace>.tmprl.cloud:7233
export TEMPORAL_NAMESPACE=<your-namespace>
export TEMPORAL_TLS_CLIENT_CERT_PATH='path/to/your/ca.pem'
export TEMPORAL_TLS_CLIENT_KEY_PATH='path/to/your/ca.key'

Then run the Worker:

go run standalone-activity/helloworld/worker/main.go

Execute a Standalone Activity on Temporal Cloud

In a separate terminal, set the same environment variables and run the starter:

export TEMPORAL_ADDRESS=<your-namespace>.tmprl.cloud:7233
export TEMPORAL_NAMESPACE=<your-namespace>
export TEMPORAL_TLS_CLIENT_CERT_PATH='path/to/your/ca.pem'
export TEMPORAL_TLS_CLIENT_KEY_PATH='path/to/your/ca.key'

go run standalone-activity/helloworld/starter/main.go

Run a Worker connected to Temporal Cloud with API keys

Alternatively, you can use an API key instead of TLS certificates:

export TEMPORAL_ADDRESS=<region>.<cloud_provider>.api.temporal.io:7233
export TEMPORAL_NAMESPACE=<your-namespace>
export TEMPORAL_API_KEY=<your-api-key>

Then run the Worker and starter the same way:

go run standalone-activity/helloworld/worker/main.go
go run standalone-activity/helloworld/starter/main.go