ruby_event_store-outbox 0.0.7 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8e2e739e68444fa47e69b09357b61b1d7b7136db13663028b97eaff9c8f3375
4
- data.tar.gz: fbf657458847ca98c43698c4b40acea4993e0a9c752f788e112490e190d2415b
3
+ metadata.gz: c1d9945d7b34ce9254c8150dc671453cdf3c72a5799bc39b11f1e86e109baa8c
4
+ data.tar.gz: 58fb2365dd712495a8681fb78ed444662422cfddf3b2b165fda5e4299082dc91
5
5
  SHA512:
6
- metadata.gz: a2a08df4a60ba2e339e2914c20fe4a5638f18b20a55a580e2b048758bf273d20fbff1a05fe0eb183d38d625ce6ce94a2e8cc034a1136320ade6bd0e3adeec165
7
- data.tar.gz: 8c6114269f96e992a134b5b731a7be3ba870c4a57d75bfc74129cd385b5100c93d450a9574a53fd3d1bf0489801a0839fc9d3fc9e35237d96672dc4c62baf0da
6
+ metadata.gz: 01fbff71356a4095838f197b96e9673762d02f9677d0d0df3075757861c273131b636b6e181db0ce165fa7d04f2d45b6450e4cd19a49261eb6f9e87d6782b8f7
7
+ data.tar.gz: 53bea6ab8e0e3c2bfad41018fbc65d7f2de33af168b05d5578cf6349a56614be4c4e00448b3954ed59ca42f637908e635701f5c2eb2ef7d4958f1078c8eac18d
@@ -11,5 +11,13 @@ class CreateEventStoreOutbox < ActiveRecord::Migration<%= migration_version %>
11
11
  end
12
12
  add_index :event_store_outbox, [:format, :enqueued_at, :split_key], name: "index_event_store_outbox_for_pool"
13
13
  add_index :event_store_outbox, [:created_at, :enqueued_at], name: "index_event_store_outbox_for_clear"
14
+
15
+ create_table(:event_store_outbox_locks, force: false) do |t|
16
+ t.string :format, null: false
17
+ t.string :split_key, null: false
18
+ t.datetime :locked_at, null: true
19
+ t.string :locked_by, null: true, limit: 36
20
+ end
21
+ add_index :event_store_outbox_locks, [:format, :split_key], name: "index_event_store_outbox_locks_for_locking", unique: true
14
22
  end
15
23
  end
@@ -5,6 +5,7 @@ module RubyEventStore
5
5
  end
6
6
  end
7
7
 
8
+ require_relative 'outbox/fetch_specification'
8
9
  require_relative 'outbox/record'
9
10
  require_relative 'outbox/sidekiq_scheduler'
10
11
  require_relative 'outbox/consumer'
@@ -59,7 +59,8 @@ module RubyEventStore
59
59
  end
60
60
 
61
61
  def build_consumer(options)
62
- logger = Logger.new(STDOUT, level: options.log_level, progname: "RES-Outbox")
62
+ consumer_uuid = SecureRandom.uuid
63
+ logger = Logger.new(STDOUT, level: options.log_level, progname: "RES-Outbox #{consumer_uuid}")
63
64
  consumer_configuration = Consumer::Configuration.new(
64
65
  split_keys: options.split_keys,
65
66
  message_format: options.message_format,
@@ -69,6 +70,7 @@ module RubyEventStore
69
70
  )
70
71
  metrics = Metrics.from_url(options.metrics_url)
71
72
  outbox_consumer = RubyEventStore::Outbox::Consumer.new(
73
+ consumer_uuid,
72
74
  options,
73
75
  logger: logger,
74
76
  metrics: metrics,
@@ -3,11 +3,13 @@ require "redis"
3
3
  require "active_record"
4
4
  require "ruby_event_store/outbox/record"
5
5
  require "ruby_event_store/outbox/sidekiq5_format"
6
+ require "ruby_event_store/outbox/sidekiq_processor"
6
7
 
7
8
  module RubyEventStore
8
9
  module Outbox
9
10
  class Consumer
10
11
  SLEEP_TIME_WHEN_NOTHING_TO_DO = 0.1
12
+ MAXIMUM_BATCH_FETCHES_IN_ONE_LOCK = 10
11
13
 
12
14
  class Configuration
13
15
  def initialize(
@@ -38,24 +40,26 @@ module RubyEventStore
38
40
  attr_reader :split_keys, :message_format, :batch_size, :database_url, :redis_url
39
41
  end
40
42
 
41
- def initialize(configuration, clock: Time, logger:, metrics:)
43
+ def initialize(consumer_uuid, configuration, clock: Time, logger:, metrics:)
42
44
  @split_keys = configuration.split_keys
43
45
  @clock = clock
44
- @redis = Redis.new(url: configuration.redis_url)
45
46
  @logger = logger
46
47
  @metrics = metrics
47
48
  @batch_size = configuration.batch_size
49
+ @consumer_uuid = consumer_uuid
48
50
  ActiveRecord::Base.establish_connection(configuration.database_url) unless ActiveRecord::Base.connected?
51
+ if ActiveRecord::Base.connection.adapter_name == "Mysql2"
52
+ ActiveRecord::Base.connection.execute("SET SESSION innodb_lock_wait_timeout = 1;")
53
+ end
49
54
 
50
55
  raise "Unknown format" if configuration.message_format != SIDEKIQ5_FORMAT
51
- @message_format = SIDEKIQ5_FORMAT
56
+ @processor = SidekiqProcessor.new(Redis.new(url: configuration.redis_url))
52
57
 
53
58
  @gracefully_shutting_down = false
54
59
  prepare_traps
55
60
  end
56
61
 
57
62
  def init
58
- @redis.sadd("queues", split_keys)
59
63
  logger.info("Initiated RubyEventStore::Outbox v#{VERSION}")
60
64
  logger.info("Handling split keys: #{split_keys ? split_keys.join(", ") : "(all of them)"}")
61
65
  end
@@ -72,51 +76,129 @@ module RubyEventStore
72
76
  end
73
77
 
74
78
  def one_loop
75
- Record.transaction do
76
- records_scope = Record.lock.where(format: message_format, enqueued_at: nil)
77
- records_scope = records_scope.where(split_key: split_keys) if !split_keys.nil?
78
- records = records_scope.order("id ASC").limit(batch_size).to_a
79
- if records.empty?
80
- metrics.write_point_queue(status: "ok")
81
- return false
79
+ remaining_split_keys = @split_keys.dup
80
+
81
+ was_something_changed = false
82
+ while (split_key = remaining_split_keys.shift)
83
+ was_something_changed |= handle_split(FetchSpecification.new(processor.message_format, split_key))
84
+ end
85
+ was_something_changed
86
+ end
87
+
88
+ def handle_split(fetch_specification)
89
+ obtained_lock = obtain_lock_for_process(fetch_specification)
90
+ return false unless obtained_lock
91
+
92
+ something_processed = false
93
+
94
+ MAXIMUM_BATCH_FETCHES_IN_ONE_LOCK.times do
95
+ batch = retrieve_batch(fetch_specification)
96
+ if batch.empty?
97
+ break
82
98
  end
83
99
 
84
- now = @clock.now.utc
85
100
  failed_record_ids = []
86
- records.each do |record|
101
+ updated_record_ids = []
102
+ batch.each do |record|
87
103
  begin
88
- handle_one_record(now, record)
104
+ now = @clock.now.utc
105
+ processor.process(record, now)
106
+
107
+ record.update_column(:enqueued_at, now)
108
+ something_processed |= true
109
+ updated_record_ids << record.id
89
110
  rescue => e
90
111
  failed_record_ids << record.id
91
112
  e.full_message.split($/).each {|line| logger.error(line) }
92
113
  end
93
114
  end
94
115
 
95
- updated_record_ids = records.map(&:id) - failed_record_ids
96
- Record.where(id: updated_record_ids).update_all(enqueued_at: now)
97
- metrics.write_point_queue(status: "ok", enqueued: updated_record_ids.size, failed: failed_record_ids.size)
116
+ metrics.write_point_queue(
117
+ enqueued: updated_record_ids.size,
118
+ failed: failed_record_ids.size,
119
+ format: fetch_specification.message_format,
120
+ split_key: fetch_specification.split_key,
121
+ remaining: get_remaining_count(fetch_specification)
122
+ )
123
+
124
+ logger.info "Sent #{updated_record_ids.size} messages from outbox table"
98
125
 
99
- logger.info "Sent #{records.size} messages from outbox table"
100
- true
126
+ obtained_lock = refresh_lock_for_process(obtained_lock)
127
+ break unless obtained_lock
101
128
  end
102
- rescue ActiveRecord::Deadlocked
103
- logger.warn "Outbox fetch deadlocked"
104
- metrics.write_point_queue(status: "deadlocked")
105
- false
106
- rescue ActiveRecord::LockWaitTimeout
107
- logger.warn "Outbox fetch lock timeout"
108
- metrics.write_point_queue(status: "lock_timeout")
109
- false
129
+
130
+ metrics.write_point_queue(
131
+ format: fetch_specification.message_format,
132
+ split_key: fetch_specification.split_key,
133
+ remaining: get_remaining_count(fetch_specification)
134
+ ) unless something_processed
135
+
136
+ release_lock_for_process(fetch_specification)
137
+
138
+ processor.after_batch
139
+
140
+ something_processed
110
141
  end
111
142
 
112
143
  private
113
- attr_reader :split_keys, :logger, :message_format, :batch_size, :metrics
144
+ attr_reader :split_keys, :logger, :batch_size, :metrics, :processor, :consumer_uuid
145
+
146
+ def obtain_lock_for_process(fetch_specification)
147
+ result = Lock.obtain(fetch_specification, consumer_uuid, clock: @clock)
148
+ case result
149
+ when :deadlocked
150
+ logger.warn "Obtaining lock for split_key '#{fetch_specification.split_key}' failed (deadlock)"
151
+ metrics.write_operation_result("obtain", "deadlocked")
152
+ return false
153
+ when :lock_timeout
154
+ logger.warn "Obtaining lock for split_key '#{fetch_specification.split_key}' failed (lock timeout)"
155
+ metrics.write_operation_result("obtain", "lock_timeout")
156
+ return false
157
+ when :taken
158
+ logger.debug "Obtaining lock for split_key '#{fetch_specification.split_key}' unsuccessful (taken)"
159
+ metrics.write_operation_result("obtain", "taken")
160
+ return false
161
+ else
162
+ return result
163
+ end
164
+ end
165
+
166
+ def release_lock_for_process(fetch_specification)
167
+ result = Lock.release(fetch_specification, consumer_uuid)
168
+ case result
169
+ when :ok
170
+ when :deadlocked
171
+ logger.warn "Releasing lock for split_key '#{fetch_specification.split_key}' failed (deadlock)"
172
+ metrics.write_operation_result("release", "deadlocked")
173
+ when :lock_timeout
174
+ logger.warn "Releasing lock for split_key '#{fetch_specification.split_key}' failed (lock timeout)"
175
+ metrics.write_operation_result("release", "lock_timeout")
176
+ when :not_taken_by_this_process
177
+ logger.debug "Releasing lock for split_key '#{fetch_specification.split_key}' failed (not taken by this process)"
178
+ metrics.write_operation_result("release", "not_taken_by_this_process")
179
+ else
180
+ raise "Unexpected result #{result}"
181
+ end
182
+ end
114
183
 
115
- def handle_one_record(now, record)
116
- hash_payload = JSON.parse(record.payload)
117
- @redis.lpush("queue:#{hash_payload.fetch("queue")}", JSON.generate(JSON.parse(record.payload).merge({
118
- "enqueued_at" => now.to_f,
119
- })))
184
+ def refresh_lock_for_process(lock)
185
+ result = lock.refresh(clock: @clock)
186
+ case result
187
+ when :deadlocked
188
+ logger.warn "Refreshing lock for split_key '#{lock.split_key}' failed (deadlock)"
189
+ metrics.write_operation_result("refresh", "deadlocked")
190
+ return false
191
+ when :lock_timeout
192
+ logger.warn "Refreshing lock for split_key '#{lock.split_key}' failed (lock timeout)"
193
+ metrics.write_operation_result("refresh", "lock_timeout")
194
+ return false
195
+ when :stolen
196
+ logger.debug "Refreshing lock for split_key '#{lock.split_key}' unsuccessful (stolen)"
197
+ metrics.write_operation_result("refresh", "stolen")
198
+ return false
199
+ else
200
+ return result
201
+ end
120
202
  end
121
203
 
122
204
  def prepare_traps
@@ -131,6 +213,14 @@ module RubyEventStore
131
213
  def initiate_graceful_shutdown
132
214
  @gracefully_shutting_down = true
133
215
  end
216
+
217
+ def retrieve_batch(fetch_specification)
218
+ Record.remaining_for(fetch_specification).order("id ASC").limit(batch_size).to_a
219
+ end
220
+
221
+ def get_remaining_count(fetch_specification)
222
+ Record.remaining_for(fetch_specification).count
223
+ end
134
224
  end
135
225
  end
136
226
  end
@@ -0,0 +1,6 @@
1
+ module RubyEventStore
2
+ module Outbox
3
+ class ConsumerProcess
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module RubyEventStore
2
+ module Outbox
3
+ class FetchSpecification
4
+ def initialize(message_format, split_key)
5
+ @message_format = message_format
6
+ @split_key = split_key
7
+ freeze
8
+ end
9
+
10
+ attr_reader :message_format, :split_key
11
+ end
12
+ end
13
+ end
@@ -1,13 +1,12 @@
1
- require "ruby_event_store/outbox/metrics/null"
2
- require "ruby_event_store/outbox/metrics/influx"
3
-
4
1
  module RubyEventStore
5
2
  module Outbox
6
3
  module Metrics
7
4
  def self.from_url(metrics_url)
8
5
  if metrics_url.nil?
6
+ require "ruby_event_store/outbox/metrics/null"
9
7
  Null.new
10
8
  else
9
+ require "ruby_event_store/outbox/metrics/influx"
11
10
  Influx.new(metrics_url)
12
11
  end
13
12
  end
@@ -12,26 +12,40 @@ module RubyEventStore
12
12
  async: true,
13
13
  time_precision: 'ns',
14
14
  }
15
- options[:username] = params["username"]&.first if params["username"].present?
16
- options[:password] = params["password"]&.first if params["password"].present?
15
+ options[:username] = params.fetch("username").first if params.key?("username")
16
+ options[:password] = params.fetch("password").first if params.key?("password")
17
17
  @influxdb_client = InfluxDB::Client.new(**options)
18
18
  end
19
19
 
20
- def write_point_queue(status:, enqueued: 0, failed: 0)
20
+ def write_operation_result(operation, result)
21
+ write_point("ruby_event_store.outbox.lock", {
22
+ values: {
23
+ value: 1,
24
+ },
25
+ tags: {
26
+ operation: operation,
27
+ result: result,
28
+ }
29
+ })
30
+ end
31
+
32
+ def write_point_queue(enqueued: 0, failed: 0, remaining: 0, format: nil, split_key: nil)
21
33
  write_point("ruby_event_store.outbox.queue", {
22
34
  values: {
23
35
  enqueued: enqueued,
24
36
  failed: failed,
37
+ remaining: remaining,
25
38
  },
26
39
  tags: {
27
- status: status,
40
+ format: format,
41
+ split_key: split_key,
28
42
  }
29
43
  })
30
44
  end
31
45
 
32
46
  def write_point(series, data)
33
- data[:timestamp] ||= Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond).to_i
34
- @influxdb_client.write_point(series, data)
47
+ data[:timestamp] = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
48
+ influxdb_client.write_point(series, data)
35
49
  end
36
50
 
37
51
  attr_reader :influxdb_client
@@ -2,7 +2,10 @@ module RubyEventStore
2
2
  module Outbox
3
3
  module Metrics
4
4
  class Null
5
- def write_point_queue(status:, enqueued: 0, failed: 0)
5
+ def write_operation_result(operation, result)
6
+ end
7
+
8
+ def write_point_queue(**kwargs)
6
9
  end
7
10
  end
8
11
  end
@@ -8,9 +8,97 @@ module RubyEventStore
8
8
  self.primary_key = :id
9
9
  self.table_name = 'event_store_outbox'
10
10
 
11
+ def self.remaining_for(fetch_specification)
12
+ where(format: fetch_specification.message_format, split_key: fetch_specification.split_key, enqueued_at: nil)
13
+ end
14
+
11
15
  def hash_payload
12
16
  JSON.parse(payload).deep_symbolize_keys
13
17
  end
18
+
19
+ def enqueued?
20
+ !enqueued_at.nil?
21
+ end
22
+ end
23
+
24
+ class Lock < ::ActiveRecord::Base
25
+ self.primary_key = :split_key
26
+ self.table_name = 'event_store_outbox_locks'
27
+
28
+ def self.obtain(fetch_specification, process_uuid, clock:)
29
+ l = nil
30
+ transaction do
31
+ l = get_lock_record(fetch_specification)
32
+
33
+ return :taken if l.recently_locked?
34
+
35
+ l.update!(
36
+ locked_by: process_uuid,
37
+ locked_at: clock.now,
38
+ )
39
+ end
40
+ l
41
+ rescue ActiveRecord::Deadlocked
42
+ :deadlocked
43
+ rescue ActiveRecord::LockWaitTimeout
44
+ :lock_timeout
45
+ end
46
+
47
+ def refresh(clock:)
48
+ transaction do
49
+ current_process_uuid = locked_by
50
+ lock!
51
+ if locked_by == current_process_uuid
52
+ update!(locked_at: clock.now)
53
+ return self
54
+ else
55
+ return :stolen
56
+ end
57
+ end
58
+ rescue ActiveRecord::Deadlocked
59
+ :deadlocked
60
+ rescue ActiveRecord::LockWaitTimeout
61
+ :lock_timeout
62
+ end
63
+
64
+ def self.release(fetch_specification, process_uuid)
65
+ transaction do
66
+ l = get_lock_record(fetch_specification)
67
+ return :not_taken_by_this_process if !l.locked_by?(process_uuid)
68
+
69
+ l.update!(locked_by: nil, locked_at: nil)
70
+ end
71
+ :ok
72
+ rescue ActiveRecord::Deadlocked
73
+ :deadlocked
74
+ rescue ActiveRecord::LockWaitTimeout
75
+ :lock_timeout
76
+ end
77
+
78
+ def locked_by?(process_uuid)
79
+ locked_by.eql?(process_uuid)
80
+ end
81
+
82
+ def recently_locked?
83
+ locked_by && locked_at > 10.minutes.ago
84
+ end
85
+
86
+ private
87
+ def self.lock_for_split_key(fetch_specification)
88
+ lock.find_by(format: fetch_specification.message_format, split_key: fetch_specification.split_key)
89
+ end
90
+
91
+ def self.get_lock_record(fetch_specification)
92
+ l = lock_for_split_key(fetch_specification)
93
+ if l.nil?
94
+ begin
95
+ l = create!(format: fetch_specification.message_format, split_key: fetch_specification.split_key)
96
+ rescue ActiveRecord::RecordNotUnique
97
+ l = lock_for_split_key(fetch_specification)
98
+ end
99
+ end
100
+ l
101
+ end
14
102
  end
15
103
  end
16
104
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_event_store/outbox/sidekiq5_format"
4
+
5
+ module RubyEventStore
6
+ module Outbox
7
+ class SidekiqProcessor
8
+ InvalidPayload = Class.new(StandardError)
9
+
10
+ def initialize(redis)
11
+ @redis = redis
12
+ @recently_used_queues = Set.new
13
+ end
14
+
15
+ def process(record, now)
16
+ parsed_record = JSON.parse(record.payload)
17
+
18
+ queue = parsed_record["queue"]
19
+ raise InvalidPayload.new("Missing queue") if queue.nil? || queue.empty?
20
+ payload = JSON.generate(parsed_record.merge({
21
+ "enqueued_at" => now.to_f,
22
+ }))
23
+
24
+ redis.lpush("queue:#{queue}", payload)
25
+
26
+ @recently_used_queues << queue
27
+ end
28
+
29
+ def after_batch
30
+ if !@recently_used_queues.empty?
31
+ redis.sadd("queues", @recently_used_queues.to_a)
32
+ @recently_used_queues.clear
33
+ end
34
+ end
35
+
36
+ def message_format
37
+ SIDEKIQ5_FORMAT
38
+ end
39
+
40
+ private
41
+ attr_reader :redis
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq'
4
+ require "ruby_event_store/outbox/sidekiq5_format"
5
+
6
+ module RubyEventStore
7
+ module Outbox
8
+ class SidekiqProducer
9
+ def call(klass, args)
10
+ sidekiq_client = Sidekiq::Client.new(Sidekiq.redis_pool)
11
+ item = {
12
+ 'class' => klass,
13
+ 'args' => args,
14
+ }
15
+ normalized_item = sidekiq_client.__send__(:normalize_item, item)
16
+ payload = sidekiq_client.__send__(:process_single, normalized_item.fetch('class'), normalized_item)
17
+ if payload
18
+ Record.create!(
19
+ format: SIDEKIQ5_FORMAT,
20
+ split_key: payload.fetch('queue'),
21
+ payload: payload.to_json
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,31 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sidekiq'
4
- require "ruby_event_store/outbox/sidekiq5_format"
3
+ require "ruby_event_store/outbox/sidekiq_producer"
5
4
 
6
5
  module RubyEventStore
7
6
  module Outbox
8
7
  class SidekiqScheduler
8
+ def initialize
9
+ @sidekiq_producer = SidekiqProducer.new
10
+ end
11
+
9
12
  def call(klass, serialized_event)
10
- sidekiq_client = Sidekiq::Client.new(Sidekiq.redis_pool)
11
- item = {
12
- 'class' => klass,
13
- 'args' => [serialized_event.to_h],
14
- }
15
- normalized_item = sidekiq_client.__send__(:normalize_item, item)
16
- payload = sidekiq_client.__send__(:process_single, normalized_item.fetch('class'), normalized_item)
17
- if payload
18
- Record.create!(
19
- format: SIDEKIQ5_FORMAT,
20
- split_key: payload.fetch('queue'),
21
- payload: payload.to_json
22
- )
23
- end
13
+ sidekiq_producer.call(klass, [serialized_event.to_h])
24
14
  end
25
15
 
26
16
  def verify(subscriber)
27
17
  Class === subscriber && subscriber.respond_to?(:through_outbox?) && subscriber.through_outbox?
28
18
  end
19
+
20
+ private
21
+ attr_reader :sidekiq_producer
29
22
  end
30
23
  end
31
24
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module Outbox
5
- VERSION = "0.0.7"
5
+ VERSION = "0.0.12"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_event_store-outbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkency
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-25 00:00:00.000000000 Z
11
+ date: 2020-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_event_store
@@ -53,12 +53,16 @@ files:
53
53
  - lib/ruby_event_store/outbox.rb
54
54
  - lib/ruby_event_store/outbox/cli.rb
55
55
  - lib/ruby_event_store/outbox/consumer.rb
56
+ - lib/ruby_event_store/outbox/consumer_process.rb
57
+ - lib/ruby_event_store/outbox/fetch_specification.rb
56
58
  - lib/ruby_event_store/outbox/metrics.rb
57
59
  - lib/ruby_event_store/outbox/metrics/influx.rb
58
60
  - lib/ruby_event_store/outbox/metrics/null.rb
59
61
  - lib/ruby_event_store/outbox/record.rb
60
62
  - lib/ruby_event_store/outbox/sidekiq5_format.rb
61
63
  - lib/ruby_event_store/outbox/sidekiq_message_handler.rb
64
+ - lib/ruby_event_store/outbox/sidekiq_processor.rb
65
+ - lib/ruby_event_store/outbox/sidekiq_producer.rb
62
66
  - lib/ruby_event_store/outbox/sidekiq_scheduler.rb
63
67
  - lib/ruby_event_store/outbox/version.rb
64
68
  homepage: https://railseventstore.org