rails-transactional-outbox 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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +13 -0
- data/.github/workflows/ci.yml +49 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +150 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +142 -0
- data/LICENSE.txt +21 -0
- data/README.md +285 -0
- data/Rakefile +10 -0
- data/bin/console +15 -0
- data/bin/rails_transactional_outbox_health_check +13 -0
- data/bin/setup +8 -0
- data/lib/rails-transactional-outbox.rb +3 -0
- data/lib/rails_transactional_outbox/configuration.rb +33 -0
- data/lib/rails_transactional_outbox/error_handlers/null_error_handler.rb +9 -0
- data/lib/rails_transactional_outbox/error_handlers.rb +6 -0
- data/lib/rails_transactional_outbox/event_type.rb +37 -0
- data/lib/rails_transactional_outbox/exponential_backoff.rb +9 -0
- data/lib/rails_transactional_outbox/health_check.rb +48 -0
- data/lib/rails_transactional_outbox/monitor.rb +47 -0
- data/lib/rails_transactional_outbox/outbox_entries_processor.rb +56 -0
- data/lib/rails_transactional_outbox/outbox_entry_factory.rb +32 -0
- data/lib/rails_transactional_outbox/outbox_model.rb +78 -0
- data/lib/rails_transactional_outbox/railtie.rb +11 -0
- data/lib/rails_transactional_outbox/record_processor.rb +35 -0
- data/lib/rails_transactional_outbox/record_processors/active_record_processor.rb +39 -0
- data/lib/rails_transactional_outbox/record_processors/base_processor.rb +15 -0
- data/lib/rails_transactional_outbox/record_processors.rb +6 -0
- data/lib/rails_transactional_outbox/reliable_model/reliable_callback.rb +41 -0
- data/lib/rails_transactional_outbox/reliable_model/reliable_callbacks_registry.rb +26 -0
- data/lib/rails_transactional_outbox/reliable_model.rb +81 -0
- data/lib/rails_transactional_outbox/runner.rb +106 -0
- data/lib/rails_transactional_outbox/runner_sleep_interval.rb +14 -0
- data/lib/rails_transactional_outbox/tracers/datadog_tracer.rb +35 -0
- data/lib/rails_transactional_outbox/tracers/null_tracer.rb +9 -0
- data/lib/rails_transactional_outbox/tracers.rb +7 -0
- data/lib/rails_transactional_outbox/version.rb +7 -0
- data/lib/rails_transactional_outbox.rb +54 -0
- data/lib/tasks/rails_transactional_outbox.rake +11 -0
- data/rails-transactional-outbox.gemspec +44 -0
- metadata +188 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
module OutboxModel
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
scope :fetch_processable, lambda { |batch_size|
|
9
|
+
where(processed_at: nil)
|
10
|
+
.lock("FOR UPDATE SKIP LOCKED")
|
11
|
+
.where("retry_at IS NULL OR retry_at <= ?", Time.current)
|
12
|
+
.order(created_at: :asc)
|
13
|
+
.limit(batch_size)
|
14
|
+
}
|
15
|
+
|
16
|
+
def self.any_records_to_process?
|
17
|
+
where(processed_at: nil)
|
18
|
+
.where("retry_at IS NULL OR retry_at <= ?", Time.current)
|
19
|
+
.exists?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.outbox_encrypt_json_for(*encryptable_json_attributes)
|
23
|
+
encryptable_json_attributes.each do |attribute|
|
24
|
+
define_method "#{attribute}=" do |payload|
|
25
|
+
super(payload.to_json)
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method "transformed_#{attribute}" do
|
29
|
+
JSON.parse(public_send(attribute)).symbolize_keys
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def transformed_arguments
|
36
|
+
arguments.to_h.symbolize_keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def transformed_changeset
|
40
|
+
changeset.to_h.symbolize_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def processed?
|
44
|
+
processed_at.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def failed?
|
48
|
+
failed_at.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_error(raised_error, clock: Time, backoff_multiplier: 5)
|
52
|
+
@error = raised_error
|
53
|
+
self.error_class = raised_error.class
|
54
|
+
self.error_message = raised_error.message
|
55
|
+
self.failed_at = clock.current
|
56
|
+
self.attempts ||= 0
|
57
|
+
self.attempts += 1
|
58
|
+
self.retry_at = clock.current.advance(
|
59
|
+
seconds: RailsTransactionalOutbox::ExponentialBackoff.backoff_for(backoff_multiplier, attempts)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def error
|
64
|
+
@error || error_class.constantize.new(error_message)
|
65
|
+
end
|
66
|
+
|
67
|
+
def event_type
|
68
|
+
RailsTransactionalOutbox::EventType.resolve_from_event_name(event_name).to_sym
|
69
|
+
end
|
70
|
+
|
71
|
+
def infer_model
|
72
|
+
model_klass = resource_class.constantize
|
73
|
+
model_klass.find(resource_id)
|
74
|
+
rescue ActiveRecord::RecordNotFound
|
75
|
+
model_klass.new(id: resource_id) if RailsTransactionalOutbox::EventType.new(event_type).destroy?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
class RecordProcessor
|
5
|
+
attr_reader :config
|
6
|
+
private :config
|
7
|
+
|
8
|
+
def initialize(config: RailsTransactionalOutbox.configuration)
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(record)
|
13
|
+
applicable_record_processors = record_processors.select { |processor| processor.applies?(record) }
|
14
|
+
applicable_record_processors.any? or raise ProcessorNotFoundError.new(record)
|
15
|
+
|
16
|
+
applicable_record_processors.each { |processor| processor.call(record) }
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :record_processors, to: :config
|
20
|
+
|
21
|
+
class ProcessorNotFoundError < StandardError
|
22
|
+
attr_reader :record
|
23
|
+
private :record
|
24
|
+
|
25
|
+
def initialize(record)
|
26
|
+
super()
|
27
|
+
@record = record
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"no processor was found for record with ID: #{record.id}, context: #{record.context}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
class RecordProcessors
|
5
|
+
class ActiveRecordProcessor < RailsTransactionalOutbox::RecordProcessors::BaseProcessor
|
6
|
+
ACTIVE_RECORD_CONTEXT = "active_record"
|
7
|
+
private_constant :ACTIVE_RECORD_CONTEXT
|
8
|
+
|
9
|
+
def self.context
|
10
|
+
ACTIVE_RECORD_CONTEXT
|
11
|
+
end
|
12
|
+
|
13
|
+
def applies?(record)
|
14
|
+
record.context == ACTIVE_RECORD_CONTEXT
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(record)
|
18
|
+
model = record.infer_model or raise CouldNotFindModelError.new(record)
|
19
|
+
model.previous_changes = record.transformed_changeset.with_indifferent_access
|
20
|
+
model.reliable_after_commit_callbacks.for_event_type(record.event_type).each do |callback|
|
21
|
+
callback.call(model)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class CouldNotFindModelError < StandardError
|
26
|
+
attr_reader :record
|
27
|
+
|
28
|
+
def initialize(record)
|
29
|
+
super()
|
30
|
+
@record = record
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"could not find model for outbox record: #{record.id}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
module ReliableModel
|
5
|
+
class ReliableCallback
|
6
|
+
attr_reader :callback, :options
|
7
|
+
private :callback, :options
|
8
|
+
|
9
|
+
def initialize(callback, options)
|
10
|
+
@callback = callback
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def for_event?(event_type)
|
15
|
+
on.include?(event_type.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(model)
|
19
|
+
return unless execute?(model)
|
20
|
+
|
21
|
+
model.instance_exec(&callback)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def on
|
27
|
+
Array(options.fetch(:on, [])).map(&:to_sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute?(model)
|
31
|
+
if options.key?(:if)
|
32
|
+
model.instance_exec(&options[:if])
|
33
|
+
elsif options.key?(:unless)
|
34
|
+
!model.instance_exec(&options[:unless])
|
35
|
+
else
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
module ReliableModel
|
5
|
+
class ReliableCallbacksRegistry
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
delegate :each, to: :registry
|
9
|
+
|
10
|
+
attr_reader :registry
|
11
|
+
private :registry
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@registry = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(item)
|
18
|
+
registry << item
|
19
|
+
end
|
20
|
+
|
21
|
+
def for_event_type(event_type)
|
22
|
+
registry.select { |cb| cb.for_event?(event_type) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
module ReliableModel
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
NOT_PROVIDED = Object.new.freeze
|
8
|
+
private_constant :NOT_PROVIDED
|
9
|
+
|
10
|
+
included do
|
11
|
+
after_create :transactional_outbox_insert_model_created
|
12
|
+
after_update :transactional_outbox_insert_model_updated
|
13
|
+
after_destroy :transactional_outbox_insert_model_destroyed
|
14
|
+
|
15
|
+
def self.reliable_after_commit_callbacks
|
16
|
+
@reliable_after_commit_callbacks ||= RailsTransactionalOutbox::ReliableModel::ReliableCallbacksRegistry.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reliable_after_commit(method_name = NOT_PROVIDED, options = {}, &block)
|
20
|
+
if block
|
21
|
+
callback_proc = block
|
22
|
+
else
|
23
|
+
raise ArgumentError.new("You must provide a block or a method name") unless method_name.is_a?(Symbol)
|
24
|
+
|
25
|
+
callback_proc = -> { send(method_name) }
|
26
|
+
end
|
27
|
+
|
28
|
+
final_options = options.reverse_merge(on: %i[create update destroy])
|
29
|
+
reliable_after_commit_callbacks << ReliableCallback.new(callback_proc, final_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.reliable_after_create_commit(method_name = NOT_PROVIDED, options = {}, &block)
|
33
|
+
reliable_after_commit(method_name, options.merge(on: :create), &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reliable_after_update_commit(method_name = NOT_PROVIDED, options = {}, &block)
|
37
|
+
reliable_after_commit(method_name, options.merge(on: :update), &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.reliable_after_destroy_commit(method_name = NOT_PROVIDED, options = {}, &block)
|
41
|
+
reliable_after_commit(method_name, options.merge(on: :destroy), &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.reliable_after_save_commit(method_name = NOT_PROVIDED, options = {}, &block)
|
45
|
+
reliable_after_commit(method_name, options.merge(on: %i[create update]), &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :original_previous_changes, :previous_changes
|
49
|
+
|
50
|
+
def previous_changes
|
51
|
+
@previous_changes || original_previous_changes
|
52
|
+
end
|
53
|
+
|
54
|
+
def previous_changes=(changeset)
|
55
|
+
@previous_changes = changeset
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def transactional_outbox_insert_model_created
|
61
|
+
transactional_outbox_entry_factory.build(self, :create).save
|
62
|
+
end
|
63
|
+
|
64
|
+
def transactional_outbox_insert_model_updated
|
65
|
+
transactional_outbox_entry_factory.build(self, :update).save!
|
66
|
+
end
|
67
|
+
|
68
|
+
def transactional_outbox_insert_model_destroyed
|
69
|
+
transactional_outbox_entry_factory.build(self, :destroy).save!
|
70
|
+
end
|
71
|
+
|
72
|
+
def transactional_outbox_entry_factory
|
73
|
+
@transactional_outbox_entry_factory ||= RailsTransactionalOutbox::OutboxEntryFactory.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def reliable_after_commit_callbacks
|
78
|
+
self.class.reliable_after_commit_callbacks
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
class Runner
|
5
|
+
attr_reader :config, :id
|
6
|
+
private :config
|
7
|
+
|
8
|
+
def initialize(config: RailsTransactionalOutbox.configuration)
|
9
|
+
@id = SecureRandom.uuid
|
10
|
+
@config = config
|
11
|
+
logger.push_tags("RailsTransactionalOutbox::Runner #{id}") if logger.respond_to?(:push_tags)
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
log("started")
|
16
|
+
instrument("rails_transactional_outbox.started")
|
17
|
+
@should_stop = false
|
18
|
+
ensure_database_connection!
|
19
|
+
loop do
|
20
|
+
if @should_stop
|
21
|
+
instrument("rails_transactional_outbox.shutting_down")
|
22
|
+
log("shutting down")
|
23
|
+
break
|
24
|
+
end
|
25
|
+
entries = process_entries
|
26
|
+
instrument("rails_transactional_outbox.heartbeat")
|
27
|
+
sleep sleep_interval_for(entries)
|
28
|
+
end
|
29
|
+
rescue => e
|
30
|
+
error_handler.capture_exception(e)
|
31
|
+
log("error: #{e} #{e.message}")
|
32
|
+
instrument("rails_transactional_outbox.error", error: e, error_message: e.message)
|
33
|
+
raise e
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
log("Rails Transactional Outbox Worker stopping")
|
38
|
+
instrument("rails_transactional_outbox.stopped")
|
39
|
+
@should_stop = true
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
delegate :error_handler, :transactional_outbox_worker_sleep_seconds,
|
45
|
+
:transactional_outbox_worker_idle_delay_multiplier, :database_connection_provider, :logger, to: :config
|
46
|
+
delegate :monitor, to: RailsTransactionalOutbox
|
47
|
+
|
48
|
+
def process_entries
|
49
|
+
tracer.trace("rails_transactional_outbox_entries_processor") do
|
50
|
+
outbox_entries_processor.call do |record|
|
51
|
+
if record.failed?
|
52
|
+
instrument("rails_transactional_outbox.record_processing_failed", outbox_record: record)
|
53
|
+
error("failed to process #{record.inspect}")
|
54
|
+
error_handler.capture_exception(record.error)
|
55
|
+
else
|
56
|
+
debug("processed #{record.inspect}")
|
57
|
+
instrument("rails_transactional_outbox.record_processed", outbox_record: record)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_database_connection!
|
64
|
+
database_connection_provider.connection.reconnect!
|
65
|
+
end
|
66
|
+
|
67
|
+
def outbox_entries_processor
|
68
|
+
@outbox_entries_processor ||= RailsTransactionalOutbox::OutboxEntriesProcessor.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def log(message)
|
72
|
+
logger.info("#{log_prefix} #{message}")
|
73
|
+
end
|
74
|
+
|
75
|
+
def debug(message)
|
76
|
+
logger.debug("#{log_prefix} #{message}")
|
77
|
+
end
|
78
|
+
|
79
|
+
def error(message)
|
80
|
+
logger.error("#{log_prefix} #{message}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def log_prefix
|
84
|
+
"[Rails Transactional Outbox Worker] "
|
85
|
+
end
|
86
|
+
|
87
|
+
def instrument(*args, **kwargs)
|
88
|
+
monitor.instrument(*args, **kwargs) do
|
89
|
+
yield if block_given?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def tracer
|
94
|
+
@tracer ||= if Object.const_defined?(:Datadog)
|
95
|
+
RailsTransactionalOutbox::Tracers::DatadogTracer.new
|
96
|
+
else
|
97
|
+
RailsTransactionalOutbox::Tracers::NullTracer
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def sleep_interval_for(entries)
|
102
|
+
RailsTransactionalOutbox::RunnerSleepInterval.interval_for(entries, transactional_outbox_worker_sleep_seconds,
|
103
|
+
transactional_outbox_worker_idle_delay_multiplier)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
class RunnerSleepInterval
|
5
|
+
# TODO: maybe apply some backoff or longer pause if there were no entries to be processed?
|
6
|
+
def self.interval_for(processed_entries, sleep_seconds, idle_delay_multiplier)
|
7
|
+
if processed_entries.any?
|
8
|
+
sleep_seconds
|
9
|
+
else
|
10
|
+
sleep_seconds * idle_delay_multiplier
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsTransactionalOutbox
|
4
|
+
module Tracers
|
5
|
+
class DatadogTracer
|
6
|
+
SERVICE_NAME = "rails_transactional_outbox_worker"
|
7
|
+
private_constant :SERVICE_NAME
|
8
|
+
|
9
|
+
def self.service_name
|
10
|
+
SERVICE_NAME
|
11
|
+
end
|
12
|
+
|
13
|
+
def trace(event_name)
|
14
|
+
tracer.trace(event_name, span_type: "worker", service: self.class.service_name,
|
15
|
+
on_error: error_handler) do |_span|
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def tracer
|
23
|
+
if Datadog.respond_to?(:tracer)
|
24
|
+
Datadog.tracer
|
25
|
+
else
|
26
|
+
Datadog::Tracing
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_handler
|
31
|
+
->(span, error) { span.set_error(error) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
require "logger"
|
5
|
+
require "dry-monitor"
|
6
|
+
require "sigurd"
|
7
|
+
require "concurrent-ruby"
|
8
|
+
|
9
|
+
class RailsTransactionalOutbox
|
10
|
+
def self.loader
|
11
|
+
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
|
12
|
+
loader.ignore(
|
13
|
+
"#{__dir__}/rails-transactional-outbox.rb",
|
14
|
+
"#{__dir__}/tracers/datadog_tracer.rb"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configuration
|
20
|
+
@configuration ||= RailsTransactionalOutbox::Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configure
|
24
|
+
yield configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.monitor
|
28
|
+
@monitor ||= RailsTransactionalOutbox::Monitor.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.reset
|
32
|
+
@configuration = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.outbox_worker_health_check
|
36
|
+
@outbox_worker_health_check ||= RailsTransactionalOutbox::HealthCheck.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.enable_outbox_worker_healthcheck
|
40
|
+
monitor.subscribe("rails_transactional_outbox.started") { outbox_worker_health_check.register_heartbeat }
|
41
|
+
monitor.subscribe("rails_transactional_outbox.stopped") { outbox_worker_health_check.worker_stopped }
|
42
|
+
monitor.subscribe("rails_transactional_outbox.heartbeat") { outbox_worker_health_check.register_heartbeat }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.start_outbox_worker(threads_number: 1)
|
46
|
+
runners = (1..threads_number).map { RailsTransactionalOutbox::Runner.new(config: configuration) }
|
47
|
+
executor = Sigurd::Executor.new(runners, sleep_seconds: 5, logger: configuration.logger)
|
48
|
+
signal_handler = Sigurd::SignalHandler.new(executor)
|
49
|
+
signal_handler.run!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
RailsTransactionalOutbox.loader.setup
|
54
|
+
require "rails_transactional_outbox/railtie" if defined?(Rails)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :rails_transactional_outbox do
|
4
|
+
desc "Starts the RailsTransactionalOutbox worker"
|
5
|
+
task worker: :environment do
|
6
|
+
$stdout.sync = true
|
7
|
+
Rails.logger.info("Running rails_transactional_outbox:worker rake task.")
|
8
|
+
threads_number = ENV.fetch("RAILS_TRANSACTIONAL_OUTBOX_THREADS_NUMBER", 1).to_i
|
9
|
+
RailsTransactionalOutbox.start_outbox_worker(threads_number: threads_number)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rails_transactional_outbox/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rails-transactional-outbox"
|
7
|
+
spec.version = RailsTransactionalOutbox::VERSION
|
8
|
+
spec.authors = ["Karol Galanciak"]
|
9
|
+
spec.email = ["karol.galanciak@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "An implementation of transactional outbox pattern to be used with Rails."
|
12
|
+
spec.description = "An implementation of transactional outbox pattern to be used with Rails."
|
13
|
+
spec.homepage = "https://github.com/BookingSync/rails-transactional-outbox"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
16
|
+
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/BookingSync/rails-transactional-outbox"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/BookingSync/rails-transactional-outbox/blob/master/CHANGELOG.md"
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
%x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
27
|
+
end
|
28
|
+
spec.bindir = "bin"
|
29
|
+
spec.executables = %w[rails_transactional_outbox_health_check]
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
spec.add_dependency "activerecord", ">= 5"
|
34
|
+
spec.add_dependency "activesupport", ">= 3.2"
|
35
|
+
spec.add_dependency "concurrent-ruby"
|
36
|
+
spec.add_dependency "dry-monitor"
|
37
|
+
spec.add_dependency "redis"
|
38
|
+
spec.add_dependency "sigurd"
|
39
|
+
spec.add_dependency "zeitwerk"
|
40
|
+
|
41
|
+
# For more information and examples about making a new gem, checkout our
|
42
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
43
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
44
|
+
end
|