rails-transactional-outbox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|