active_saga 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/ADAPTERS.md +6 -6
- data/CHANGELOG.md +1 -1
- data/GUIDE.md +10 -10
- data/LICENSE +1 -1
- data/README.md +51 -51
- data/lib/{active_workflow → active_saga}/backoff.rb +1 -1
- data/lib/{active_workflow → active_saga}/configuration.rb +3 -3
- data/lib/{active_workflow → active_saga}/context.rb +1 -1
- data/lib/{active_workflow → active_saga}/dsl/options.rb +8 -8
- data/lib/{active_workflow → active_saga}/dsl/signals.rb +5 -5
- data/lib/{active_workflow → active_saga}/dsl/steps.rb +11 -11
- data/lib/{active_workflow → active_saga}/errors.rb +1 -1
- data/lib/{active_workflow → active_saga}/execution.rb +5 -5
- data/lib/{active_workflow → active_saga}/jobs/runner_job.rb +8 -8
- data/lib/active_saga/railtie.rb +17 -0
- data/lib/{active_workflow → active_saga}/serializers/json.rb +1 -1
- data/lib/{active_workflow → active_saga}/stores/active_record.rb +34 -34
- data/lib/{active_workflow → active_saga}/stores/base.rb +4 -4
- data/lib/{active_workflow → active_saga}/task.rb +5 -5
- data/lib/active_saga/version.rb +5 -0
- data/lib/{active_workflow → active_saga}/workflow.rb +5 -5
- data/lib/{active_workflow.rb → active_saga.rb} +21 -21
- data/lib/generators/{active_workflow → active_saga}/install/install_generator.rb +3 -3
- data/lib/generators/active_saga/install/templates/initializer.rb +8 -0
- data/lib/generators/{active_workflow/install/templates/migrations/create_active_workflow_tables.rb → active_saga/install/templates/migrations/create_active_saga_tables.rb} +14 -14
- data/lib/generators/{active_workflow → active_saga}/install/templates/sample_workflow.rb +2 -2
- data/lib/generators/{active_workflow → active_saga}/workflow/templates/workflow.rb +1 -1
- data/lib/generators/{active_workflow → active_saga}/workflow/workflow_generator.rb +1 -1
- metadata +26 -26
- data/lib/active_workflow/railtie.rb +0 -17
- data/lib/active_workflow/version.rb +0 -5
- data/lib/generators/active_workflow/install/templates/initializer.rb +0 -8
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module ActiveSaga
|
|
4
4
|
module Jobs
|
|
5
5
|
# Executes workflow transitions within Active Job.
|
|
6
6
|
class RunnerJob < ActiveJob::Base
|
|
7
|
-
queue_as :
|
|
7
|
+
queue_as :active_saga
|
|
8
8
|
|
|
9
9
|
# We want retries for transient errors only; user-defined retries handled within workflow.
|
|
10
10
|
retry_on ActiveRecord::Deadlocked, wait: 1.second, attempts: 5 if defined?(ActiveRecord::Deadlocked)
|
|
11
11
|
|
|
12
12
|
def perform(execution_id)
|
|
13
|
-
execution =
|
|
13
|
+
execution = ActiveSaga.configuration.store.load_execution(execution_id)
|
|
14
14
|
if execution
|
|
15
15
|
workflow = execution.workflow_class || "UnknownWorkflow"
|
|
16
16
|
step = execution.cursor_step || "pending"
|
|
17
|
-
|
|
18
|
-
"
|
|
17
|
+
ActiveSaga.configuration.logger.info(
|
|
18
|
+
"ActiveSaga::RunnerJob executing #{workflow}##{step} (execution=#{execution_id}) (state=#{execution.state})"
|
|
19
19
|
)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
rescue
|
|
24
|
-
|
|
22
|
+
ActiveSaga.configuration.store!.process_execution(execution_id)
|
|
23
|
+
rescue ActiveSaga::Errors::InvalidWorkflow => e
|
|
24
|
+
ActiveSaga.configuration.logger.error("ActiveSaga invalid workflow: #{e.message}")
|
|
25
25
|
raise
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module ActiveSaga
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
initializer "active_saga.configure" do |_app|
|
|
8
|
+
ActiveSaga.configure do |config|
|
|
9
|
+
config.logger ||= Rails.logger
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
rake_tasks do
|
|
14
|
+
load "active_saga/tasks.rake" if File.exist?(File.join(__dir__, "../../tasks.rake"))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "active_record"
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module ActiveSaga
|
|
6
6
|
module Stores
|
|
7
7
|
# ActiveRecord-backed persistence implementation.
|
|
8
8
|
class ActiveRecord < Base
|
|
@@ -10,26 +10,26 @@ module ActiveWorkflow
|
|
|
10
10
|
|
|
11
11
|
module Models
|
|
12
12
|
class Execution < ::ActiveRecord::Base
|
|
13
|
-
self.table_name = "
|
|
13
|
+
self.table_name = "as_executions"
|
|
14
14
|
|
|
15
15
|
has_many :steps,
|
|
16
|
-
class_name: "
|
|
16
|
+
class_name: "ActiveSaga::Stores::ActiveRecord::Models::Step",
|
|
17
17
|
foreign_key: :execution_id,
|
|
18
18
|
inverse_of: :execution,
|
|
19
19
|
dependent: :destroy
|
|
20
20
|
|
|
21
21
|
has_many :events,
|
|
22
|
-
class_name: "
|
|
22
|
+
class_name: "ActiveSaga::Stores::ActiveRecord::Models::Event",
|
|
23
23
|
foreign_key: :execution_id,
|
|
24
24
|
inverse_of: :execution,
|
|
25
25
|
dependent: :destroy
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
class Step < ::ActiveRecord::Base
|
|
29
|
-
self.table_name = "
|
|
29
|
+
self.table_name = "as_steps"
|
|
30
30
|
|
|
31
31
|
belongs_to :execution,
|
|
32
|
-
class_name: "
|
|
32
|
+
class_name: "ActiveSaga::Stores::ActiveRecord::Models::Execution",
|
|
33
33
|
foreign_key: :execution_id,
|
|
34
34
|
inverse_of: :steps
|
|
35
35
|
|
|
@@ -49,10 +49,10 @@ module ActiveWorkflow
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
class Event < ::ActiveRecord::Base
|
|
52
|
-
self.table_name = "
|
|
52
|
+
self.table_name = "as_events"
|
|
53
53
|
|
|
54
54
|
belongs_to :execution,
|
|
55
|
-
class_name: "
|
|
55
|
+
class_name: "ActiveSaga::Stores::ActiveRecord::Models::Execution",
|
|
56
56
|
foreign_key: :execution_id,
|
|
57
57
|
inverse_of: :events
|
|
58
58
|
end
|
|
@@ -93,7 +93,7 @@ module ActiveWorkflow
|
|
|
93
93
|
|
|
94
94
|
if steps.empty?
|
|
95
95
|
record.update!(state: "completed", cursor_step: nil, completed_at: now)
|
|
96
|
-
ActiveSupport::Notifications.instrument("
|
|
96
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.completed",
|
|
97
97
|
execution_id: record.id,
|
|
98
98
|
workflow: workflow_class)
|
|
99
99
|
end
|
|
@@ -129,9 +129,9 @@ module ActiveWorkflow
|
|
|
129
129
|
|
|
130
130
|
def enqueue_runner(execution_id, run_at: nil)
|
|
131
131
|
if run_at
|
|
132
|
-
|
|
132
|
+
ActiveSaga::Jobs::RunnerJob.set(wait_until: run_at).perform_later(execution_id)
|
|
133
133
|
else
|
|
134
|
-
|
|
134
|
+
ActiveSaga::Jobs::RunnerJob.perform_later(execution_id)
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
@@ -157,10 +157,10 @@ module ActiveWorkflow
|
|
|
157
157
|
return build_execution(execution)
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
-
raise
|
|
160
|
+
raise ActiveSaga::Errors::StepNotWaiting, "Step #{step_name} is not waiting" unless step&.waiting?
|
|
161
161
|
|
|
162
162
|
if idempotency_key && step.completion_idempotency_key.present? && step.completion_idempotency_key != idempotency_key
|
|
163
|
-
raise
|
|
163
|
+
raise ActiveSaga::Errors::AsyncCompletionConflict, "Completion idempotency mismatch"
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
return build_execution(execution) if step.completion_idempotency_key.present? && step.completion_idempotency_key == idempotency_key
|
|
@@ -187,7 +187,7 @@ module ActiveWorkflow
|
|
|
187
187
|
completed_at: clock.call
|
|
188
188
|
)
|
|
189
189
|
|
|
190
|
-
ActiveSupport::Notifications.instrument("
|
|
190
|
+
ActiveSupport::Notifications.instrument("active_saga.step.completed_async",
|
|
191
191
|
execution_id: execution.id,
|
|
192
192
|
step: step.name,
|
|
193
193
|
workflow: execution.workflow_class)
|
|
@@ -201,7 +201,7 @@ module ActiveWorkflow
|
|
|
201
201
|
def fail_step!(execution_id, step_name, error_class:, message:, details:, idempotency_key: nil)
|
|
202
202
|
with_execution_lock(execution_id) do |execution|
|
|
203
203
|
step = execution.steps.lock.find_by(name: step_name.to_s)
|
|
204
|
-
raise
|
|
204
|
+
raise ActiveSaga::Errors::StepNotWaiting, "Step #{step_name} is not waiting" unless step&.waiting?
|
|
205
205
|
|
|
206
206
|
definition = step_definition(execution, step)
|
|
207
207
|
current_attempts = step.attempts.to_i
|
|
@@ -212,7 +212,7 @@ module ActiveWorkflow
|
|
|
212
212
|
attempts: current_attempts,
|
|
213
213
|
async: true)
|
|
214
214
|
|
|
215
|
-
ActiveSupport::Notifications.instrument("
|
|
215
|
+
ActiveSupport::Notifications.instrument("active_saga.step.failed_async",
|
|
216
216
|
execution_id: execution.id,
|
|
217
217
|
step: step.name,
|
|
218
218
|
workflow: execution.workflow_class,
|
|
@@ -245,7 +245,7 @@ module ActiveWorkflow
|
|
|
245
245
|
with_execution_lock(execution_id) do |execution|
|
|
246
246
|
Models::Event.create!(execution:, name: name.to_s, payload: payload)
|
|
247
247
|
|
|
248
|
-
ActiveSupport::Notifications.instrument("
|
|
248
|
+
ActiveSupport::Notifications.instrument("active_saga.signal.received",
|
|
249
249
|
execution_id: execution.id,
|
|
250
250
|
signal: name)
|
|
251
251
|
|
|
@@ -273,7 +273,7 @@ module ActiveWorkflow
|
|
|
273
273
|
cancelled_at: clock.call
|
|
274
274
|
)
|
|
275
275
|
|
|
276
|
-
ActiveSupport::Notifications.instrument("
|
|
276
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.cancelled",
|
|
277
277
|
execution_id: execution.id,
|
|
278
278
|
workflow: execution.workflow_class,
|
|
279
279
|
reason: reason)
|
|
@@ -283,7 +283,7 @@ module ActiveWorkflow
|
|
|
283
283
|
end
|
|
284
284
|
|
|
285
285
|
def context_from(execution)
|
|
286
|
-
|
|
286
|
+
ActiveSaga::Context.new(serializer.load(execution.ctx))
|
|
287
287
|
end
|
|
288
288
|
|
|
289
289
|
def persist_context(execution, ctx)
|
|
@@ -291,7 +291,7 @@ module ActiveWorkflow
|
|
|
291
291
|
end
|
|
292
292
|
|
|
293
293
|
def build_execution(record)
|
|
294
|
-
|
|
294
|
+
ActiveSaga::Execution.new(
|
|
295
295
|
id: record.id,
|
|
296
296
|
workflow_class: record.workflow_class,
|
|
297
297
|
state: record.state,
|
|
@@ -333,7 +333,7 @@ module ActiveWorkflow
|
|
|
333
333
|
state: "completed",
|
|
334
334
|
completed_at: clock.call
|
|
335
335
|
)
|
|
336
|
-
ActiveSupport::Notifications.instrument("
|
|
336
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.completed",
|
|
337
337
|
execution_id: execution.id,
|
|
338
338
|
workflow: execution.workflow_class)
|
|
339
339
|
end
|
|
@@ -361,13 +361,13 @@ module ActiveWorkflow
|
|
|
361
361
|
|
|
362
362
|
if current_attempts < max_attempts
|
|
363
363
|
retry_index = [current_attempts, 1].max
|
|
364
|
-
delay =
|
|
364
|
+
delay = ActiveSaga::Backoff.calculate(retry_config, attempts: retry_index)
|
|
365
365
|
scheduled_at = clock.call + delay
|
|
366
366
|
|
|
367
367
|
step.update!(state: "pending", attempts: current_attempts, scheduled_at: scheduled_at)
|
|
368
368
|
execution.update!(state: "running", cursor_step: definition.name)
|
|
369
369
|
|
|
370
|
-
ActiveSupport::Notifications.instrument("
|
|
370
|
+
ActiveSupport::Notifications.instrument("active_saga.retry.scheduled",
|
|
371
371
|
execution_id: execution.id,
|
|
372
372
|
workflow: execution.workflow_class,
|
|
373
373
|
step: step.name,
|
|
@@ -384,7 +384,7 @@ module ActiveWorkflow
|
|
|
384
384
|
step.update!(state: "failed")
|
|
385
385
|
run_compensations!(execution, upto: step.position)
|
|
386
386
|
execution.update!(state: "failed", cursor_step: nil)
|
|
387
|
-
ActiveSupport::Notifications.instrument("
|
|
387
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.failed",
|
|
388
388
|
execution_id: execution.id,
|
|
389
389
|
workflow: execution.workflow_class,
|
|
390
390
|
step: step.name)
|
|
@@ -406,7 +406,7 @@ module ActiveWorkflow
|
|
|
406
406
|
next if step.state == "compensated"
|
|
407
407
|
next unless compensate
|
|
408
408
|
|
|
409
|
-
ActiveSupport::Notifications.instrument("
|
|
409
|
+
ActiveSupport::Notifications.instrument("active_saga.step.compensating",
|
|
410
410
|
execution_id: execution.id,
|
|
411
411
|
step: definition.name) do
|
|
412
412
|
case compensate
|
|
@@ -464,7 +464,7 @@ module ActiveWorkflow
|
|
|
464
464
|
if skip_step?(workflow, definition)
|
|
465
465
|
mark_skipped(step, definition)
|
|
466
466
|
store.persist_context(execution, workflow.ctx)
|
|
467
|
-
ActiveSupport::Notifications.instrument("
|
|
467
|
+
ActiveSupport::Notifications.instrument("active_saga.step.skipped",
|
|
468
468
|
execution_id: execution.id, step: definition.name, workflow: workflow_class.name)
|
|
469
469
|
process! # continue to next
|
|
470
470
|
return
|
|
@@ -475,7 +475,7 @@ module ActiveWorkflow
|
|
|
475
475
|
step.update!(state: "running", attempts: attempt, started_at: now, scheduled_at: nil)
|
|
476
476
|
execution.update!(state: "running", cursor_step: definition.name)
|
|
477
477
|
|
|
478
|
-
ActiveSupport::Notifications.instrument("
|
|
478
|
+
ActiveSupport::Notifications.instrument("active_saga.step.started",
|
|
479
479
|
execution_id: execution.id,
|
|
480
480
|
workflow: workflow_class.name,
|
|
481
481
|
step: definition.name,
|
|
@@ -489,11 +489,11 @@ module ActiveWorkflow
|
|
|
489
489
|
when :complete
|
|
490
490
|
store.persist_context(execution, workflow.ctx)
|
|
491
491
|
step.update!(state: "completed", completed_at: store.clock.call)
|
|
492
|
-
ActiveSupport::Notifications.instrument("
|
|
492
|
+
ActiveSupport::Notifications.instrument("active_saga.step.completed",
|
|
493
493
|
execution_id: execution.id, workflow: workflow_class.name, step: definition.name)
|
|
494
494
|
new_state = next_state_after(step)
|
|
495
495
|
execution.update!(state: new_state, cursor_step: next_step_name(step))
|
|
496
|
-
ActiveSupport::Notifications.instrument("
|
|
496
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.completed",
|
|
497
497
|
execution_id: execution.id,
|
|
498
498
|
workflow: execution.workflow_class) if new_state == "completed"
|
|
499
499
|
process!
|
|
@@ -501,11 +501,11 @@ module ActiveWorkflow
|
|
|
501
501
|
# Synchronous result returned.
|
|
502
502
|
store.persist_context(execution, workflow.ctx)
|
|
503
503
|
step.update!(state: "completed", completed_at: store.clock.call)
|
|
504
|
-
ActiveSupport::Notifications.instrument("
|
|
504
|
+
ActiveSupport::Notifications.instrument("active_saga.step.completed",
|
|
505
505
|
execution_id: execution.id, workflow: workflow_class.name, step: definition.name, result: result)
|
|
506
506
|
new_state = next_state_after(step)
|
|
507
507
|
execution.update!(state: new_state, cursor_step: next_step_name(step))
|
|
508
|
-
ActiveSupport::Notifications.instrument("
|
|
508
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.completed",
|
|
509
509
|
execution_id: execution.id,
|
|
510
510
|
workflow: execution.workflow_class) if new_state == "completed"
|
|
511
511
|
process!
|
|
@@ -526,7 +526,7 @@ module ActiveWorkflow
|
|
|
526
526
|
next unless step.timeout_at && step.timeout_at <= now
|
|
527
527
|
|
|
528
528
|
definition = step_definition(step)
|
|
529
|
-
ActiveSupport::Notifications.instrument("
|
|
529
|
+
ActiveSupport::Notifications.instrument("active_saga.step.timeout",
|
|
530
530
|
execution_id: execution.id,
|
|
531
531
|
workflow: execution.workflow_class,
|
|
532
532
|
step: step.name)
|
|
@@ -624,7 +624,7 @@ module ActiveWorkflow
|
|
|
624
624
|
)
|
|
625
625
|
execution.update!(state: "waiting", cursor_step: definition.name)
|
|
626
626
|
|
|
627
|
-
ActiveSupport::Notifications.instrument("
|
|
627
|
+
ActiveSupport::Notifications.instrument("active_saga.step.waiting",
|
|
628
628
|
execution_id: execution.id, workflow: execution.workflow_class, step: definition.name)
|
|
629
629
|
|
|
630
630
|
store.enqueue_runner(execution.id, run_at: timeout_at) if timeout_at
|
|
@@ -645,7 +645,7 @@ module ActiveWorkflow
|
|
|
645
645
|
attempts: attempts
|
|
646
646
|
)
|
|
647
647
|
|
|
648
|
-
ActiveSupport::Notifications.instrument("
|
|
648
|
+
ActiveSupport::Notifications.instrument("active_saga.step.failed",
|
|
649
649
|
execution_id: execution.id,
|
|
650
650
|
workflow: execution.workflow_class,
|
|
651
651
|
step: definition.name,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module ActiveSaga
|
|
4
4
|
module Stores
|
|
5
5
|
# Base interface for persistence adapters.
|
|
6
6
|
class Base
|
|
7
7
|
attr_reader :logger, :serializer, :clock
|
|
8
8
|
|
|
9
|
-
def initialize(logger:
|
|
10
|
-
serializer:
|
|
11
|
-
clock:
|
|
9
|
+
def initialize(logger: ActiveSaga.configuration.logger,
|
|
10
|
+
serializer: ActiveSaga.configuration.serializer,
|
|
11
|
+
clock: ActiveSaga.configuration.clock)
|
|
12
12
|
@logger = logger
|
|
13
13
|
@serializer = serializer
|
|
14
14
|
@clock = clock
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module ActiveSaga
|
|
4
4
|
# Base class for reusable workflow tasks.
|
|
5
5
|
class Task
|
|
6
6
|
class << self
|
|
7
|
-
attr_reader :
|
|
7
|
+
attr_reader :_as_async_options
|
|
8
8
|
|
|
9
9
|
def async!(**options)
|
|
10
|
-
@
|
|
10
|
+
@_as_async_options = options.deep_symbolize_keys
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def async_options
|
|
14
|
-
|
|
14
|
+
_as_async_options || {}
|
|
15
|
+
end
|
|
15
16
|
end
|
|
16
|
-
end
|
|
17
17
|
|
|
18
18
|
def initialize(context)
|
|
19
19
|
@context = context
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module ActiveSaga
|
|
4
4
|
# Base class for defining workflows.
|
|
5
5
|
class Workflow
|
|
6
6
|
extend DSL::Options
|
|
@@ -15,12 +15,12 @@ module ActiveWorkflow
|
|
|
15
15
|
explicit_idempotency_key = options.delete(:idempotency_key)
|
|
16
16
|
metadata = options.delete(:metadata) { {} }
|
|
17
17
|
|
|
18
|
-
context =
|
|
18
|
+
context = ActiveSaga::Context.new(options)
|
|
19
19
|
workflow = new(context: context)
|
|
20
20
|
workflow.before_start
|
|
21
21
|
|
|
22
22
|
idempotency_key = explicit_idempotency_key || workflow.compute_idempotency_key
|
|
23
|
-
store =
|
|
23
|
+
store = ActiveSaga.configuration.store!
|
|
24
24
|
|
|
25
25
|
execution = store.start_execution(
|
|
26
26
|
workflow_class: name,
|
|
@@ -31,7 +31,7 @@ module ActiveWorkflow
|
|
|
31
31
|
metadata: (metadata || {}).deep_symbolize_keys
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
ActiveSupport::Notifications.instrument("
|
|
34
|
+
ActiveSupport::Notifications.instrument("active_saga.execution.started",
|
|
35
35
|
execution_id: execution.id, workflow: name)
|
|
36
36
|
|
|
37
37
|
execution
|
|
@@ -94,7 +94,7 @@ module ActiveWorkflow
|
|
|
94
94
|
when :wait
|
|
95
95
|
:waiting
|
|
96
96
|
else
|
|
97
|
-
raise
|
|
97
|
+
raise ActiveSaga::Errors::InvalidStep, "Unknown style #{step_definition.style}"
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -10,33 +10,33 @@ require "active_support/inflector"
|
|
|
10
10
|
require "active_support/notifications"
|
|
11
11
|
require "active_job"
|
|
12
12
|
|
|
13
|
-
require_relative "
|
|
14
|
-
require_relative "
|
|
15
|
-
require_relative "
|
|
16
|
-
require_relative "
|
|
17
|
-
require_relative "
|
|
18
|
-
require_relative "
|
|
19
|
-
require_relative "
|
|
20
|
-
require_relative "
|
|
21
|
-
require_relative "
|
|
22
|
-
require_relative "
|
|
23
|
-
require_relative "
|
|
24
|
-
require_relative "
|
|
25
|
-
require_relative "
|
|
26
|
-
require_relative "
|
|
27
|
-
require_relative "
|
|
28
|
-
require_relative "
|
|
13
|
+
require_relative "active_saga/version"
|
|
14
|
+
require_relative "active_saga/errors"
|
|
15
|
+
require_relative "active_saga/configuration"
|
|
16
|
+
require_relative "active_saga/context"
|
|
17
|
+
require_relative "active_saga/execution"
|
|
18
|
+
require_relative "active_saga/backoff"
|
|
19
|
+
require_relative "active_saga/dsl/options"
|
|
20
|
+
require_relative "active_saga/dsl/steps"
|
|
21
|
+
require_relative "active_saga/dsl/signals"
|
|
22
|
+
require_relative "active_saga/workflow"
|
|
23
|
+
require_relative "active_saga/task"
|
|
24
|
+
require_relative "active_saga/jobs/runner_job"
|
|
25
|
+
require_relative "active_saga/stores/base"
|
|
26
|
+
require_relative "active_saga/stores/active_record"
|
|
27
|
+
require_relative "active_saga/serializers/json"
|
|
28
|
+
require_relative "active_saga/railtie" if defined?(Rails::Railtie)
|
|
29
29
|
|
|
30
|
-
module
|
|
30
|
+
module ActiveSaga
|
|
31
31
|
class << self
|
|
32
|
-
# @return [
|
|
32
|
+
# @return [ActiveSaga::Configuration]
|
|
33
33
|
def configuration
|
|
34
34
|
@configuration ||= Configuration.new
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Yields global configuration block and memoizes the configuration instance.
|
|
38
38
|
#
|
|
39
|
-
# @yieldparam [
|
|
39
|
+
# @yieldparam [ActiveSaga::Configuration] config
|
|
40
40
|
def configure
|
|
41
41
|
yield(configuration)
|
|
42
42
|
end
|
|
@@ -48,7 +48,7 @@ module ActiveWorkflow
|
|
|
48
48
|
|
|
49
49
|
# Delegates the store accessor for convenience.
|
|
50
50
|
#
|
|
51
|
-
# @return [
|
|
51
|
+
# @return [ActiveSaga::Stores::Base]
|
|
52
52
|
def store
|
|
53
53
|
configuration.store!
|
|
54
54
|
end
|
|
@@ -91,7 +91,7 @@ module ActiveWorkflow
|
|
|
91
91
|
private
|
|
92
92
|
|
|
93
93
|
def with_instrumentation(action, execution_id, step_name)
|
|
94
|
-
ActiveSupport::Notifications.instrument("
|
|
94
|
+
ActiveSupport::Notifications.instrument("active_saga.step.#{action}",
|
|
95
95
|
execution_id: execution_id,
|
|
96
96
|
step: step_name.to_s) { yield }
|
|
97
97
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "rails/generators"
|
|
4
4
|
require "rails/generators/active_record"
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module ActiveSaga
|
|
7
7
|
module Generators
|
|
8
8
|
class InstallGenerator < Rails::Generators::Base
|
|
9
9
|
include ActiveRecord::Generators::Migration
|
|
@@ -16,11 +16,11 @@ module ActiveWorkflow
|
|
|
16
16
|
def copy_initializer
|
|
17
17
|
return if options[:skip_initializer]
|
|
18
18
|
|
|
19
|
-
template "initializer.rb", "config/initializers/
|
|
19
|
+
template "initializer.rb", "config/initializers/active_saga.rb"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def copy_migration
|
|
23
|
-
migration_template "migrations/
|
|
23
|
+
migration_template "migrations/create_active_saga_tables.rb", "db/migrate/create_active_saga_tables.rb"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def copy_sample_workflow
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class CreateActiveSagaTables < ActiveRecord::Migration[7.1]
|
|
4
4
|
def change
|
|
5
|
-
create_table :
|
|
5
|
+
create_table :as_executions do |t|
|
|
6
6
|
t.string :workflow_class, null: false
|
|
7
7
|
t.string :state, null: false
|
|
8
8
|
t.text :ctx, null: false
|
|
@@ -20,12 +20,12 @@ class CreateActiveWorkflowTables < ActiveRecord::Migration[7.1]
|
|
|
20
20
|
t.timestamps
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
add_index :
|
|
24
|
-
add_index :
|
|
25
|
-
add_index :
|
|
23
|
+
add_index :as_executions, :workflow_class
|
|
24
|
+
add_index :as_executions, :state
|
|
25
|
+
add_index :as_executions, :idempotency_key, unique: true
|
|
26
26
|
|
|
27
|
-
create_table :
|
|
28
|
-
t.references :execution, null: false, foreign_key: { to_table: :
|
|
27
|
+
create_table :as_steps do |t|
|
|
28
|
+
t.references :execution, null: false, foreign_key: { to_table: :as_executions }
|
|
29
29
|
t.string :name, null: false
|
|
30
30
|
t.string :style, null: false
|
|
31
31
|
t.jsonb :options, null: false, default: {}
|
|
@@ -51,12 +51,12 @@ class CreateActiveWorkflowTables < ActiveRecord::Migration[7.1]
|
|
|
51
51
|
t.timestamps
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
add_index :
|
|
55
|
-
add_index :
|
|
56
|
-
add_index :
|
|
54
|
+
add_index :as_steps, [:execution_id, :position]
|
|
55
|
+
add_index :as_steps, [:execution_id, :name]
|
|
56
|
+
add_index :as_steps, [:execution_id, :completion_idempotency_key], unique: true, where: "completion_idempotency_key IS NOT NULL"
|
|
57
57
|
|
|
58
|
-
create_table :
|
|
59
|
-
t.references :execution, null: false, foreign_key: { to_table: :
|
|
58
|
+
create_table :as_events do |t|
|
|
59
|
+
t.references :execution, null: false, foreign_key: { to_table: :as_executions }
|
|
60
60
|
t.string :name, null: false
|
|
61
61
|
t.jsonb :payload, null: false, default: {}
|
|
62
62
|
t.datetime :consumed_at
|
|
@@ -64,7 +64,7 @@ class CreateActiveWorkflowTables < ActiveRecord::Migration[7.1]
|
|
|
64
64
|
t.timestamps
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
add_index :
|
|
68
|
-
add_index :
|
|
67
|
+
add_index :as_events, [:execution_id, :name]
|
|
68
|
+
add_index :as_events, [:execution_id, :name], where: "consumed_at IS NULL", name: "index_as_events_unconsumed"
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class SampleWorkflow <
|
|
3
|
+
class SampleWorkflow < ActiveSaga::Workflow
|
|
4
4
|
idempotency_key { "sample:#{ctx[:reference]}" }
|
|
5
5
|
defaults retry: { max: 3, strategy: :exponential, first_delay: 2.seconds, jitter: true }
|
|
6
6
|
|
|
@@ -16,7 +16,7 @@ class SampleWorkflow < ActiveWorkflow::Workflow
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
class RemoteExportTask <
|
|
19
|
+
class RemoteExportTask < ActiveSaga::Task
|
|
20
20
|
async! timeout: 15.minutes
|
|
21
21
|
|
|
22
22
|
def call(ctx)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class <%= class_name %> <
|
|
3
|
+
class <%= class_name %> < ActiveSaga::Workflow
|
|
4
4
|
# idempotency_key { "#{name.underscore}:#{ctx[:resource_id]}" }
|
|
5
5
|
# defaults retry: { max: 3, strategy: :exponential, first_delay: 2.seconds, jitter: true }
|
|
6
6
|
|