Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get latest from upstream #6

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
628960b
Add Schedule api support (#277)
dhruv-stripe Jan 4, 2024
cfcbdd3
Remove cancelation commands when underlying futures are closed (#275)
jeffschoner Jan 4, 2024
052641c
Fix task queue type to match enum (#252)
awesomenix Jan 4, 2024
b6c7a76
fix: Use Standard Interface for Metrics Tags (#228)
0xTheProDev Jan 5, 2024
95d62d2
Add keyword arguments support to Activity classes (#255)
santiagodoldan Jan 5, 2024
3e0dae7
Fix warnings (#282)
jeffschoner Jan 16, 2024
65dfdb0
Add option for gRPC client connection retries (#270)
hughevans Feb 5, 2024
c4fb094
Update README with middleware documentation (#288)
DeRauk Mar 5, 2024
b20abf6
Pin examples auto-setup image to v1.22.0 (#298)
DeRauk Apr 6, 2024
f075102
Move off `Dry::Struct::Value` before its removed from `dry-struct` (#…
SalvatoreT Apr 6, 2024
34a7e4d
Plumb through :use_error_serialization_v2 from Configuration -> GRPC …
davehughes May 9, 2024
3fbc675
Mark continue_as_new as not implemented in testing context (#299)
taonic May 9, 2024
5d12aa3
Replay testing (#300)
jeffschoner Jun 24, 2024
dc937e8
Specify Protobuf Version To Fix Build (#308)
DeRauk Jul 17, 2024
0c9a0c7
Add pagination to get_workflow_history (#290)
cduanfigma Jul 22, 2024
c3991a9
Relax version specifier for 'google-protobuf' to fix build errors on …
jazev-stripe Jul 30, 2024
0d3a8bb
Support passing activity task rate limit on worker options (#311)
jazev-stripe Jul 30, 2024
e3c351f
Fix integration specs (#315)
antstorm Sep 3, 2024
c73a07e
[Refactor] Remove Temporal::Concerns::Payloads (#314)
antstorm Sep 4, 2024
f41efb7
[Refactor] Remove global config references (#317)
antstorm Sep 4, 2024
b5efd2c
Add workflow start delay option (#294)
santiagodoldan Dec 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

- name: Start dependencies
run: |
docker-compose \
docker compose \
-f examples/docker-compose.yml \
up -d

Expand Down Expand Up @@ -82,4 +82,4 @@ jobs:
env:
USE_ERROR_SERIALIZATION_V2: 1
run: |
cd examples && bundle exec rspec
cd examples && bundle exec rspec
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
source 'https://rubygems.org'

gem 'google-protobuf', '~> 3.19'

gemspec
74 changes: 73 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ Temporal::Worker.new(
workflow_thread_pool_size: 10, # how many threads poll for workflows
binary_checksum: nil, # identifies the version of workflow worker code
activity_poll_retry_seconds: 0, # how many seconds to wait after unsuccessful poll for activities
workflow_poll_retry_seconds: 0 # how many seconds to wait after unsuccessful poll for workflows
workflow_poll_retry_seconds: 0, # how many seconds to wait after unsuccessful poll for workflows
activity_max_tasks_per_second: 0 # rate-limit for starting activity tasks (new activities + retries) on the task queue
)
```

Expand Down Expand Up @@ -178,6 +179,47 @@ Temporal.configure do |config|
end
```

## Configuration

This gem is optimised for the smoothest out-of-the-box experience, which is achieved using a global
configuration:

```ruby
Temporal.configure do |config|
config.host = '127.0.0.1' # sets global host
...
end

Temporal::Worker.new # uses global host
Temporal.start_workflow(...) # uses global host
```

This will work just fine for simpler use-cases, however at some point you might need to setup
multiple clients and workers within the same instance of your app (e.g. you have different Temporal
hosts, need to use different codecs/converters for different parts of your app, etc). Should this be
the case we recommend using explicit local configurations for each client/worker:

```ruby
config_1 = Temporal::Configuration.new
config_1.host = 'temporal-01'

config_2 = Temporal::Configuration.new
config_2.host = 'temporal-01'

worker_1 = Temporal::Worker.new(config_1)
worker_2 = Temporal::Worker.new(config_2)

client_1 = Temporal::Client.new(config_1)
client_1.start_workflow(...)

client_2 = Temporal::Client.new(config_2)
client_2.start_workflow(...)
```

*NOTE: Almost all the methods on the `Temporal` module are delegated to the default client that's
initialized using global configuration. The same methods can be used directly on your own client
instances.*

## Workflows

A workflow is defined using pure Ruby code, however it should contain only a high-level
Expand Down Expand Up @@ -400,6 +442,36 @@ arguments are identical to the `Temporal.start_workflow` API.
set it to allow as many invocations as you need. You can also set it to `nil`, which will use a
default value of 10 years.*

## Middleware
Middleware sits between the execution of your workflows/activities and the Temporal SDK, allowing you to insert custom code before or after the execution.

### Activity Middleware Stack
Middleware added to the activity middleware stack will be executed around each activity method. This is useful when you want to perform a certain task before and/or after each activity execution, such as logging, error handling, or measuring execution time.

### Workflow Middleware Stack
There are actually two types of workflow middleware in Temporal Ruby SDK:

*Workflow Middleware*: This middleware is executed around each entire workflow. This is similar to activity middleware, but for workflows.

*Workflow Task Middleware*: This middleware is executed around each workflow task, of which there will be many for each workflow.

### Example
To add a middleware, you need to define a class that responds to the call method. Within the call method, you should call yield to allow the next middleware in the stack (or the workflow/activity method itself if there are no more middlewares) to execute. Here's an example:

```
class MyMiddleware
def call(metadata)
puts "Before execution"
yield
puts "After execution"
result
end
end
```

You can add this middleware to the stack like so `worker.add_activity_middleware(MyMiddleware)`

Please note that the order of middleware in the stack matters. The middleware that is added last will be the first one to execute. In the example above, MyMiddleware will execute before any other middleware in the stack.

## Breaking Changes

Expand Down
5 changes: 3 additions & 2 deletions examples/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ source 'https://rubygems.org'

gem 'temporal-ruby', path: '../'

gem 'dry-types', '>= 1.2.0'
gem 'dry-struct', '~> 1.1.1'
gem 'dry-types', '>= 1.7.2'
gem 'dry-struct', '~> 1.6.0'
gem 'google-protobuf', '~> 3.19'

gem 'rspec', group: :test
51 changes: 51 additions & 0 deletions examples/bin/update_replay_test_histories
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env ruby

# This script regenerates the workflow history files used in the example replay tests
# under examples/spec/replay/histories. It starts the necessary workflow, sends some
# signals, awaits workflow completion, then collects the history into JSON and protobuf
# binary file formats.
#
# To use this, start your Temporal server and bin/worker first. This script can then
# be run without any arguments. It will overwrite existing history files in the tree.
#
# NOTE: By default, collected history files contain the host names of the machines
# where the worker and this script are run because the default identity is pid@hostname.
# If you'd like, you can override this by setting an identity in the configuration in
# init.rb.

require_relative "../init"
require_relative "../workflows/signal_with_start_workflow"

workflow_id = SecureRandom.uuid
run_id = Temporal.start_workflow(
SignalWithStartWorkflow,
"hit",
options: {
workflow_id: workflow_id,
timeouts: {
execution: 30
},
signal_name: "miss",
signal_input: 1
}
)
Temporal.logger.info("Started workflow", {workflow_id: workflow_id, run_id: run_id})
sleep(1)
Temporal.signal_workflow(SignalWithStartWorkflow, "miss", workflow_id, run_id, 2)
sleep(1)
Temporal.signal_workflow(SignalWithStartWorkflow, "hit", workflow_id, run_id, 3)
Temporal.await_workflow_result(SignalWithStartWorkflow, workflow_id: workflow_id, run_id: run_id)

# Save in JSON, exactly like would be downloaded from Temporal UI
history_json = Temporal.get_workflow_history_json(workflow_id: workflow_id, run_id: run_id)
filename = File.expand_path("../spec/replay/histories/signal_with_start.json", File.dirname(__FILE__))
File.open(filename, "w") do |f|
f.write(history_json)
end

# Save in protobuf binary format
history_binary = Temporal.get_workflow_history_protobuf(workflow_id: workflow_id, run_id: run_id)
filename = File.expand_path("../spec/replay/histories/signal_with_start.protobin", File.dirname(__FILE__))
File.open(filename, "wb") do |f|
f.write(history_binary)
end
2 changes: 1 addition & 1 deletion examples/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.5'

services:
temporal:
image: temporalio/auto-setup:latest
image: temporalio/auto-setup:1.22.0
ports:
- "7233:7233"
environment:
Expand Down
7 changes: 5 additions & 2 deletions examples/init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@

metrics_logger = Logger.new(STDOUT, progname: 'metrics')

DEFAULT_NAMESPACE = 'ruby-samples'.freeze
DEFAULT_TASK_QUEUE = 'general'.freeze

Temporal.configure do |config|
config.host = ENV.fetch('TEMPORAL_HOST', 'localhost')
config.port = ENV.fetch('TEMPORAL_PORT', 7233).to_i
config.namespace = ENV.fetch('TEMPORAL_NAMESPACE', 'ruby-samples')
config.task_queue = ENV.fetch('TEMPORAL_TASK_QUEUE', 'general')
config.namespace = ENV.fetch('TEMPORAL_NAMESPACE', DEFAULT_NAMESPACE)
config.task_queue = ENV.fetch('TEMPORAL_TASK_QUEUE', DEFAULT_TASK_QUEUE)
config.metrics_adapter = Temporal::MetricsAdapters::Log.new(metrics_logger)
end
8 changes: 6 additions & 2 deletions examples/spec/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def wait_for_workflow_completion(workflow_id, run_id)
def fetch_history(workflow_id, run_id, options = {})
connection = Temporal.send(:default_client).send(:connection)
options = {
namespace: Temporal.configuration.namespace,
namespace: integration_spec_namespace,
workflow_id: workflow_id,
run_id: run_id,
}.merge(options)
Expand All @@ -30,6 +30,10 @@ def fetch_history(workflow_id, run_id, options = {})
end

def integration_spec_namespace
ENV.fetch('TEMPORAL_NAMESPACE', 'ruby-samples')
ENV.fetch('TEMPORAL_NAMESPACE', DEFAULT_NAMESPACE)
end

def integration_spec_task_queue
ENV.fetch('TEMPORAL_TASK_QUEUE', DEFAULT_TASK_QUEUE)
end
end
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
require 'workflows/call_failing_activity_workflow'

describe CallFailingActivityWorkflow, :integration do

class TestDeserializer
include Temporal::Concerns::Payloads
end

it 'correctly re-raises an activity-thrown exception in the workflow' do
workflow_id = SecureRandom.uuid
expected_message = "a failure message"
Expand Down
22 changes: 11 additions & 11 deletions examples/spec/integration/converter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@
require 'grpc/errors'

describe 'Converter', :integration do
around(:each) do |example|
task_queue = Temporal.configuration.task_queue
let(:codec) do
Temporal::Connection::Converter::Codec::Chain.new(
payload_codecs: [
Temporal::CryptPayloadCodec.new
]
)
end

around(:each) do |example|
Temporal.configure do |config|
config.task_queue = 'crypt'
config.payload_codec = Temporal::Connection::Converter::Codec::Chain.new(
payload_codecs: [
Temporal::CryptPayloadCodec.new
]
)
config.payload_codec = codec
end

example.run
ensure
Temporal.configure do |config|
config.task_queue = task_queue
config.task_queue = integration_spec_task_queue
config.payload_codec = Temporal::Configuration::DEFAULT_PAYLOAD_CODEC
end
end
Expand Down Expand Up @@ -67,8 +69,6 @@
completion_event = events[:EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED].first
result = completion_event.workflow_execution_completed_event_attributes.result

payload_codec = Temporal.configuration.payload_codec

expect(payload_codec.decodes(result).payloads.first.data).to eq('"Hello World, Tom"')
expect(codec.decodes(result).payloads.first.data).to eq('"Hello World, Tom"')
end
end
87 changes: 87 additions & 0 deletions examples/spec/integration/create_schedule_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require "temporal/errors"
require "temporal/schedule/backfill"
require "temporal/schedule/calendar"
require "temporal/schedule/interval"
require "temporal/schedule/schedule"
require "temporal/schedule/schedule_spec"
require "temporal/schedule/schedule_policies"
require "temporal/schedule/schedule_state"
require "temporal/schedule/start_workflow_action"

describe "Temporal.create_schedule", :integration do
let(:example_schedule) do
workflow_id = SecureRandom.uuid
Temporal::Schedule::Schedule.new(
spec: Temporal::Schedule::ScheduleSpec.new(
calendars: [Temporal::Schedule::Calendar.new(day_of_week: "*", hour: "18", minute: "30")],
intervals: [Temporal::Schedule::Interval.new(every: 6000, offset: 300)],
cron_expressions: ["@hourly"],
jitter: 30,
# Set an end time so that the test schedule doesn't run forever
end_time: Time.now + 600
),
action: Temporal::Schedule::StartWorkflowAction.new(
"HelloWorldWorkflow",
"Test",
options: {
workflow_id: workflow_id,
task_queue: integration_spec_task_queue
}
),
policies: Temporal::Schedule::SchedulePolicies.new(
overlap_policy: :buffer_one
),
state: Temporal::Schedule::ScheduleState.new(
notes: "Created by integration test"
)
)
end

it "can create schedules" do
namespace = integration_spec_namespace

schedule_id = SecureRandom.uuid

create_response = Temporal.create_schedule(
namespace,
schedule_id,
example_schedule,
memo: {"schedule_memo" => "schedule memo value"},
trigger_immediately: true,
backfill: Temporal::Schedule::Backfill.new(start_time: (Date.today - 90).to_time, end_time: Time.now)
)
expect(create_response).to(be_an_instance_of(Temporalio::Api::WorkflowService::V1::CreateScheduleResponse))

describe_response = Temporal.describe_schedule(namespace, schedule_id)

expect(describe_response.memo).to(eq({"schedule_memo" => "schedule memo value"}))
expect(describe_response.schedule.spec.jitter.seconds).to(eq(30))
expect(describe_response.schedule.policies.overlap_policy).to(eq(:SCHEDULE_OVERLAP_POLICY_BUFFER_ONE))
expect(describe_response.schedule.action.start_workflow.workflow_type.name).to(eq("HelloWorldWorkflow"))
expect(describe_response.schedule.state.notes).to(eq("Created by integration test"))
end

it "can create schedules with a minimal set of fields" do
namespace = integration_spec_namespace
schedule_id = SecureRandom.uuid

schedule = Temporal::Schedule::Schedule.new(
spec: Temporal::Schedule::ScheduleSpec.new(
cron_expressions: ["@hourly"],
# Set an end time so that the test schedule doesn't run forever
end_time: Time.now + 600
),
action: Temporal::Schedule::StartWorkflowAction.new(
"HelloWorldWorkflow",
"Test",
options: {task_queue: integration_spec_task_queue}
)
)

Temporal.create_schedule(namespace, schedule_id, schedule)

describe_response = Temporal.describe_schedule(namespace, schedule_id)
expect(describe_response.schedule.action.start_workflow.workflow_type.name).to(eq("HelloWorldWorkflow"))
expect(describe_response.schedule.policies.overlap_policy).to(eq(:SCHEDULE_OVERLAP_POLICY_SKIP))
end
end
Loading