cadence-ruby 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +456 -0
  3. data/cadence.gemspec +9 -2
  4. data/lib/cadence-ruby.rb +1 -0
  5. data/lib/cadence.rb +176 -0
  6. data/lib/cadence/activity.rb +33 -0
  7. data/lib/cadence/activity/async_token.rb +34 -0
  8. data/lib/cadence/activity/context.rb +64 -0
  9. data/lib/cadence/activity/poller.rb +89 -0
  10. data/lib/cadence/activity/task_processor.rb +73 -0
  11. data/lib/cadence/activity/workflow_convenience_methods.rb +41 -0
  12. data/lib/cadence/client.rb +21 -0
  13. data/lib/cadence/client/errors.rb +8 -0
  14. data/lib/cadence/client/thrift_client.rb +380 -0
  15. data/lib/cadence/concerns/executable.rb +33 -0
  16. data/lib/cadence/concerns/typed.rb +40 -0
  17. data/lib/cadence/configuration.rb +36 -0
  18. data/lib/cadence/errors.rb +21 -0
  19. data/lib/cadence/executable_lookup.rb +25 -0
  20. data/lib/cadence/execution_options.rb +32 -0
  21. data/lib/cadence/json.rb +18 -0
  22. data/lib/cadence/metadata.rb +73 -0
  23. data/lib/cadence/metadata/activity.rb +28 -0
  24. data/lib/cadence/metadata/base.rb +17 -0
  25. data/lib/cadence/metadata/decision.rb +25 -0
  26. data/lib/cadence/metadata/workflow.rb +23 -0
  27. data/lib/cadence/metrics.rb +37 -0
  28. data/lib/cadence/metrics_adapters/log.rb +33 -0
  29. data/lib/cadence/metrics_adapters/null.rb +9 -0
  30. data/lib/cadence/middleware/chain.rb +30 -0
  31. data/lib/cadence/middleware/entry.rb +9 -0
  32. data/lib/cadence/retry_policy.rb +27 -0
  33. data/lib/cadence/saga/concern.rb +37 -0
  34. data/lib/cadence/saga/result.rb +22 -0
  35. data/lib/cadence/saga/saga.rb +24 -0
  36. data/lib/cadence/testing.rb +50 -0
  37. data/lib/cadence/testing/cadence_override.rb +112 -0
  38. data/lib/cadence/testing/future_registry.rb +27 -0
  39. data/lib/cadence/testing/local_activity_context.rb +17 -0
  40. data/lib/cadence/testing/local_workflow_context.rb +207 -0
  41. data/lib/cadence/testing/workflow_execution.rb +44 -0
  42. data/lib/cadence/testing/workflow_override.rb +36 -0
  43. data/lib/cadence/thread_local_context.rb +14 -0
  44. data/lib/cadence/thread_pool.rb +68 -0
  45. data/lib/cadence/types.rb +7 -0
  46. data/lib/cadence/utils.rb +17 -0
  47. data/lib/cadence/uuid.rb +19 -0
  48. data/lib/cadence/version.rb +1 -1
  49. data/lib/cadence/worker.rb +91 -0
  50. data/lib/cadence/workflow.rb +42 -0
  51. data/lib/cadence/workflow/context.rb +266 -0
  52. data/lib/cadence/workflow/convenience_methods.rb +34 -0
  53. data/lib/cadence/workflow/decision.rb +39 -0
  54. data/lib/cadence/workflow/decision_state_machine.rb +48 -0
  55. data/lib/cadence/workflow/decision_task_processor.rb +105 -0
  56. data/lib/cadence/workflow/dispatcher.rb +31 -0
  57. data/lib/cadence/workflow/execution_info.rb +45 -0
  58. data/lib/cadence/workflow/executor.rb +45 -0
  59. data/lib/cadence/workflow/future.rb +75 -0
  60. data/lib/cadence/workflow/history.rb +76 -0
  61. data/lib/cadence/workflow/history/event.rb +71 -0
  62. data/lib/cadence/workflow/history/event_target.rb +79 -0
  63. data/lib/cadence/workflow/history/window.rb +40 -0
  64. data/lib/cadence/workflow/poller.rb +74 -0
  65. data/lib/cadence/workflow/replay_aware_logger.rb +36 -0
  66. data/lib/cadence/workflow/serializer.rb +31 -0
  67. data/lib/cadence/workflow/serializer/base.rb +22 -0
  68. data/lib/cadence/workflow/serializer/cancel_timer.rb +19 -0
  69. data/lib/cadence/workflow/serializer/complete_workflow.rb +20 -0
  70. data/lib/cadence/workflow/serializer/fail_workflow.rb +21 -0
  71. data/lib/cadence/workflow/serializer/record_marker.rb +21 -0
  72. data/lib/cadence/workflow/serializer/request_activity_cancellation.rb +19 -0
  73. data/lib/cadence/workflow/serializer/schedule_activity.rb +54 -0
  74. data/lib/cadence/workflow/serializer/start_child_workflow.rb +52 -0
  75. data/lib/cadence/workflow/serializer/start_timer.rb +20 -0
  76. data/lib/cadence/workflow/state_manager.rb +324 -0
  77. data/lib/gen/thrift/cadence_constants.rb +11 -0
  78. data/lib/gen/thrift/cadence_types.rb +11 -0
  79. data/lib/gen/thrift/shared_constants.rb +11 -0
  80. data/lib/gen/thrift/shared_types.rb +4600 -0
  81. data/lib/gen/thrift/workflow_service.rb +3142 -0
  82. data/rbi/cadence-ruby.rbi +39 -0
  83. metadata +152 -5
@@ -0,0 +1,33 @@
1
+ require 'cadence/retry_policy'
2
+
3
+ module Cadence
4
+ module Concerns
5
+ module Executable
6
+ def domain(*args)
7
+ return @domain if args.empty?
8
+ @domain = args.first
9
+ end
10
+
11
+ def task_list(*args)
12
+ return @task_list if args.empty?
13
+ @task_list = args.first
14
+ end
15
+
16
+ def retry_policy(*args)
17
+ return @retry_policy if args.empty?
18
+ @retry_policy = Cadence::RetryPolicy.new(args.first)
19
+ @retry_policy.validate!
20
+ end
21
+
22
+ def timeouts(*args)
23
+ return @timeouts if args.empty?
24
+ @timeouts = args.first
25
+ end
26
+
27
+ def headers(*args)
28
+ return @headers if args.empty?
29
+ @headers = args.first
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require 'dry-struct'
2
+ require 'cadence/types'
3
+
4
+ module Cadence
5
+ module Concerns
6
+ module Typed
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ attr_reader :input_class
13
+
14
+ def execute_in_context(context, input)
15
+ input = input_class[*input] if input_class
16
+
17
+ super(context, input)
18
+ end
19
+
20
+ def input(klass = nil, &block)
21
+ if klass
22
+ unless klass.is_a?(Dry::Types::Type)
23
+ raise 'Unsupported input class. Use one of the provided Cadence::Types'
24
+ end
25
+ @input_class = klass
26
+ else
27
+ @input_class = generate_struct
28
+ @input_class.instance_eval(&block)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def generate_struct
35
+ Class.new(Dry::Struct::Value) { transform_keys(&:to_sym) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ require 'logger'
2
+ require 'cadence/metrics_adapters/null'
3
+
4
+ module Cadence
5
+ class Configuration
6
+ attr_reader :timeouts
7
+ attr_accessor :client_type, :host, :port, :logger, :metrics_adapter, :domain, :task_list, :headers
8
+
9
+ DEFAULT_TIMEOUTS = {
10
+ execution: 60, # End-to-end workflow time
11
+ task: 10, # Decision task processing time
12
+ schedule_to_close: nil, # End-to-end activity time (default: schedule_to_start + start_to_close)
13
+ schedule_to_start: 10, # Queue time for an activity
14
+ start_to_close: 30, # Time spent processing an activity
15
+ heartbeat: nil # Max time between heartbeats (off by default)
16
+ }.freeze
17
+
18
+ DEFAULT_HEADERS = {}.freeze
19
+ DEFAULT_DOMAIN = 'default-domain'.freeze
20
+ DEFAULT_TASK_LIST = 'default-task-list'.freeze
21
+
22
+ def initialize
23
+ @client_type = :thrift
24
+ @logger = Logger.new(STDOUT, progname: 'cadence_client')
25
+ @metrics_adapter = MetricsAdapters::Null.new
26
+ @timeouts = DEFAULT_TIMEOUTS
27
+ @domain = DEFAULT_DOMAIN
28
+ @task_list = DEFAULT_TASK_LIST
29
+ @headers = DEFAULT_HEADERS
30
+ end
31
+
32
+ def timeouts=(new_timeouts)
33
+ @timeouts = DEFAULT_TIMEOUTS.merge(new_timeouts)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ module Cadence
2
+ # Superclass for all Cadence errors
3
+ class Error < StandardError; end
4
+
5
+ # Superclass for errors specific to Cadence worker itself
6
+ class InternalError < Error; end
7
+
8
+ # Indicates a non-deterministic workflow execution, might be due to
9
+ # a non-deterministic workflow implementation or the gem's bug
10
+ class NonDeterministicWorkflowError < InternalError; end
11
+
12
+ # Superclass for misconfiguration/misuse on the client (user) side
13
+ class ClientError < Error; end
14
+
15
+ # Represents any timeout
16
+ class TimeoutError < ClientError; end
17
+
18
+ # A superclass for activity exceptions raised explicitly
19
+ # with the itent to propagate to a workflow
20
+ class ActivityException < ClientError; end
21
+ end
@@ -0,0 +1,25 @@
1
+ # This class is responsible for matching an executable (activity or workflow) name
2
+ # to a class implementing it.
3
+ #
4
+ # TODO: This class should be responsible for handling executable versions
5
+ # when these are implemented
6
+ #
7
+ module Cadence
8
+ class ExecutableLookup
9
+ def initialize
10
+ @executables = {}
11
+ end
12
+
13
+ def add(name, executable)
14
+ executables[name] = executable
15
+ end
16
+
17
+ def find(name)
18
+ executables[name]
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :executables
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require 'cadence/concerns/executable'
2
+
3
+ module Cadence
4
+ class ExecutionOptions
5
+ attr_reader :name, :domain, :task_list, :retry_policy, :timeouts, :headers
6
+
7
+ def initialize(object, options = {})
8
+ @name = options[:name] || object.to_s
9
+ @domain = options[:domain]
10
+ @task_list = options[:task_list]
11
+ @retry_policy = options[:retry_policy]
12
+ @cron_schedule = options[:cron_schedule]
13
+ @timeouts = options[:timeouts] || {}
14
+ @headers = options[:headers] || {}
15
+
16
+ if object.singleton_class.included_modules.include?(Concerns::Executable)
17
+ @domain ||= object.domain
18
+ @task_list ||= object.task_list
19
+ @retry_policy ||= object.retry_policy
20
+ @timeouts = object.timeouts.merge(@timeouts) if object.timeouts
21
+ @headers = object.headers.merge(@headers) if object.headers
22
+ end
23
+
24
+ @domain ||= Cadence.configuration.domain
25
+ @task_list ||= Cadence.configuration.task_list
26
+ @timeouts = Cadence.configuration.timeouts.merge(@timeouts)
27
+ @headers = Cadence.configuration.headers.merge(@headers)
28
+
29
+ freeze
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ # Helper class for serializing/deserializing JSON
2
+ require 'oj'
3
+
4
+ module Cadence
5
+ module JSON
6
+ OJ_OPTIONS = {
7
+ mode: :object
8
+ }.freeze
9
+
10
+ def self.serialize(value)
11
+ Oj.dump(value, OJ_OPTIONS)
12
+ end
13
+
14
+ def self.deserialize(value)
15
+ Oj.load(value.to_s, OJ_OPTIONS)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ require 'cadence/errors'
2
+ require 'cadence/metadata/activity'
3
+ require 'cadence/metadata/decision'
4
+ require 'cadence/metadata/workflow'
5
+
6
+ module Cadence
7
+ module Metadata
8
+ ACTIVITY_TYPE = :activity
9
+ DECISION_TYPE = :decision
10
+ WORKFLOW_TYPE = :workflow
11
+
12
+ class << self
13
+ def generate(type, data, domain = nil)
14
+ case type
15
+ when ACTIVITY_TYPE
16
+ activity_metadata_from(data, domain)
17
+ when DECISION_TYPE
18
+ decision_metadata_from(data, domain)
19
+ when WORKFLOW_TYPE
20
+ workflow_metadata_from(data)
21
+ else
22
+ raise InternalError, 'Unsupported metadata type'
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def activity_metadata_from(task, domain)
29
+ Metadata::Activity.new(
30
+ domain: domain,
31
+ id: task.activityId,
32
+ name: task.activityType.name,
33
+ task_token: task.taskToken,
34
+ attempt: task.attempt,
35
+ workflow_run_id: task.workflowExecution.runId,
36
+ workflow_id: task.workflowExecution.workflowId,
37
+ workflow_name: task.workflowType.name,
38
+ headers: task.header&.fields || {},
39
+ timeouts: {
40
+ start_to_close: task.startToCloseTimeoutSeconds,
41
+ schedule_to_close: task.scheduleToCloseTimeoutSeconds,
42
+ heartbeat: task.heartbeatTimeoutSeconds
43
+ }
44
+ )
45
+ end
46
+
47
+ def decision_metadata_from(task, domain)
48
+ Metadata::Decision.new(
49
+ domain: domain,
50
+ id: task.startedEventId,
51
+ task_token: task.taskToken,
52
+ attempt: task.attempt,
53
+ workflow_run_id: task.workflowExecution.runId,
54
+ workflow_id: task.workflowExecution.workflowId,
55
+ workflow_name: task.workflowType.name
56
+ )
57
+ end
58
+
59
+ def workflow_metadata_from(event)
60
+ Metadata::Workflow.new(
61
+ name: event.workflowType.name,
62
+ run_id: event.originalExecutionRunId,
63
+ attempt: event.attempt,
64
+ headers: event.header&.fields || {},
65
+ timeouts: {
66
+ execution: event.executionStartToCloseTimeoutSeconds,
67
+ task: event.taskStartToCloseTimeoutSeconds
68
+ }
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ require 'cadence/metadata/base'
2
+
3
+ module Cadence
4
+ module Metadata
5
+ class Activity < Base
6
+ attr_reader :domain, :id, :name, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :headers, :timeouts
7
+
8
+ def initialize(domain:, id:, name:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:, timeouts:, headers: {})
9
+ @domain = domain
10
+ @id = id
11
+ @name = name
12
+ @task_token = task_token
13
+ @attempt = attempt
14
+ @workflow_run_id = workflow_run_id
15
+ @workflow_id = workflow_id
16
+ @workflow_name = workflow_name
17
+ @timeouts = timeouts
18
+ @headers = headers
19
+
20
+ freeze
21
+ end
22
+
23
+ def activity?
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Cadence
2
+ module Metadata
3
+ class Base
4
+ def activity?
5
+ false
6
+ end
7
+
8
+ def decision?
9
+ false
10
+ end
11
+
12
+ def workflow?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'cadence/metadata/base'
2
+
3
+ module Cadence
4
+ module Metadata
5
+ class Decision < Base
6
+ attr_reader :domain, :id, :task_token, :attempt, :workflow_run_id, :workflow_id, :workflow_name, :timeouts
7
+
8
+ def initialize(domain:, id:, task_token:, attempt:, workflow_run_id:, workflow_id:, workflow_name:)
9
+ @domain = domain
10
+ @id = id
11
+ @task_token = task_token
12
+ @attempt = attempt
13
+ @workflow_run_id = workflow_run_id
14
+ @workflow_id = workflow_id
15
+ @workflow_name = workflow_name
16
+
17
+ freeze
18
+ end
19
+
20
+ def decision?
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'cadence/metadata/base'
2
+
3
+ module Cadence
4
+ module Metadata
5
+ class Workflow < Base
6
+ attr_reader :name, :run_id, :attempt, :headers, :timeouts
7
+
8
+ def initialize(name:, run_id:, attempt:, timeouts:, headers: {})
9
+ @name = name
10
+ @run_id = run_id
11
+ @attempt = attempt
12
+ @headers = headers
13
+ @timeouts = timeouts
14
+
15
+ freeze
16
+ end
17
+
18
+ def workflow?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Cadence
2
+ class Metrics
3
+ def initialize(adapter)
4
+ @adapter = adapter
5
+ end
6
+
7
+ def increment(key, tags = {})
8
+ count(key, 1, tags)
9
+ end
10
+
11
+ def decrement(key, tags = {})
12
+ count(key, -1, tags)
13
+ end
14
+
15
+ def count(key, count, tags = {})
16
+ adapter.count(key, count, tags)
17
+ rescue StandardError => error
18
+ Cadence.logger.error("Adapter failed to send count metrics for #{key}: #{error.inspect}")
19
+ end
20
+
21
+ def gauge(key, value, tags = {})
22
+ adapter.gauge(key, value, tags)
23
+ rescue StandardError => error
24
+ Cadence.logger.error("Adapter failed to send gauge metrics for #{key}: #{error.inspect}")
25
+ end
26
+
27
+ def timing(key, time, tags = {})
28
+ adapter.timing(key, time, tags)
29
+ rescue StandardError => error
30
+ Cadence.logger.error("Adapter failed to send timing metrics for #{key}: #{error.inspect}")
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :adapter
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ module Cadence
2
+ module MetricsAdapters
3
+ class Log
4
+ def initialize(logger)
5
+ @logger = logger
6
+ end
7
+
8
+ def count(key, count, tags)
9
+ logger.debug(format_message(key, 'count', count, tags))
10
+ end
11
+
12
+ def gauge(key, value, tags)
13
+ logger.debug(format_message(key, 'gauge', value, tags))
14
+ end
15
+
16
+ def timing(key, time, tags)
17
+ logger.debug(format_message(key, 'timing', time, tags))
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :logger
23
+
24
+ def format_message(key, type, value, tags)
25
+ tags_str = tags.map { |k, v| "#{k}:#{v}" }.join(',')
26
+ parts = [key, type, value]
27
+ parts << tags_str if !tags_str.empty?
28
+
29
+ parts.join(' | ')
30
+ end
31
+ end
32
+ end
33
+ end