diff --git a/Gemfile b/Gemfile index b42ef5e..becd54d 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,14 @@ source 'https://rubygems.org' +# Primary Temporal dependency gem 'temporalio' +# Some samples require certain dependencies, so they are in groups below alphabetically by group +# TODO(cretz): Move this to coinbase/temporal-ruby and off this fork/branch +# when https://github.com/coinbase/temporal-ruby/pull/335 merged, and then update the coinbase_ruby README +gem 'temporal-ruby', github: 'cretz/coinbase-temporal-ruby', branch: 'disable-proto-load-option', group: :coinbase_ruby + group :development do gem 'minitest' gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock index a73699d..65ea626 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,30 +1,48 @@ +GIT + remote: https://github.com/cretz/coinbase-temporal-ruby.git + revision: debe21843b6214995fdcb1e4ea052f2b1075cc18 + branch: disable-proto-load-option + specs: + temporal-ruby (0.1.1) + base64 + grpc + oj + GEM remote: https://rubygems.org/ specs: ast (2.4.2) + base64 (0.2.0) bigdecimal (3.1.9) - google-protobuf (4.30.2) - bigdecimal - rake (>= 13) - google-protobuf (4.30.2-aarch64-linux) - bigdecimal - rake (>= 13) - google-protobuf (4.30.2-arm64-darwin) - bigdecimal - rake (>= 13) - google-protobuf (4.30.2-x86-linux) - bigdecimal - rake (>= 13) - google-protobuf (4.30.2-x86_64-darwin) - bigdecimal - rake (>= 13) - google-protobuf (4.30.2-x86_64-linux) - bigdecimal - rake (>= 13) + google-protobuf (3.25.7) + googleapis-common-protos-types (1.19.0) + google-protobuf (>= 3.18, < 5.a) + grpc (1.71.0) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.71.0-aarch64-linux) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.71.0-arm64-darwin) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.71.0-x86-linux) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.71.0-x86_64-darwin) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) + grpc (1.71.0-x86_64-linux) + google-protobuf (>= 3.25, < 5.0) + googleapis-common-protos-types (~> 1.0) json (2.9.1) language_server-protocol (3.17.0.3) logger (1.7.0) minitest (5.25.4) + oj (3.16.10) + bigdecimal (>= 3.0) + ostruct (>= 0.2) + ostruct (0.6.1) parallel (1.26.3) parser (3.3.6.0) ast (~> 2.4.1) @@ -77,6 +95,7 @@ DEPENDENCIES minitest rake rubocop + temporal-ruby! temporalio BUNDLED WITH diff --git a/README.md b/README.md index 8314db5..51472b8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Prerequisites: * [activity_simple](activity_simple) - Simple workflow that calls two activities. * [activity_worker](activity_worker) - Use Ruby activities from a workflow in another language. +* [coinbase_ruby](coinbase_ruby) - Demonstrate interoperability with the + [Coinbase Ruby SDK](https://github.com/coinbase/temporal-ruby). * [context_propagation](context_propagation) - Use interceptors to propagate thread/fiber local data from clients through workflows/activities. * [message_passing_simple](message_passing_simple) - Simple workflow that accepts signals, queries, and updates. diff --git a/coinbase_ruby/README.md b/coinbase_ruby/README.md new file mode 100644 index 0000000..10ff98a --- /dev/null +++ b/coinbase_ruby/README.md @@ -0,0 +1,28 @@ +# Coinbase Ruby + +This sample shows a workflow, activity, and client from [Coinbase Ruby SDK](https://github.com/coinbase/temporal-ruby) +able to interoperate with a workflow, activity, and client from Temporal Ruby SDK. Specifically this sample contains an +activity in both SDKs, a workflow in both SDKs each calling both activities, a worker in both SDKs running in the same +process, and a starter with clients from each SDK each invoking both workflows. + +⚠️ NOTE - this requires disabling the loading of protos from the Coinbase Ruby SDK. As of this writing, +https://github.com/coinbase/temporal-ruby/pull/335 is not merged, so the Gemfile depends on the branch at +https://github.com/cretz/coinbase-temporal-ruby/tree/disable-proto-load-option for now. + +To run, first see [README.md](../README.md) for prerequisites. Then, in another terminal, start the Ruby worker +from this directory: + + bundle exec ruby worker.rb + +Finally in another terminal, use the Ruby client to run the workflow from this directory: + + bundle exec ruby starter.rb + +The Ruby code will invoke 4 workflows. The output of the final command should be: + +``` +Coinbase SDK workflow result from Temporal SDK client: ["Hello from Coinbase Ruby SDK, user1!", "Hello from Temporal Ruby SDK, user1!"] +Temporal SDK workflow result from Temporal SDK client: ["Hello from Coinbase Ruby SDK, user2!", "Hello from Temporal Ruby SDK, user2!"] +Coinbase SDK workflow result from Coinbase SDK client: ["Hello from Coinbase Ruby SDK, user3!", "Hello from Temporal Ruby SDK, user3!"] +Temporal SDK workflow result from Coinbase SDK client: ["Hello from Coinbase Ruby SDK, user4!", "Hello from Temporal Ruby SDK, user4!"] +``` \ No newline at end of file diff --git a/coinbase_ruby/coinbase_activity.rb b/coinbase_ruby/coinbase_activity.rb new file mode 100644 index 0000000..fc717de --- /dev/null +++ b/coinbase_ruby/coinbase_activity.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'temporal-ruby' + +module CoinbaseRuby + class CoinbaseActivity < Temporal::Activity + def execute(name) + "Hello from Coinbase Ruby SDK, #{name}!" + end + end +end diff --git a/coinbase_ruby/coinbase_workflow.rb b/coinbase_ruby/coinbase_workflow.rb new file mode 100644 index 0000000..7deca2f --- /dev/null +++ b/coinbase_ruby/coinbase_workflow.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'temporal-ruby' +require_relative 'coinbase_activity' + +module CoinbaseRuby + class CoinbaseWorkflow < Temporal::Workflow + def execute(name) + [ + # Execute activity on Coinbase SDK worker + CoinbaseActivity.execute!(name), + # Execute activity on Temporal SDK worker + workflow.execute_activity!(:TemporalActivity, name, options: { task_queue: 'coinbase-ruby-sample-temporal' }) + ] + end + end +end diff --git a/coinbase_ruby/starter.rb b/coinbase_ruby/starter.rb new file mode 100644 index 0000000..9007df2 --- /dev/null +++ b/coinbase_ruby/starter.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# We must require Temporal SDK first and set the env var to prevent Coinbase SDK from trying to load its protos +require 'temporalio/client' +ENV['COINBASE_TEMPORAL_RUBY_DISABLE_PROTO_LOAD'] = '1' + +require_relative 'coinbase_workflow' +require_relative 'temporal_workflow' +require 'logger' +require 'temporal-ruby' + +# Create Temporal SDK client +client = Temporalio::Client.connect('localhost:7233', 'default') + +# Run Coinbase workflow +result = client.execute_workflow( + CoinbaseRuby::CoinbaseWorkflow.name, 'user1', + id: 'coinbase-ruby-sample-workflow-id-1', task_queue: 'coinbase-ruby-sample-coinbase' +) +puts "Coinbase SDK workflow result from Temporal SDK client: #{result}" + +# Run Temporal workflow +result = client.execute_workflow( + CoinbaseRuby::TemporalWorkflow, 'user2', + id: 'coinbase-ruby-sample-workflow-id-2', task_queue: 'coinbase-ruby-sample-temporal' +) +puts "Temporal SDK workflow result from Temporal SDK client: #{result}" + +# Now do the same with Coinbase SDK, first configuring the client +Temporal.configure do |config| + config.host = 'localhost' + config.port = 7233 + config.namespace = 'default' +end + +# Run Coinbase workflow +run_id = Temporal.start_workflow( + CoinbaseRuby::CoinbaseWorkflow, 'user3', + options: { workflow_id: 'coinbase-ruby-sample-workflow-id-3', task_queue: 'coinbase-ruby-sample-coinbase' } +) +result = Temporal.await_workflow_result( + CoinbaseRuby::CoinbaseWorkflow, + workflow_id: 'coinbase-ruby-sample-workflow-id-3', run_id: +) +puts "Coinbase SDK workflow result from Coinbase SDK client: #{result}" + +# Run Temporal workflow +run_id = Temporal.start_workflow( + :TemporalWorkflow, 'user4', + options: { workflow_id: 'coinbase-ruby-sample-workflow-id-4', task_queue: 'coinbase-ruby-sample-temporal' } +) +result = Temporal.await_workflow_result( + :TemporalWorkflow, + workflow_id: 'coinbase-ruby-sample-workflow-id-4', run_id: +) +puts "Temporal SDK workflow result from Coinbase SDK client: #{result}" diff --git a/coinbase_ruby/temporal_activity.rb b/coinbase_ruby/temporal_activity.rb new file mode 100644 index 0000000..ab43333 --- /dev/null +++ b/coinbase_ruby/temporal_activity.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'temporalio/activity' + +module CoinbaseRuby + class TemporalActivity < Temporalio::Activity::Definition + def execute(name) + "Hello from Temporal Ruby SDK, #{name}!" + end + end +end diff --git a/coinbase_ruby/temporal_workflow.rb b/coinbase_ruby/temporal_workflow.rb new file mode 100644 index 0000000..40dcde3 --- /dev/null +++ b/coinbase_ruby/temporal_workflow.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'temporalio/workflow' +require_relative 'coinbase_activity' +require_relative 'temporal_activity' + +module CoinbaseRuby + class TemporalWorkflow < Temporalio::Workflow::Definition + def execute(name) + [ + # Execute activity on Coinbase SDK worker + Temporalio::Workflow.execute_activity(CoinbaseActivity.name, name, + start_to_close_timeout: 10, task_queue: 'coinbase-ruby-sample-coinbase'), + # Execute activity on Temporal SDK worker + Temporalio::Workflow.execute_activity(TemporalActivity, name, start_to_close_timeout: 10) + ] + end + end +end diff --git a/coinbase_ruby/worker.rb b/coinbase_ruby/worker.rb new file mode 100644 index 0000000..1c97c71 --- /dev/null +++ b/coinbase_ruby/worker.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# We must require Temporal SDK first and set the env var to prevent Coinbase SDK from trying to load its protos +require 'temporalio/client' +require 'temporalio/worker' +ENV['COINBASE_TEMPORAL_RUBY_DISABLE_PROTO_LOAD'] = '1' + +require_relative 'coinbase_activity' +require_relative 'coinbase_workflow' +require_relative 'temporal_activity' +require_relative 'temporal_workflow' +require 'logger' +require 'temporal-ruby' +require 'temporal/worker' + +# Create a Temporal client +client = Temporalio::Client.connect( + 'localhost:7233', + 'default', + logger: Logger.new($stdout, level: Logger::INFO) +) + +# Create Temporal worker with the activity and workflow on the coinbase-ruby-sample-temporal task queue +worker = Temporalio::Worker.new( + client:, + task_queue: 'coinbase-ruby-sample-temporal', + activities: [CoinbaseRuby::TemporalActivity], + workflows: [CoinbaseRuby::TemporalWorkflow] +) + +# Run the Temporal worker and inside it run the Coinbase worker +puts 'Starting worker on both Temporal Ruby SDK and Coinbase Ruby SDK' +worker.run do + # Configure Coinbase client/worker on the coinbase-ruby-sample-coinbase task queue + Temporal.configure do |config| + config.host = 'localhost' + config.port = 7233 + config.namespace = 'default' + config.task_queue = 'coinbase-ruby-sample-coinbase' + end + + # Run the Coinbase worker + worker = Temporal::Worker.new + worker.register_activity(CoinbaseRuby::CoinbaseActivity) + worker.register_workflow(CoinbaseRuby::CoinbaseWorkflow) + worker.start +end diff --git a/test/coinbase_ruby/workflow_test.rb b/test/coinbase_ruby/workflow_test.rb new file mode 100644 index 0000000..c67f903 --- /dev/null +++ b/test/coinbase_ruby/workflow_test.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'securerandom' +require 'test' +require 'temporalio/testing' +require 'temporalio/worker' +ENV['COINBASE_TEMPORAL_RUBY_DISABLE_PROTO_LOAD'] = '1' +require 'temporal-ruby' +require 'temporal/worker' + +require 'coinbase_ruby/coinbase_workflow' +require 'coinbase_ruby/temporal_workflow' + +module CoinbaseRuby + class WorkflowTest < Test + def test_both_sdks + # Start a local env + Temporalio::Testing::WorkflowEnvironment.start_local do |env| + # Create Coinbase config, client, and worker + coinbase_config = Temporal::Configuration.new + host, port = env.client.connection.options.target_host.split(':') + coinbase_config.host = host + coinbase_config.port = port.to_i + coinbase_config.namespace = 'default' + coinbase_config.task_queue = 'coinbase-ruby-sample-coinbase' + coinbase_client = Temporal::Client.new(coinbase_config) + coinbase_worker = Temporal::Worker.new(coinbase_config) + coinbase_worker.register_activity(CoinbaseRuby::CoinbaseActivity) + coinbase_worker.register_workflow(CoinbaseRuby::CoinbaseWorkflow) + + # Run all inside Temporal worker + worker = Temporalio::Worker.new( + client: env.client, + task_queue: 'coinbase-ruby-sample-temporal', + activities: [CoinbaseRuby::TemporalActivity], + workflows: [CoinbaseRuby::TemporalWorkflow] + ) + worker.run do + # Run Coinbase worker in background, stop it when done + Thread.new { coinbase_worker.start } + + # Run both workflows from Temporal client + assert_equal ['Hello from Coinbase Ruby SDK, user-a!', 'Hello from Temporal Ruby SDK, user-a!'], + env.client.execute_workflow( + CoinbaseRuby::CoinbaseWorkflow.name, 'user-a', + id: "wf-#{SecureRandom.uuid}", task_queue: 'coinbase-ruby-sample-coinbase' + ) + assert_equal ['Hello from Coinbase Ruby SDK, user-b!', 'Hello from Temporal Ruby SDK, user-b!'], + env.client.execute_workflow( + CoinbaseRuby::TemporalWorkflow, 'user-b', + id: "wf-#{SecureRandom.uuid}", task_queue: 'coinbase-ruby-sample-temporal' + ) + + # Run both workflows from Coinbase client + workflow_id = "wf-#{SecureRandom.uuid}" + run_id = coinbase_client.start_workflow( + CoinbaseRuby::CoinbaseWorkflow, 'user-c', + options: { workflow_id:, task_queue: 'coinbase-ruby-sample-coinbase' } + ) + assert_equal ['Hello from Coinbase Ruby SDK, user-c!', 'Hello from Temporal Ruby SDK, user-c!'], + coinbase_client.await_workflow_result(CoinbaseRuby::CoinbaseWorkflow, workflow_id:, run_id:) + workflow_id = "wf-#{SecureRandom.uuid}" + run_id = coinbase_client.start_workflow( + :TemporalWorkflow, 'user-d', + options: { workflow_id:, task_queue: 'coinbase-ruby-sample-temporal' } + ) + assert_equal ['Hello from Coinbase Ruby SDK, user-d!', 'Hello from Temporal Ruby SDK, user-d!'], + coinbase_client.await_workflow_result(:TemporalWorkflow, workflow_id:, run_id:) + ensure + coinbase_worker.stop + end + end + end + end +end