diff --git a/README.md b/README.md index e00f49c..34a0c07 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Prerequisites: * [rails_app](rails_app) - Basic Rails API application using Temporal workflows and activities. * [saga](saga) - Using undo/compensation using a very simplistic Saga pattern. * [sorbet_generic](sorbet_generic) - Proof of concept of how to do _advanced_ Sorbet typing with the SDK. +* [standalone_activity](standalone_activity) - Execute, start, list, and count Standalone Activities -- Activities run directly from a Client without a Workflow. * [updatable_timer](updatable_timer) - Demonstrates a blocking sleep that can be updated. * [worker_specific_task_queues](worker_specific_task_queues) - Use a unique Task Queue for each Worker to run a sequence of Activities on the same Worker. * [worker_versioning](worker_versioning) - Use the Worker Versioning feature to more easily version your workflows & other code. diff --git a/standalone_activity/README.md b/standalone_activity/README.md new file mode 100644 index 0000000..270c303 --- /dev/null +++ b/standalone_activity/README.md @@ -0,0 +1,74 @@ +# Standalone Activity + +This sample demonstrates [Standalone Activities](https://docs.temporal.io/standalone-activity): +Activities executed directly from a Client, without a Workflow. + +To run, first see [README.md](../README.md) for prerequisites. Standalone Activities require +Temporal CLI v1.7.0+ and Temporal Server v1.31.0+. + +Start a Temporal dev server in one terminal: + + temporal server start-dev + +In another terminal, start the Worker from this directory: + + bundle exec ruby worker.rb + +The Worker registers `ComposeGreeting` and polls the `standalone-activity-sample` Task Queue. +No Workflows are required. + +## Execute a Standalone Activity + +Run an Activity from the Client and block until the result is returned: + + bundle exec ruby execute_activity.rb + +Expected output: + + Activity result: Hello, World! + +Or use the Temporal CLI: + + temporal activity execute \ + --type ComposeGreeting \ + --activity-id standalone-activity-id \ + --task-queue standalone-activity-sample \ + --start-to-close-timeout 10s \ + --input '"Hello"' \ + --input '"World"' + +## Start a Standalone Activity without waiting + +Start an Activity, get back an `ActivityHandle`, and fetch the result later: + + bundle exec ruby start_activity.rb + +Or use the Temporal CLI: + + temporal activity start \ + --type ComposeGreeting \ + --activity-id standalone-activity-id \ + --task-queue standalone-activity-sample \ + --start-to-close-timeout 10s \ + --input '"Hello"' \ + --input '"World"' + +## List Standalone Activities + +List Standalone Activity Executions matching a [List Filter](https://docs.temporal.io/list-filter): + + bundle exec ruby list_activities.rb + +Or use the Temporal CLI: + + temporal activity list --query "TaskQueue = 'standalone-activity-sample'" + +## Count Standalone Activities + +Count Standalone Activity Executions matching a List Filter: + + bundle exec ruby count_activities.rb + +Or use the Temporal CLI: + + temporal activity count --query "TaskQueue = 'standalone-activity-sample'" diff --git a/standalone_activity/count_activities.rb b/standalone_activity/count_activities.rb new file mode 100644 index 0000000..f0387c1 --- /dev/null +++ b/standalone_activity/count_activities.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'temporalio/client' +require 'temporalio/env_config' + +# Load config and apply defaults +args, kwargs = Temporalio::EnvConfig::ClientConfig.load_client_connect_options +args[0] ||= 'localhost:7233' # Default address +args[1] ||= 'default' # Default namespace + +client = Temporalio::Client.connect(*args, **kwargs) + +# Count Standalone Activity Executions matching a query. Only Standalone +# Activity Executions are counted -- Activities scheduled inside Workflows +# are not. +result = client.count_activities("TaskQueue = 'standalone-activity-sample'") +puts "Total: #{result.count}" +result.groups.each do |group| + puts " #{group.group_values.join(',')} => #{group.count}" +end diff --git a/standalone_activity/execute_activity.rb b/standalone_activity/execute_activity.rb new file mode 100644 index 0000000..7175637 --- /dev/null +++ b/standalone_activity/execute_activity.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'temporalio/client' +require 'temporalio/env_config' +require_relative 'my_activities' + +# Load config and apply defaults +args, kwargs = Temporalio::EnvConfig::ClientConfig.load_client_connect_options +args[0] ||= 'localhost:7233' # Default address +args[1] ||= 'default' # Default namespace + +client = Temporalio::Client.connect(*args, **kwargs) + +# Execute a Standalone Activity directly from the Client and block until it +# returns a result. The Activity is durably enqueued on the Server and run by +# the Worker registered for the same Task Queue. +result = client.execute_activity( + StandaloneActivity::MyActivities::ComposeGreeting, + 'Hello', 'World', + id: 'standalone-activity-id', + task_queue: 'standalone-activity-sample', + start_to_close_timeout: 10 +) +puts "Activity result: #{result}" diff --git a/standalone_activity/list_activities.rb b/standalone_activity/list_activities.rb new file mode 100644 index 0000000..a80e278 --- /dev/null +++ b/standalone_activity/list_activities.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'temporalio/client' +require 'temporalio/env_config' + +# Load config and apply defaults +args, kwargs = Temporalio::EnvConfig::ClientConfig.load_client_connect_options +args[0] ||= 'localhost:7233' # Default address +args[1] ||= 'default' # Default namespace + +client = Temporalio::Client.connect(*args, **kwargs) + +# List Standalone Activity Executions on this Task Queue. Only Standalone +# Activity Executions are returned -- Activities scheduled inside Workflows +# are not. +client.list_activities("TaskQueue = 'standalone-activity-sample'").each do |execution| + puts "#{execution.activity_id} #{execution.activity_type} #{execution.status}" +end diff --git a/standalone_activity/my_activities.rb b/standalone_activity/my_activities.rb new file mode 100644 index 0000000..89e088a --- /dev/null +++ b/standalone_activity/my_activities.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'temporalio/activity' + +module StandaloneActivity + module MyActivities + class ComposeGreeting < Temporalio::Activity::Definition + def execute(greeting, name) + "#{greeting}, #{name}!" + end + end + end +end diff --git a/standalone_activity/start_activity.rb b/standalone_activity/start_activity.rb new file mode 100644 index 0000000..066bbea --- /dev/null +++ b/standalone_activity/start_activity.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'temporalio/client' +require 'temporalio/env_config' +require_relative 'my_activities' + +# Load config and apply defaults +args, kwargs = Temporalio::EnvConfig::ClientConfig.load_client_connect_options +args[0] ||= 'localhost:7233' # Default address +args[1] ||= 'default' # Default namespace + +client = Temporalio::Client.connect(*args, **kwargs) + +# Start a Standalone Activity without waiting for the result. The call returns +# as soon as the Activity is durably enqueued on the Server. +handle = client.start_activity( + StandaloneActivity::MyActivities::ComposeGreeting, + 'Hello', 'World', + id: 'standalone-activity-id', + task_queue: 'standalone-activity-sample', + start_to_close_timeout: 10 +) +puts "Started Activity with id=#{handle.id} run_id=#{handle.run_id}" + +# Wait for the result later via the handle. +puts "Activity result: #{handle.result}" diff --git a/standalone_activity/worker.rb b/standalone_activity/worker.rb new file mode 100644 index 0000000..9d9b951 --- /dev/null +++ b/standalone_activity/worker.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'logger' +require 'temporalio/client' +require 'temporalio/env_config' +require 'temporalio/worker' +require_relative 'my_activities' + +# Load config and apply defaults +args, kwargs = Temporalio::EnvConfig::ClientConfig.load_client_connect_options +args[0] ||= 'localhost:7233' # Default address +args[1] ||= 'default' # Default namespace + +client = Temporalio::Client.connect(*args, **kwargs, logger: Logger.new($stdout, level: Logger::INFO)) + +# A Worker for Standalone Activities is configured the same way as one for +# Workflow Activities: register the Activity classes and run the Worker. No +# Workflows are required. +worker = Temporalio::Worker.new( + client:, + task_queue: 'standalone-activity-sample', + activities: [StandaloneActivity::MyActivities::ComposeGreeting] +) + +puts 'Starting worker (ctrl+c to exit)' +worker.run(shutdown_signals: ['SIGINT'])