forked from coinbase/temporal-ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for invoking and processing queries (coinbase#141)
* Support for invoking and processing queries, WIP * Catch-all query handler support, feedback changes Made a handful of changes on approach from the initial spike. This is operating under an assumption that the added EventTarget type for query is a valid approach * Fixes for on_query interface, clean up workflow and spec * Fix method signature on testing context * Move catch-all handler back to block Also adding a second targeted query handler to spec * Use nil workflow class in test case * Updates to remove catch all handling, add query reject handling * More concise when no status returned from server * More consistent raise message style * Add test for reject condition not met * Simplify legacy handling and use serializers for query protos * Add specs for the new changes * Test query result & freeze them * Implement QueryRegistry * Swap Context#query_handlers with QueryRegistry * Add a spec for Workflow::Context * Rename QueryFailedFailure error to QueryFailed * Small cleanup items * Update readme Co-authored-by: antstorm <[email protected]>
- Loading branch information
Showing
32 changed files
with
848 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/usr/bin/env ruby | ||
require_relative '../init' | ||
|
||
Dir[File.expand_path('../workflows/*.rb', __dir__)].each { |f| require f } | ||
|
||
workflow_class_name, workflow_id, run_id, query, args = ARGV | ||
workflow_class = Object.const_get(workflow_class_name) | ||
|
||
if ![workflow_class, workflow_id, run_id, query].all? | ||
fail 'Wrong arguments, use `bin/query WORKFLOW WORKFLOW_ID RUN_ID QUERY [ARGS]`' | ||
end | ||
|
||
result = Temporal.query_workflow(workflow_class, query, workflow_id, run_id, args) | ||
puts result.inspect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require 'workflows/query_workflow' | ||
require 'temporal/errors' | ||
|
||
describe QueryWorkflow, :integration do | ||
subject { described_class } | ||
|
||
it 'returns the correct result for the queries' do | ||
workflow_id, run_id = run_workflow(described_class) | ||
|
||
# Query with nil workflow class | ||
expect(Temporal.query_workflow(nil, 'state', workflow_id, run_id)) | ||
.to eq 'started' | ||
|
||
# Query with arbitrary args | ||
expect(Temporal.query_workflow(described_class, 'state', workflow_id, run_id, | ||
'upcase', 'ignored', 'reverse')) | ||
.to eq 'DETRATS' | ||
|
||
# Query with no args | ||
expect(Temporal.query_workflow(described_class, 'signal_count', workflow_id, run_id)) | ||
.to eq 0 | ||
|
||
# Query with unregistered handler | ||
expect { Temporal.query_workflow(described_class, 'unknown_query', workflow_id, run_id) } | ||
.to raise_error(Temporal::QueryFailed, 'Workflow did not register a handler for unknown_query') | ||
|
||
Temporal.signal_workflow(described_class, 'make_progress', workflow_id, run_id) | ||
|
||
# Query for updated signal_count with an unsatisfied reject condition | ||
expect(Temporal.query_workflow(described_class, 'signal_count', workflow_id, run_id, query_reject_condition: :not_open)) | ||
.to eq 1 | ||
|
||
Temporal.signal_workflow(described_class, 'finish', workflow_id, run_id) | ||
wait_for_workflow_completion(workflow_id, run_id) | ||
|
||
# Repeating original query scenarios above, expecting updated state and signal results | ||
expect(Temporal.query_workflow(nil, 'state', workflow_id, run_id)) | ||
.to eq 'finished' | ||
|
||
expect(Temporal.query_workflow(described_class, 'state', workflow_id, run_id, | ||
'upcase', 'ignored', 'reverse')) | ||
.to eq 'DEHSINIF' | ||
|
||
expect(Temporal.query_workflow(described_class, 'signal_count', workflow_id, run_id)) | ||
.to eq 2 | ||
|
||
expect { Temporal.query_workflow(described_class, 'unknown_query', workflow_id, run_id) } | ||
.to raise_error(Temporal::QueryFailed, 'Workflow did not register a handler for unknown_query') | ||
|
||
# Now that the workflow is completed, test a query with a reject condition satisfied | ||
expect { Temporal.query_workflow(described_class, 'state', workflow_id, run_id, query_reject_condition: :not_open) } | ||
.to raise_error(Temporal::QueryFailed, 'Query rejected: status WORKFLOW_EXECUTION_STATUS_COMPLETED') | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
class QueryWorkflow < Temporal::Workflow | ||
attr_reader :state, :signal_count, :last_signal_received | ||
|
||
def execute | ||
@state = "started" | ||
@signal_count = 0 | ||
@last_signal_received = nil | ||
|
||
workflow.on_query("state") { |*args| apply_transforms(state, args) } | ||
workflow.on_query("signal_count") { signal_count } | ||
|
||
workflow.on_signal do |signal| | ||
@signal_count += 1 | ||
@last_signal_received = signal | ||
end | ||
|
||
workflow.wait_for { last_signal_received == "finish" } | ||
@state = "finished" | ||
|
||
{ | ||
signal_count: signal_count, | ||
last_signal_received: last_signal_received, | ||
final_state: state | ||
} | ||
end | ||
|
||
private | ||
|
||
def apply_transforms(value, transforms) | ||
return value if value.nil? || transforms.empty? | ||
transforms.inject(value) do |memo, input| | ||
next memo unless memo.respond_to?(input) | ||
memo.public_send(input) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
require 'temporal/connection/serializer/base' | ||
require 'temporal/concerns/payloads' | ||
|
||
module Temporal | ||
module Connection | ||
module Serializer | ||
class QueryAnswer < Base | ||
include Concerns::Payloads | ||
|
||
def to_proto | ||
Temporal::Api::Query::V1::WorkflowQueryResult.new( | ||
result_type: Temporal::Api::Enums::V1::QueryResultType::QUERY_RESULT_TYPE_ANSWERED, | ||
answer: to_query_payloads(object.result) | ||
) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
require 'temporal/connection/serializer/base' | ||
|
||
module Temporal | ||
module Connection | ||
module Serializer | ||
class QueryFailure < Base | ||
def to_proto | ||
Temporal::Api::Query::V1::WorkflowQueryResult.new( | ||
result_type: Temporal::Api::Enums::V1::QueryResultType::QUERY_RESULT_TYPE_FAILED, | ||
error_message: object.error.message | ||
) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.