diff --git a/docs/develop/go/index.mdx b/docs/develop/go/index.mdx index 15723c7de2..f0152a6a28 100644 --- a/docs/develop/go/index.mdx +++ b/docs/develop/go/index.mdx @@ -96,6 +96,14 @@ Complete Activities asynchronously. - [How to asynchronously complete an Activity](/develop/go/asynchronous-activity-completion) +## [Standalone Activities](/develop/go/standalone-activities) + +Execute Activities independently without a Workflow using the Temporal Client. + +- [How to execute a Standalone Activity](/develop/go/standalone-activities#execute-activity) +- [How to get the result of a Standalone Activity](/develop/go/standalone-activities#get-activity-result) +- [How to get a handle to an existing Standalone Activity](/develop/go/standalone-activities#get-activity-handle) + ## [Versioning](/develop/go/versioning) Change Workflow Definitions without causing non-deterministic behavior in running Workflows. diff --git a/docs/develop/go/standalone-activities.mdx b/docs/develop/go/standalone-activities.mdx new file mode 100644 index 0000000000..9a360ee2cf --- /dev/null +++ b/docs/develop/go/standalone-activities.mdx @@ -0,0 +1,445 @@ +--- +id: standalone-activities +title: Standalone Activities - Go SDK +sidebar_label: Standalone Activities +toc_max_heading_level: 4 +keywords: + - standalone activity + - activity execution + - execute activity + - activity handle + - list activities + - count activities + - go sdk +tags: + - Activities + - Temporal Client + - Go SDK + - Temporal SDKs +description: Execute Activities independently without a Workflow using the Temporal Go SDK. +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Temporal Go SDK support for Standalone Activities is at +[Pre-release](/evaluate/development-production-features/release-stages#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: + +- [Get Started with Standalone Activities](#get-started) +- [Define your Activity](#define-activity) +- [Run a Worker with the Activity registered](#run-worker) +- [Execute a Standalone Activity](#execute-activity) +- [Get the result of a Standalone Activity](#get-activity-result) +- [Get a handle to an existing Standalone Activity](#get-activity-handle) +- [List Standalone Activities](#list-activities) +- [Count Standalone Activities](#count-activities) +- [Run Standalone Activities with Temporal Cloud](#run-standalone-activities-temporal-cloud) + +:::note + +This documentation uses source code from the +[Go sample](https://github.com/temporalio/samples-go/tree/main/standalone-activity/helloworld). + +::: + +## Get Started with Standalone Activities {#get-started} + +To get started, run the Temporal Development Server with Standalone Activities enabled. + +Prerequisites: + +- Install the latest Temporal CLI + + 🚧 Please build from development branch: https://github.com/temporalio/cli/tree/release/v1.6.x-standalone-activity +- [Install the latest Temporal Go SDK](https://docs.temporal.io/develop/go/core-application#install-a-temporal-sdk) + (v1.40.0 or higher recommended) + +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](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](https://github.com/temporalio/samples-go) repository to follow along: + +``` +git clone https://github.com/temporalio/samples-go.git +cd samples-go +``` + +## Define your Activity {#define-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](https://github.com/temporalio/samples-go/blob/main/standalone-activity/helloworld/activity.go) + +```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 {#run-worker} + +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](/develop/go/core-application#develop-worker) for more details on Worker setup and +configuration options. + +[standalone-activity/helloworld/worker/main.go](https://github.com/temporalio/samples-go/blob/main/standalone-activity/helloworld/worker/main.go) + +```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 {#execute-activity} + +Use [`client.ExecuteActivity()`](https://pkg.go.dev/go.temporal.io/sdk/client#Client) 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`](https://pkg.go.dev/go.temporal.io/sdk/client#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](https://github.com/temporalio/samples-go/blob/main/standalone-activity/helloworld/starter/main.go) + +```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: + +```go +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`](https://pkg.go.dev/go.temporal.io/sdk/client#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 {#get-activity-result} + +Use `ActivityHandle.Get()` to block until the Activity completes and retrieve its result. This is analogous to calling +`Get()` on a `WorkflowRun`. + +```go +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 {#get-activity-handle} + +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. + +```go +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 {#list-activities} + +Use [`client.ListActivities()`](https://pkg.go.dev/go.temporal.io/sdk/client#Client) to list Standalone Activity +Executions that match a [List Filter](/list-filter) query. The result contains an iterator that yields +[`ActivityExecutionInfo`](https://pkg.go.dev/go.temporal.io/sdk/client#ActivityExecutionInfo) entries. + +```go +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](/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 {#count-activities} + +Use [`client.CountActivities()`](https://pkg.go.dev/go.temporal.io/sdk/client#Client) to count Standalone Activity +Executions that match a [List Filter](/list-filter) query. + +```go +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 {#run-standalone-activities-temporal-cloud} + +This section assumes you are already familiar with +[how to connect a Worker to Temporal Cloud](https://docs.temporal.io/develop/go/core-application#run-a-temporal-cloud-worker). +The same [source code](https://github.com/temporalio/samples-go/tree/main/standalone-activity/helloworld) 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 \ + --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](https://cloud.temporal.io/namespaces). + +### Run a Worker connected to Temporal Cloud with TLS certificates + +The sample uses [`envconfig.MustLoadDefaultClientOptions()`](https://pkg.go.dev/go.temporal.io/sdk/contrib/envconfig) to +read connection options from [environment variables](/references/client-environment-configuration). Set the following +environment variables before running the Worker: + +``` +export TEMPORAL_ADDRESS=.tmprl.cloud:7233 +export TEMPORAL_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=.tmprl.cloud:7233 +export TEMPORAL_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=..api.temporal.io:7233 +export TEMPORAL_NAMESPACE= +export TEMPORAL_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 +``` diff --git a/docs/develop/python/index.mdx b/docs/develop/python/index.mdx index a5304efaa5..2d8e51bf8e 100644 --- a/docs/develop/python/index.mdx +++ b/docs/develop/python/index.mdx @@ -49,6 +49,14 @@ Connect to a Temporal Service and start a Workflow Execution. - [Connect to Temporal Cloud](/develop/python/temporal-client#connect-to-temporal-cloud) - [Start a Workflow Execution](/develop/python/temporal-client#start-workflow-execution) +## [Standalone Activities](/develop/python/standalone-activities) + +Execute Activities independently without a Workflow using the Temporal Client. + +- [How to execute a Standalone Activity](/develop/python/standalone-activities#execute-activity) +- [How to get the result of a Standalone Activity](/develop/python/standalone-activities#get-activity-result) +- [How to get a handle to an existing Standalone Activity](/develop/python/standalone-activities#get-activity-handle) + ## [Python SDK Sandbox](/develop/python/python-sdk-sandbox) Use third-party Python modules without non-deterministic behavior. diff --git a/docs/develop/python/standalone-activities.mdx b/docs/develop/python/standalone-activities.mdx new file mode 100644 index 0000000000..da01032fea --- /dev/null +++ b/docs/develop/python/standalone-activities.mdx @@ -0,0 +1,295 @@ +--- +id: standalone-activities +title: Standalone Activities - Python SDK +sidebar_label: Standalone Activities +toc_max_heading_level: 4 +keywords: + - standalone activity + - activity execution + - execute activity + - activity handle + - list activities + - count activities + - python sdk +tags: + - Activities + - Temporal Client + - Python SDK + - Temporal SDKs +description: Execute Activities independently without a Workflow using the Temporal Python SDK. +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Temporal Python SDK support for Standalone Activities is at +[Pre-release](/evaluate/development-production-features/release-stages#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, you start a Standalone Activity directly from a Temporal +Client. + +The way you write the Activity Definition and register it with a Worker is identical to [Workflow +Activities](./core-application.mdx#develop-activities). The only difference is that you execute a +Standalone Activity directly from your Temporal Client. + +This page covers the following: + +- [Get Started with Standalone Activities](#get-started) +- [Run a Worker with the Activity registered](#run-worker) +- [Execute a Standalone Activity](#execute-activity) +- [Start a Standalone Activity without waiting for the result](#start-activity) +- [Get a handle to an existing Standalone Activity](#get-activity-handle) +- [Wait for the result of a Standalone Activity](#get-activity-result) +- [List Standalone Activities](#list-activities) +- [Count Standalone Activities](#count-activities) + +:::note + +This documentation uses source code derived from the [Python sample](https://github.com/temporalio/samples-python/blob/main/hello/hello_standalone_activity.py). + + + +::: + +## Get Started with Standalone Activities {#get-started} + +To get started, run the Temporal Development Server with Standalone Activities enabled. + +Prerequisites: + +- Install the latest Temporal CLI + + 🚧 Please build from development branch: https://github.com/temporalio/cli/tree/release/v1.6.x-standalone-activity +- [Install the latest Temporal Python SDK](https://docs.temporal.io/develop/python/set-up-your-local-python#install-the-temporal-python-sdk) + +The first step in running a Standalone Activity involves starting a Temporal server. + +```bash +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 will now be available for client connections on `localhost:7233`, and the +Temporal Web UI will now be accessible at [http://localhost:8233](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-python](https://github.com/temporalio/samples-python) repository to follow along: + +``` +git clone https://github.com/temporalio/samples-python.git +cd samples-python +``` + +## Write an Activity Function {#write-activity} + +An Activity Definition in the Temporal Python SDK is just a normal function with the +`@activity.defn` decorator. It can optionally be an `async def`. The way you write a Standalone +Activity is identical to how you write an Activity to be orchestrated by a Workflow. In fact, an +Activity can be executed both as a Standalone Activity and as a Workflow Activity. + +To see this code in a working example, see the [Standalone Activity +sample](https://github.com/temporalio/samples-python/blob/main/hello/hello_standalone_activity.py). + + +```python +# my_activity.py +from dataclasses import dataclass + +from temporalio import activity + + +@dataclass +class ComposeGreetingInput: + greeting: str + name: str + + +@activity.defn +async def compose_greeting(input: ComposeGreetingInput) -> str: + activity.logger.info("Running activity with parameter %s" % input) + return f"{input.greeting}, {input.name}!" +``` + +## Run a Worker with the Activity registered {#run-worker} + +Running a Worker for Standalone Activities is the same as running a Worker for Workflow Activities β€” +you create a Worker, register the Activity, and run the Worker. The Worker doesn't need to know +whether the Activity will be invoked from a Workflow or as a Standalone Activity. See [How to run a +Worker](/develop/python/core-application#run-a-dev-worker) for more details on Worker setup and +configuration options. + +To see this code in a working example, see the [Standalone Activity +sample](https://github.com/temporalio/samples-python/blob/main/hello/hello_standalone_activity.py). + +```python +import asyncio + +from my_activity import compose_greeting +from temporalio.client import Client +from temporalio.worker import Worker + + +async def main(): + client = await Client.connect("localhost:7233") + worker = Worker( + client, + task_queue="hello-standalone-activity-task-queue", + activities=[compose_greeting], + ) + await worker.run() + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## Execute a Standalone Activity {#execute-activity} + +Use +[`client.execute_activity()`](https://python.temporal.io/temporalio.client.Client.html#execute_activity) +to execute a Standalone Activity. Call this from your application code, not from inside a Workflow +Definition. This durably enqueues your Standalone Activity in the Temporal Server, waits for it to +be executed on your Worker, and then fetches the result: + +```python +import asyncio +from datetime import timedelta + +from temporalio.client import Client + +from my_activity import ComposeGreetingInput, compose_greeting + + +async def my_application(): + client = await Client.connect("localhost:7233") + + result = await client.execute_activity( + compose_greeting, + args=[ComposeGreetingInput("Hello", "World")], + id="my-standalone-activity-id", + task_queue="hello-standalone-activity-task-queue", + start_to_close_timeout=timedelta(seconds=10), + ) + print(f"Activity result: {result}") + + +if __name__ == "__main__": + asyncio.run(my_application()) +``` + + +## Start a Standalone Activity without waiting for the result {#start-activity} + +Starting a Standalone Activity means sending a request to the Temporal Server to durably enqueue +your Activity job, without waiting for it to be executed by your Worker. + +Use +[`client.start_activity()`](https://python.temporal.io/temporalio.client.Client.html#start_activity) +to start your Standalone Activity and get a handle: + +```python +activity_handle = await client.start_activity( + compose_greeting, + args=[ComposeGreetingInput("Hello", "World")], + id="my-standalone-activity-id", + task_queue="hello-standalone-activity-task-queue", + start_to_close_timeout=timedelta(seconds=10), +) +``` + +You can also use `client.get_activity_handle()` to create a handle to a previously started Standalone Activity: + +```python +activity_handle = client.get_activity_handle( + activity_id="my-standalone-activity-id", + run_id="the-run-id", +) +``` + +You can now use the handle to wait for the result, describe, cancel, or terminate the Activity. + +## Wait for the result of a Standalone Activity {#get-activity-result} + +Under the hood, calling `client.execute_activity()` is the same as calling +[`client.start_activity()`](https://python.temporal.io/temporalio.client.Client.html#start_activity) +to durably enqueue the Standalone Activity, and then calling `await activity_handle.result()` to +wait for the activity to be executed and fetch the result: + +```python +activity_result = await activity_handle.result() +``` + + +## List Standalone Activities {#list-activities} + +Use +[`client.list_activities()`](https://python.temporal.io/temporalio.client.Client.html#list_activities) +to list Standalone Activity Executions that match a [List Filter](/list-filter) query. The result is +an async iterator that yields ActivityExecution entries: + +```python +import asyncio + +from temporalio.client import Client + + +async def my_application(): + client = await Client.connect("localhost:7233") + + activities = client.list_activities( + query="TaskQueue = 'my-task-queue'", + ) + + async for info in activities: + print( + f"ActivityID: {info.activity_id}, Type: {info.activity_type}, Status: {info.status}" + ) + + +if __name__ == "__main__": + asyncio.run(my_application()) +``` + +The query parameter accepts the same [List Filter](/list-filter) syntax used for [Workflow +Visibility](/visibility). For example, "ActivityType = 'MyActivity' AND Status = 'Running'". + + +## Count Standalone Activities {#count-activities} + +Use [`client.count_activities()`](https://python.temporal.io/temporalio.client.Client.html#count_activities) to count +Standalone Activity Executions that match a [List Filter](/list-filter) query. + +```python +import asyncio + +from temporalio.client import Client + + +async def my_application(): + client = await Client.connect("localhost:7233") + + resp = await client.count_activities( + query="TaskQueue = 'my-task-queue'", + ) + + print("Total activities:", resp.count) + + for group in resp.groups: + print(f"Group {group.group_values}: {group.count}") + + +if __name__ == "__main__": + asyncio.run(my_application()) +``` + +{/* ## Run Standalone Activities with Temporal Cloud {#run-standalone-activities-temporal-cloud} */} + +{/* TODO: Add Temporal Cloud section for Python SDK */} diff --git a/docs/encyclopedia/activities/activities.mdx b/docs/encyclopedia/activities/activities.mdx index b3dd08b612..b2683ca9e3 100644 --- a/docs/encyclopedia/activities/activities.mdx +++ b/docs/encyclopedia/activities/activities.mdx @@ -3,8 +3,8 @@ id: activities title: What is a Temporal Activity? sidebar_label: Activities description: - Understand Temporal Activities, including Activity Definitions, Types, Executions, idempotency, cancellations, and - Local Activities. + Understand Temporal Activities, including Activity Definitions, Types, Executions, idempotency, cancellations, Local Activities, and + Standalone Activities. slug: /activities toc_max_heading_level: 4 keywords: @@ -19,7 +19,7 @@ tags: This guide provides a comprehensive overview of Temporal Activities including [Activity Definition](/activity-definition), [Activity Type](/activity-definition#activity-type), -[Activity Execution](/activity-execution), and [Local Activity](/local-activity). +[Activity Execution](/activity-execution), [Local Activity](/local-activity), and [Standalone Activity](/standalone-activity). An Activity is a normal function or method that executes a single, well-defined action (either short or long running), such as calling another service, transcoding a media file, or sending an email message. Activity code can be @@ -34,11 +34,14 @@ Activities are the most common Temporal primitive and encompass small units of w Larger pieces of functionality should be broken up into multiple activities. This makes it easier to do failure recovery, have short timeouts, and be idempotent. -Workflow code orchestrates the execution of Activities, persisting the results. If an Activity Function Execution fails, -any future execution starts from initial state (except -[Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat)). +Workflow code orchestrates the execution of Activities, persisting the results. If an Activity Execution fails, +any future attempt will start from the initial state, unless your code uses ([Heartbeat details payloads](/encyclopedia/detecting-activity-failures#activity-heartbeat)) +for checkpointing (storing state on the server, and using it when resuming subsequent attempts). Activity Functions are executed by Worker Processes. When the Activity Function returns, the Worker sends the results back to the Temporal Service as part of the [ActivityTaskCompleted](/references/events#activitytaskcompleted) Event. The Event is added to the Workflow Execution's Event History. For other Activity-related Events, see [Activity Events](/workflow-execution/event#activity-events). + +If you only want to execute one Activity Function, then you don't need to use a Workflow: you can +use your SDK Client to invoke it directly as a [Standalone Activity](/standalone-activity). diff --git a/docs/encyclopedia/activities/activity-execution.mdx b/docs/encyclopedia/activities/activity-execution.mdx index 5696d66a19..3e9a65c782 100644 --- a/docs/encyclopedia/activities/activity-execution.mdx +++ b/docs/encyclopedia/activities/activity-execution.mdx @@ -22,6 +22,7 @@ This page discusses the following: - [Activity Execution](#activity-execution) - [Cancellation](#cancellation) - [Activity Id](#activity-id) +- [Isolated ID Space for Standalone Activities](#isolated-id-space) - [Asynchronous Activity Completion](#asynchronous-activity-completion) - [Task Token](#task-token) @@ -46,8 +47,9 @@ You can customize [Activity Execution timeouts](/encyclopedia/detecting-activity [retry policies](/encyclopedia/retry-policies). If an Activity Execution fails (because it exhausted all retries, threw a -[non-retryable error](/encyclopedia/retry-policies#non-retryable-errors), or was canceled), the error is returned to the -[Workflow](/workflows), which decides how to handle it. +[non-retryable error](/encyclopedia/retry-policies#non-retryable-errors), or was canceled), the error is returned to your +[Workflow](/workflows) code when it attempts to fetch the Activity result. For [Standalone Activities](/standalone-activity) the error is +returned to the Client when you attempt to fetch the Activity result. :::note @@ -113,6 +115,11 @@ an Activity Id if an earlier Activity Execution with the same Id has closed.) An Activity Id can be used to [complete the Activity asynchronously](#asynchronous-activity-completion). +## Isolated ID Space for Standalone Activities {#isolated-id-space} + +[Standalone Activities](/standalone-activity) have a separate ID space from Workflows and other Temporal primitives. +This means use of conflict policy (`USE_EXISTING`, …) and reuse policy (`REJECT_DUPLICATES`, …) will only observe the Standalone Activity ID space. + ## What is Asynchronous Activity Completion? {#asynchronous-activity-completion} Asynchronous Activity Completion is a feature that enables an Activity Function to return without causing the Activity diff --git a/docs/encyclopedia/activities/standalone-activity.mdx b/docs/encyclopedia/activities/standalone-activity.mdx new file mode 100644 index 0000000000..ee8e072b5a --- /dev/null +++ b/docs/encyclopedia/activities/standalone-activity.mdx @@ -0,0 +1,97 @@ +--- +id: standalone-activity +title: Standalone Activity +sidebar_label: Standalone Activity +description: Learn about Standalone Activities in Temporal, their benefits, execution model, and when to use them. +slug: /standalone-activity +toc_max_heading_level: 4 +keywords: + - standalone activity + - explanation + - term + - timeouts +tags: + - Concepts + - Activities + - Durable Execution +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +Standalone Activities are available as a [Pre-release](/evaluate/development-production-features/release-stages#pre-release) feature in [Temporal Cloud](#temporal-cloud-support) and in a [special release](#temporal-cli-support) of the Temporal CLI. + +::: + +See [limitations](#pre-release-limitations) below. + +## What is a Standalone Activity? {#standalone-activity} + +A top-level [Activity Execution](/activity-execution) that is started directly by a +[Client](/encyclopedia/temporal-sdks#temporal-client), without using a Workflow, is called a +Standalone Activity. + +Standalone Activities are Temporal's [job queue](/evaluate/development-production-features/job-queue). + +If you need to orchestrate multiple Activities, then you should use a Workflow. But if you just need +to execute a single Activity, then you can use a Standalone Activity. This will result in fewer +[Billable Actions](/cloud/actions#actions-in-workflows) in Temporal Cloud than using a Workflow to +run a single Activity. If your Activity Execution is short-lived, then you will also notice lower +latency, since there are fewer worker round-trips than when executing the Activity in a Workflow. + +Standalone Activities support the same retry policies and timeouts as Workflow Activities, and you +write your Activity Functions in the same way for both. In fact, an Activity Function can be +executed both as a Standalone Activity and as a Workflow Activity. + +## Use cases + +Standalone Activities can be used for [durable job processing use cases](/evaluate/development-production-features/job-queue) such as sending an email, processing a webhook, syncing data, or executing a single function reliably with built-in retries and timeouts. + +## Key features +- Execute any Temporal Activity as a top-level primitive without the overhead of a Workflow +- Native async job processing model: schedule -> dispatch -> process -> result +- No head-of-line blocking - a slow job doesn’t block the dispatch of other Tasks +- Arbitrary length jobs with heartbeats for liveness and checkpointing progress +- At-least-once execution by default with native retry policy and timeouts +- At-most-once execution if retry max attempts is 1 +- Addressable - get an Activity ID / Run ID and get the result, cancel, and terminate +- Deduplication - with conflict policy: (USE_EXISTING, …), reuse policy: (REJECT_DUPLICATES, …) +- Separate ID space from Workflows - Standalone Activities are a different kind of top-level execution +- Priority and fairness - multi-tenant fairness, weighted priority tiers, and safeguards against starvation of lower-weighted tasks +- Visibility - list Activity Executions and view status, retry count, and last error +- Manual completion by ID (or token): ignore activity return and wait for external completion +- Activity metrics - including counts for success, failure, timeout, and cancel +- Dual use - execute Activities within a Workflow or standalone with no Worker code changes + +## Pre-release limitations + +The pre-release of Standalone Activities is recommended for experimental use only and has some known limitations. + +General limitations: +- Not suitable for production use cases during Pre-release +- Delete, pause, reset, and update options are not supported yet +- The `TerminateExisting` conflict policy and `TerminateIfRunning` reuse policy are not supported yet +- Heartbeat Timeout must be explicitly set by the caller if using heartbeats in an Activity Definition, otherwise it will fail immediately + +Temporal Cloud limitations: +- Standalone Activities are free for evaluation purposes during Pre-release, so we reserve the right to limit usage if it exceeds a reasonable amount +- 1 day max retention for Standalone Activities +- We recommend enabling Standalone Activities on a new namespace for dev/test experimental use only + +## Temporal CLI support + +A [special release of the Temporal CLI](https://TODO) supports Standalone Activities during Pre-release: +- The `temporal activity` subcommand supports Standalone Activities with commands including: `start`, `result`, and `list` +- Temporal Dev Server has Standalone Activities enabled by default in this special CLI release for local testing + +## Temporal Cloud support + +Contact your account team to enable Standalone Activities in Temporal Cloud as a Pre-release feature. + +## Get started + +:::tip RESOURCES + +- [Go SDK - Standalone Activities quick start and code sample](/develop/go/standalone-activities) +- [Python SDK - Standalone Activities quick start and code sample](/develop/python/standalone-activities) + + ::: diff --git a/docs/encyclopedia/detecting-activity-failures.mdx b/docs/encyclopedia/detecting-activity-failures.mdx index 9a2355d719..3213dd59bb 100644 --- a/docs/encyclopedia/detecting-activity-failures.mdx +++ b/docs/encyclopedia/detecting-activity-failures.mdx @@ -185,7 +185,7 @@ Activity Heartbeats are implemented within the Activity Definition. Custom progress information can be included in the Heartbeat which can then be used by the Activity Execution should a retry occur. An Activity Heartbeat can be recorded as often as needed (e.g. once a minute or every loop iteration). -It is often a good practice to Heartbeat on anything but the shortest Activity Function Execution. +It is often a good practice to Heartbeat on anything but the shortest Activity Execution. Temporal SDKs control the rate at which Heartbeats are sent to the Temporal Service. Heartbeating is not required from [Local Activities](/local-activity), and does nothing. diff --git a/docs/evaluate/development-production-features/job-queue.mdx b/docs/evaluate/development-production-features/job-queue.mdx new file mode 100644 index 0000000000..549e87a1a9 --- /dev/null +++ b/docs/evaluate/development-production-features/job-queue.mdx @@ -0,0 +1,60 @@ +--- +id: job-queue +title: Job Queue +sidebar_label: Job Queue +description: Standalone Activities adds the ability to execute any Temporal Activity as a top-level primitive without the full overhead of a Workflow. +keywords: + - job queue + - job system + - standalone activities + - background jobs + - durable execution +tags: + - Features + - Standalone Activities +--- + +import { RelatedReadContainer, RelatedReadItem } from '@site/src/components'; + +## What is a Job Queue? + +A job is a single, discrete unit of work that runs asynchronously in the background such as sending an email, processing a webhook, syncing data, or executing a single function reliably. + +A job queue is the system that manages these jobs: accepting work, dispatching it to workers, retrying on failure, and providing visibility into what's running and what failed. + +**Standalone Activities are Temporal's job queue.** + +They let you use Temporal Activities as background jobs, in addition to using the same Activities as steps inside a Workflow. You write an Activity once and can run it either as a background job or as part of a multi-step Workflow. + +Temporal provides stronger guarantees, better visibility, and more control than traditional job queues - while remaining cost-effective for high-volume use cases and offering a clean upgrade path to multi-step workflow orchestration. + +### Overview + +Standalone Activities add the ability to execute any Temporal Activity as a top-level Activity Execution for durable job processing. + +#### Unified programming model & worker deployment + +- Write an Activity once and use it anywhere - with a unified Activity programming model +- Optional heartbeats support checkpointing for long-running jobs +- Deploy to an Activity Worker once, and invoke standalone or from within a Workflow + +#### Execution lifecycle + +- Jobs are submitted as Standalone Activity Executions +- Each job is durably persisted with Temporal reliability, so jobs are not lost +- Jobs are scheduled with priority, fairness, deduplication and no head-of-line blocking +- Workers poll task queues and execute Activities (you run your own Workers) +- Temporal ensures retries, timeouts, and exponential backoff policy is enforced + +#### Observability & lifecycle controls + +- Full job visibility (list, search) with detailed execution state, retry count, errors & results +- OpenMetrics support +- Lifecycle controls: cancel, pause, unpause, reset, terminate +- Manual completion for external integrations & on-call management + + + + + + \ No newline at end of file diff --git a/sidebars.js b/sidebars.js index 567a21a3fd..6205e39aef 100644 --- a/sidebars.js +++ b/sidebars.js @@ -36,6 +36,7 @@ module.exports = { 'evaluate/development-production-features/cloud-automation', 'evaluate/development-production-features/low-latency', 'evaluate/development-production-features/multi-tenancy', + 'evaluate/development-production-features/job-queue', { type: 'category', label: 'Product release stages', @@ -98,6 +99,7 @@ module.exports = { 'develop/go/set-up-your-local-go', 'develop/go/core-application', 'develop/go/temporal-client', + 'develop/go/standalone-activities', 'develop/go/go-sdk-multithreading', 'develop/go/namespaces', 'develop/go/testing-suite', @@ -193,6 +195,7 @@ module.exports = { 'develop/python/set-up-your-local-python', 'develop/python/core-application', 'develop/python/temporal-client', + 'develop/python/standalone-activities', 'develop/python/python-sdk-sandbox', 'develop/python/python-sdk-sync-vs-async', 'develop/python/testing-suite', @@ -705,6 +708,7 @@ module.exports = { 'encyclopedia/activities/activity-definition', 'encyclopedia/activities/activity-execution', 'encyclopedia/activities/local-activity', + 'encyclopedia/activities/standalone-activity', ], }, { diff --git a/static/img/standalone-activities-ui-nav.png b/static/img/standalone-activities-ui-nav.png new file mode 100644 index 0000000000..733e4f6f2e Binary files /dev/null and b/static/img/standalone-activities-ui-nav.png differ