cadence-ruby 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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