transactional_outbox 1.0.0beta1
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/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/lib/generators/transactional_outbox/migration/migration_generator.rb +24 -0
- data/lib/generators/transactional_outbox/migration/templates/active_record.rb.erb +18 -0
- data/lib/generators/transactional_outbox/migration/templates/sequel.rb.erb +26 -0
- data/lib/transactional_outbox/adapters_container.rb +24 -0
- data/lib/transactional_outbox/constants.rb +16 -0
- data/lib/transactional_outbox/database/adapters/active_record.rb +25 -0
- data/lib/transactional_outbox/database/adapters/interface.rb +25 -0
- data/lib/transactional_outbox/database/adapters/null.rb +37 -0
- data/lib/transactional_outbox/database/adapters/sequel.rb +26 -0
- data/lib/transactional_outbox/database/adapters.rb +7 -0
- data/lib/transactional_outbox/database.rb +25 -0
- data/lib/transactional_outbox/event/builder.rb +18 -0
- data/lib/transactional_outbox/event/contextable.rb +35 -0
- data/lib/transactional_outbox/event/payloadable.rb +17 -0
- data/lib/transactional_outbox/event.rb +76 -0
- data/lib/transactional_outbox/exceptions.rb +12 -0
- data/lib/transactional_outbox/exponential_backoff.rb +7 -0
- data/lib/transactional_outbox/producer/adapters/interface.rb +19 -0
- data/lib/transactional_outbox/producer/adapters/kafka.rb +25 -0
- data/lib/transactional_outbox/producer/adapters/null.rb +20 -0
- data/lib/transactional_outbox/producer/adapters.rb +8 -0
- data/lib/transactional_outbox/producer.rb +31 -0
- data/lib/transactional_outbox/railtie.rb +13 -0
- data/lib/transactional_outbox/relay/event_processor.rb +54 -0
- data/lib/transactional_outbox/relay/failover.rb +9 -0
- data/lib/transactional_outbox/relay/graceful_shutdown.rb +37 -0
- data/lib/transactional_outbox/relay/monitor.rb +28 -0
- data/lib/transactional_outbox/relay/runner.rb +50 -0
- data/lib/transactional_outbox/relay/worker_set/processor.rb +55 -0
- data/lib/transactional_outbox/relay/worker_set/worker.rb +68 -0
- data/lib/transactional_outbox/relay/worker_set.rb +38 -0
- data/lib/transactional_outbox/relay.rb +9 -0
- data/lib/transactional_outbox/tasks/relay.rake +8 -0
- data/lib/transactional_outbox/version.rb +5 -0
- data/lib/transactional_outbox.rb +82 -0
- metadata +257 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "event/contextable"
|
|
4
|
+
require_relative "event/payloadable"
|
|
5
|
+
|
|
6
|
+
module TransactionalOutbox
|
|
7
|
+
class Event
|
|
8
|
+
extend Dry::Configurable
|
|
9
|
+
extend Contextable
|
|
10
|
+
extend Payloadable
|
|
11
|
+
|
|
12
|
+
setting :schema
|
|
13
|
+
setting :aggregate_type
|
|
14
|
+
setting :event_type
|
|
15
|
+
setting :topic
|
|
16
|
+
setting :event_builder
|
|
17
|
+
|
|
18
|
+
config.values.keys.each do |attr|
|
|
19
|
+
define_singleton_method(attr) do |value|
|
|
20
|
+
config[attr] = value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create!(context)
|
|
25
|
+
validate_context!(context)
|
|
26
|
+
|
|
27
|
+
event = build_event(context)
|
|
28
|
+
|
|
29
|
+
save([event])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bulk_create!(contexts)
|
|
33
|
+
validate_contexts!(contexts)
|
|
34
|
+
|
|
35
|
+
events = build_events(contexts)
|
|
36
|
+
|
|
37
|
+
save(events)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def config = @config ||= self.class.config
|
|
43
|
+
|
|
44
|
+
def event_builder
|
|
45
|
+
@event_builder ||= begin
|
|
46
|
+
klass = config.event_builder || TransactionalOutbox.config.default_event_builder
|
|
47
|
+
|
|
48
|
+
Object.const_get(klass)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def save(rows) = TransactionalOutbox::Database.new.insert_events(rows)
|
|
53
|
+
|
|
54
|
+
def validate_event(schema, payload)
|
|
55
|
+
return true if config.schema.nil?
|
|
56
|
+
|
|
57
|
+
errors = JSON::Validator.fully_validate(schema, payload)
|
|
58
|
+
|
|
59
|
+
return true if errors.empty?
|
|
60
|
+
|
|
61
|
+
raise TransactionalOutbox::InvalidPayloadError, errors.join(", ")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_event(context)
|
|
65
|
+
payload = build_payload(context)
|
|
66
|
+
|
|
67
|
+
validate_event(config.schema, payload)
|
|
68
|
+
|
|
69
|
+
event_builder.build(self.class.config, payload, context)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_events(contexts) = contexts.map { build_event(_1) }
|
|
73
|
+
|
|
74
|
+
def validate_contexts!(contexts) = contexts.each { |context| validate_context!(context) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
module Exceptions
|
|
5
|
+
class UnsupportedEventTypeError < StandardError; end
|
|
6
|
+
class InvalidPayloadError < StandardError; end
|
|
7
|
+
class UnknownAdapterError < StandardError; end
|
|
8
|
+
class AdapterAlreadyExistsError < StandardError; end
|
|
9
|
+
class MigrationFileNotExistsError < StandardError; end
|
|
10
|
+
class InvalidContextError < StandardError; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Producer
|
|
5
|
+
class Adapters
|
|
6
|
+
class Interface
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def produce_batch(_topic, _batch) = raise NotImplementedError
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
attr_reader :client
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Producer
|
|
5
|
+
class Adapters
|
|
6
|
+
class Kafka < Interface
|
|
7
|
+
def produce_batch(topic, events)
|
|
8
|
+
buffer_events(topic, events)
|
|
9
|
+
|
|
10
|
+
producer.deliver_messages
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def producer = @producer ||= client.producer
|
|
16
|
+
|
|
17
|
+
def buffer_events(topic, events)
|
|
18
|
+
events.each do |event|
|
|
19
|
+
producer.produce(event.to_json, topic:)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Producer
|
|
5
|
+
class Adapters
|
|
6
|
+
class Null < Interface
|
|
7
|
+
def messages = @messages ||= {}
|
|
8
|
+
def clear_store = @messages = {}
|
|
9
|
+
|
|
10
|
+
def produce_batch(topic, events)
|
|
11
|
+
msg = messages[topic] ||= []
|
|
12
|
+
|
|
13
|
+
msg.concat(events)
|
|
14
|
+
|
|
15
|
+
TransactionalOutbox.config.logger.info("Batch produced")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Producer
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
def_delegators :@adapter, :produce_batch
|
|
8
|
+
|
|
9
|
+
attr_reader :adapter
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@adapter = TransactionalOutbox::Producer::Adapters.resolve(fetch_adapter).new(fetch_client)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def config = @config ||= TransactionalOutbox.config
|
|
18
|
+
|
|
19
|
+
def fetch_adapter
|
|
20
|
+
return :null if config.test_environment
|
|
21
|
+
|
|
22
|
+
config.producer.adapter.to_sym
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_client
|
|
26
|
+
return if config.test_environment
|
|
27
|
+
|
|
28
|
+
config.producer.client
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
5
|
+
generators do
|
|
6
|
+
require_relative "../generators/transactional_outbox/migration/migration_generator"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
rake_tasks do
|
|
10
|
+
Dir[File.join(File.dirname(__FILE__), "tasks/*.rake")].each { |f| load f }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Relay
|
|
5
|
+
class EventProcessor
|
|
6
|
+
attr_reader :topic, :db, :producer
|
|
7
|
+
|
|
8
|
+
def initialize(topic)
|
|
9
|
+
@topic = topic
|
|
10
|
+
@db = TransactionalOutbox::Database.new
|
|
11
|
+
@producer = TransactionalOutbox::Producer.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
events = db.fetch_events(topic, config.relay.batch_size)
|
|
16
|
+
|
|
17
|
+
return if events.empty?
|
|
18
|
+
|
|
19
|
+
process_events(events)
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
TransactionalOutbox::Relay.monitor.publish(
|
|
22
|
+
TransactionalOutbox::WORKER_EXCEPTIONS_TOTAL_MONITOR_EVENT,
|
|
23
|
+
{ topic:, exception: e.class.to_s }
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
resolve_failover.call(e, events)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def config = @config ||= TransactionalOutbox.config
|
|
32
|
+
|
|
33
|
+
def resolve_failover
|
|
34
|
+
configured = config.relay.failover
|
|
35
|
+
|
|
36
|
+
return Object.get_const(configured) if configured.is_a?(String)
|
|
37
|
+
|
|
38
|
+
configured
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_events(events)
|
|
42
|
+
producer.produce_batch(topic, events)
|
|
43
|
+
|
|
44
|
+
db.delete_events(events.map { |x| x[:id] })
|
|
45
|
+
|
|
46
|
+
TransactionalOutbox::Relay.monitor.publish(
|
|
47
|
+
TransactionalOutbox::WORKER_EVENTS_PROCESSED_MONITOR_EVENT, { topic:, count: events.size }
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
config.logger.info("Events have sent to topic #{topic}: #{events.size}")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Relay
|
|
5
|
+
class GracefulShutdown
|
|
6
|
+
class << self
|
|
7
|
+
def call(worker_set)
|
|
8
|
+
worker_set.stop_workers
|
|
9
|
+
|
|
10
|
+
wait_workers(worker_set)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def config = @config ||= TransactionalOutbox.config
|
|
16
|
+
|
|
17
|
+
def wait_workers(worker_set)
|
|
18
|
+
shutdown_time = Time.now.utc
|
|
19
|
+
|
|
20
|
+
while shutdown_time + config.shutdown_waiting_time_seconds > Time.now.utc
|
|
21
|
+
if worker_set.all_stopped?
|
|
22
|
+
TransactionalOutbox::Relay.monitor.publish(TransactionalOutbox::RUNNER_STOPPED_MONITOR_EVENT)
|
|
23
|
+
|
|
24
|
+
config.logger.info("All workers have stopped.")
|
|
25
|
+
|
|
26
|
+
return true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sleep(0.1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Relay
|
|
5
|
+
class Monitor
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
def_delegators :@notifications, :publish, :subscribe
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@notifications = Dry::Monitor::Notifications.new(:transactional_outbox).tap { |n| register_events(n) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :notifications
|
|
17
|
+
|
|
18
|
+
def register_events(notifications)
|
|
19
|
+
notifications.register_event(TransactionalOutbox::RUNNER_INIT_MONITOR_EVENT)
|
|
20
|
+
notifications.register_event(TransactionalOutbox::RUNNER_STOPPED_MONITOR_EVENT)
|
|
21
|
+
notifications.register_event(TransactionalOutbox::WORKER_RUN_MONITOR_EVENT)
|
|
22
|
+
notifications.register_event(TransactionalOutbox::WORKER_EVENTS_PROCESSED_MONITOR_EVENT)
|
|
23
|
+
notifications.register_event(TransactionalOutbox::WORKER_STOPPED_MONITOR_EVENT)
|
|
24
|
+
notifications.register_event(TransactionalOutbox::WORKER_EXCEPTIONS_TOTAL_MONITOR_EVENT)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "worker_set"
|
|
4
|
+
require_relative "worker_set/processor"
|
|
5
|
+
require_relative "graceful_shutdown"
|
|
6
|
+
|
|
7
|
+
module TransactionalOutbox
|
|
8
|
+
class Relay
|
|
9
|
+
class Runner
|
|
10
|
+
class << self
|
|
11
|
+
def start
|
|
12
|
+
TransactionalOutbox::Relay.monitor.publish(TransactionalOutbox::RUNNER_INIT_MONITOR_EVENT)
|
|
13
|
+
|
|
14
|
+
worker_set = TransactionalOutbox::Relay::WorkerSet.new
|
|
15
|
+
|
|
16
|
+
start_relay(worker_set)
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
shutdown(e, worker_set, 1)
|
|
19
|
+
rescue SignalException => e
|
|
20
|
+
shutdown(e, worker_set, 0)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def start_relay(worker_set) = TransactionalOutbox::Relay::WorkerSet::Processor.new(worker_set).call
|
|
26
|
+
def config = @config ||= TransactionalOutbox.config
|
|
27
|
+
|
|
28
|
+
def start_graceful_shutdown(worker_set)
|
|
29
|
+
return unless worker_set
|
|
30
|
+
|
|
31
|
+
TransactionalOutbox::Relay::GracefulShutdown.call(worker_set)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def call_exit(code)
|
|
35
|
+
return true if config.test_environment
|
|
36
|
+
|
|
37
|
+
exit(code)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shutdown(exception, worker_set, exit_code)
|
|
41
|
+
config.logger.info("Received exception: #{exception}. Shutting down...")
|
|
42
|
+
|
|
43
|
+
start_graceful_shutdown(worker_set)
|
|
44
|
+
|
|
45
|
+
call_exit(exit_code)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TransactionalOutbox
|
|
4
|
+
class Relay
|
|
5
|
+
class WorkerSet
|
|
6
|
+
class Processor
|
|
7
|
+
def initialize(worker_set)
|
|
8
|
+
@worker_set = worker_set
|
|
9
|
+
@db = TransactionalOutbox::Database.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call # rubocop:disable Metrics/MethodLength
|
|
13
|
+
@retry_counter = 0
|
|
14
|
+
|
|
15
|
+
loop do
|
|
16
|
+
topics = db.fetch_topics
|
|
17
|
+
|
|
18
|
+
process_topics(topics)
|
|
19
|
+
|
|
20
|
+
break if config.test_environment
|
|
21
|
+
|
|
22
|
+
@retry_counter = 0
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
config.logger.error("Exception: #{e}, trying to retry...")
|
|
25
|
+
|
|
26
|
+
raise e if @retry_counter >= config.relay.max_runner_retries_count
|
|
27
|
+
|
|
28
|
+
@retry_counter += 1
|
|
29
|
+
|
|
30
|
+
sleep(calculate_retry_delay)
|
|
31
|
+
|
|
32
|
+
retry
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :worker_set, :db
|
|
39
|
+
|
|
40
|
+
def config = @config ||= TransactionalOutbox.config
|
|
41
|
+
def calculate_retry_delay = TransactionalOutbox::ExponentialBackoff.calculate_retry_delay(@retry_counter)
|
|
42
|
+
|
|
43
|
+
def process_topics(topics)
|
|
44
|
+
topics.each do |topic|
|
|
45
|
+
worker = worker_set.get_worker(topic)
|
|
46
|
+
|
|
47
|
+
worker ? worker_set.try_to_recover_worker(topic) : worker_set.add_worker(topic)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sleep(config.relay.delay_between_worker_set_processor_cycles)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../event_processor"
|
|
4
|
+
|
|
5
|
+
module TransactionalOutbox
|
|
6
|
+
class Relay
|
|
7
|
+
class WorkerSet
|
|
8
|
+
class Worker
|
|
9
|
+
attr_reader :topic, :db, :producer
|
|
10
|
+
|
|
11
|
+
def initialize(topic)
|
|
12
|
+
@topic = topic
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
@thread = spawn_thread
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def shutdown
|
|
20
|
+
return true if thread[:stopped]
|
|
21
|
+
|
|
22
|
+
thread[:shutdown] = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def shutting_down? = !thread.nil? && !!thread[:shutdown]
|
|
26
|
+
def stopped? = !thread.nil? && !!thread[:stopped]
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :thread
|
|
31
|
+
|
|
32
|
+
def config = @config ||= TransactionalOutbox.config
|
|
33
|
+
|
|
34
|
+
def spawn_thread # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
35
|
+
TransactionalOutbox::Relay.monitor.publish(TransactionalOutbox::WORKER_RUN_MONITOR_EVENT, { topic: })
|
|
36
|
+
|
|
37
|
+
Thread.new do
|
|
38
|
+
loop do
|
|
39
|
+
TransactionalOutbox::Relay::EventProcessor.new(topic).call
|
|
40
|
+
|
|
41
|
+
if Thread.current[:shutdown]
|
|
42
|
+
config.logger.info("Thread for topic #{topic} successfully shutted down")
|
|
43
|
+
|
|
44
|
+
mark_thread_as_stopped
|
|
45
|
+
|
|
46
|
+
break
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
break if config.test_environment
|
|
50
|
+
|
|
51
|
+
sleep(config.relay.wait_between_batches_seconds)
|
|
52
|
+
end
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
mark_thread_as_stopped
|
|
55
|
+
|
|
56
|
+
raise e
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def mark_thread_as_stopped
|
|
61
|
+
Thread.current[:stopped] = true
|
|
62
|
+
|
|
63
|
+
TransactionalOutbox::Relay.monitor.publish(TransactionalOutbox::WORKER_STOPPED_MONITOR_EVENT, { topic: })
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "worker_set/worker"
|
|
4
|
+
|
|
5
|
+
module TransactionalOutbox
|
|
6
|
+
class Relay
|
|
7
|
+
class WorkerSet
|
|
8
|
+
def initialize
|
|
9
|
+
@workers = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_worker(topic) = workers[topic]
|
|
13
|
+
|
|
14
|
+
def add_worker(topic)
|
|
15
|
+
return if workers.key?(topic)
|
|
16
|
+
|
|
17
|
+
create_worker(topic)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def try_to_recover_worker(topic)
|
|
21
|
+
worker = get_worker(topic)
|
|
22
|
+
|
|
23
|
+
return if !worker.nil? && (worker.shutting_down? || !worker.stopped?)
|
|
24
|
+
|
|
25
|
+
create_worker(topic)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stop_workers = workers.each_value(&:shutdown)
|
|
29
|
+
def all_stopped? = workers.values.all?(&:stopped?)
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
attr_reader :workers
|
|
34
|
+
|
|
35
|
+
def create_worker(topic) = workers[topic] = Worker.new(topic).tap(&:run)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
require "dry-container"
|
|
6
|
+
require "dry-configurable"
|
|
7
|
+
require "dry-monitor"
|
|
8
|
+
require "dry-validation"
|
|
9
|
+
require "json-schema"
|
|
10
|
+
require "securerandom"
|
|
11
|
+
|
|
12
|
+
require_relative "transactional_outbox/version"
|
|
13
|
+
require_relative "transactional_outbox/adapters_container"
|
|
14
|
+
require_relative "transactional_outbox/database/adapters"
|
|
15
|
+
require_relative "transactional_outbox/database/adapters/interface"
|
|
16
|
+
require_relative "transactional_outbox/database/adapters/null"
|
|
17
|
+
require_relative "transactional_outbox/database/adapters/active_record"
|
|
18
|
+
require_relative "transactional_outbox/database/adapters/sequel"
|
|
19
|
+
require_relative "transactional_outbox/producer/adapters"
|
|
20
|
+
require_relative "transactional_outbox/producer/adapters/interface"
|
|
21
|
+
require_relative "transactional_outbox/producer/adapters/null"
|
|
22
|
+
require_relative "transactional_outbox/producer/adapters/kafka"
|
|
23
|
+
require_relative "transactional_outbox/constants"
|
|
24
|
+
require_relative "transactional_outbox/database"
|
|
25
|
+
require_relative "transactional_outbox/event"
|
|
26
|
+
require_relative "transactional_outbox/event/builder"
|
|
27
|
+
require_relative "transactional_outbox/exceptions"
|
|
28
|
+
require_relative "transactional_outbox/exponential_backoff"
|
|
29
|
+
require_relative "transactional_outbox/producer"
|
|
30
|
+
|
|
31
|
+
require_relative "transactional_outbox/relay"
|
|
32
|
+
require_relative "transactional_outbox/relay/failover"
|
|
33
|
+
require_relative "transactional_outbox/relay/runner"
|
|
34
|
+
|
|
35
|
+
require_relative "transactional_outbox/railtie" if defined?(Rails::Railtie)
|
|
36
|
+
|
|
37
|
+
TransactionalOutbox::Database::Adapters.register(:null, TransactionalOutbox::Database::Adapters::Null)
|
|
38
|
+
TransactionalOutbox::Database::Adapters.register(:sequel, TransactionalOutbox::Database::Adapters::Sequel)
|
|
39
|
+
TransactionalOutbox::Database::Adapters.register(:active_record, TransactionalOutbox::Database::Adapters::ActiveRecord)
|
|
40
|
+
|
|
41
|
+
TransactionalOutbox::Producer::Adapters.register(:null, TransactionalOutbox::Producer::Adapters::Null)
|
|
42
|
+
TransactionalOutbox::Producer::Adapters.register(:kafka, TransactionalOutbox::Producer::Adapters::Kafka)
|
|
43
|
+
|
|
44
|
+
module TransactionalOutbox
|
|
45
|
+
include Constants
|
|
46
|
+
include Exceptions
|
|
47
|
+
|
|
48
|
+
extend Dry::Configurable
|
|
49
|
+
|
|
50
|
+
setting :logger
|
|
51
|
+
setting :outbox_table_name, default: "outbox_events"
|
|
52
|
+
setting :default_event_builder, default: TransactionalOutbox::Event::Builder
|
|
53
|
+
setting :shutdown_waiting_time_seconds, default: 10
|
|
54
|
+
setting :migrations_directory
|
|
55
|
+
setting :test_environment, default: false
|
|
56
|
+
|
|
57
|
+
setting :relay do
|
|
58
|
+
setting :batch_size, default: 20
|
|
59
|
+
setting :wait_between_batches_seconds, default: 0.1
|
|
60
|
+
setting :failover, default: TransactionalOutbox::Relay::Failover
|
|
61
|
+
setting :max_runner_retries_count, default: 5
|
|
62
|
+
setting :delay_between_worker_set_processor_cycles, default: 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
setting :db do
|
|
66
|
+
setting :adapter
|
|
67
|
+
setting :connection_data
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
setting :producer do
|
|
71
|
+
setting :adapter
|
|
72
|
+
setting :client
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
def transaction(event)
|
|
77
|
+
Database.new.transaction do
|
|
78
|
+
yield(event.new)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|