rails-transactional-outbox 0.2.1 → 0.3.1
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/CHANGELOG.md +9 -1
- data/Gemfile.lock +16 -14
- data/README.md +11 -13
- data/lib/rails_transactional_outbox/configuration.rb +10 -1
- data/lib/rails_transactional_outbox/health_check.rb +15 -16
- data/lib/rails_transactional_outbox/record_processors/active_record_processor.rb +8 -1
- data/lib/rails_transactional_outbox/version.rb +1 -1
- data/lib/rails_transactional_outbox.rb +1 -0
- data/rails-transactional-outbox.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e55a23c67288d7804bffc02cf8bfa1f36fc93562600f2002cce9393ca097b95
|
4
|
+
data.tar.gz: 64693c24b6affcf0357973f439aa0907636537c74ffd247b93ddf77984c0f6fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ce32e65ec889efa9e3c56931c9a8146ffeff13178ab67aaf242b4dad72d22d33c9949bfde0d8609bbd610a1309d9fb26b5eef73be949b14ec9803444f181810
|
7
|
+
data.tar.gz: 1484e44ad3673815550375b95e78dfc08f235535dd47f519c7a26bf2ed38e781b16d9ff9e1acd69b8dfa029967e55f1308302d1989c36a03b97b37cb9fd077fd
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.1] - 2023-05-24
|
4
|
+
|
5
|
+
- add config option whether to raise error when outbox entry record is not found
|
6
|
+
|
7
|
+
## [0.3.0] - 2022-12-20
|
8
|
+
|
9
|
+
- Move to file-based healthchecks, instead of using Redis-based ones.
|
10
|
+
|
3
11
|
## [0.2.1] - 2022-09-08
|
4
12
|
|
5
13
|
- Simplify update to 0.2 (`causality_key` is not required if `outbox_entry_causality_key_resolver` is not used)
|
6
14
|
|
7
15
|
## [0.2.0] - 2022-09-08
|
8
16
|
|
9
|
-
- Introduce `RailsTransactionalOutbox::OutboxEntriesProcessors::OrderedByCausalityKeyProcessor`
|
17
|
+
- Introduce `RailsTransactionalOutbox::OutboxEntriesProcessors::OrderedByCausalityKeyProcessor`
|
10
18
|
|
11
19
|
## [0.1.0] - 2022-08-23
|
12
20
|
|
data/Gemfile.lock
CHANGED
@@ -9,12 +9,12 @@ GIT
|
|
9
9
|
PATH
|
10
10
|
remote: .
|
11
11
|
specs:
|
12
|
-
rails-transactional-outbox (0.
|
12
|
+
rails-transactional-outbox (0.3.1)
|
13
13
|
activerecord (>= 5)
|
14
14
|
activesupport (>= 3.2)
|
15
15
|
concurrent-ruby
|
16
16
|
dry-monitor
|
17
|
-
|
17
|
+
file-based-healthcheck
|
18
18
|
sigurd
|
19
19
|
zeitwerk
|
20
20
|
|
@@ -40,21 +40,23 @@ GEM
|
|
40
40
|
msgpack
|
41
41
|
debase-ruby_core_source (0.10.16)
|
42
42
|
diff-lcs (1.5.0)
|
43
|
-
dry-configurable (0.
|
43
|
+
dry-configurable (1.0.1)
|
44
|
+
dry-core (~> 1.0, < 2)
|
45
|
+
zeitwerk (~> 2.6)
|
46
|
+
dry-core (1.0.0)
|
44
47
|
concurrent-ruby (~> 1.0)
|
45
|
-
|
46
|
-
dry-
|
48
|
+
zeitwerk (~> 2.6)
|
49
|
+
dry-events (1.0.1)
|
47
50
|
concurrent-ruby (~> 1.0)
|
48
|
-
|
49
|
-
|
50
|
-
dry-
|
51
|
-
|
52
|
-
dry-
|
53
|
-
dry-core (~> 0.5, >= 0.5)
|
54
|
-
dry-events (~> 0.2)
|
55
|
-
zeitwerk (~> 2.5)
|
51
|
+
dry-core (~> 1.0, < 2)
|
52
|
+
dry-monitor (1.0.1)
|
53
|
+
dry-configurable (~> 1.0, < 2)
|
54
|
+
dry-core (~> 1.0, < 2)
|
55
|
+
dry-events (~> 1.0, < 2)
|
56
56
|
exponential-backoff (0.0.4)
|
57
57
|
ffi (1.15.5)
|
58
|
+
file-based-healthcheck (0.2.0)
|
59
|
+
activesupport (>= 3.2)
|
58
60
|
i18n (1.12.0)
|
59
61
|
concurrent-ruby (~> 1.0)
|
60
62
|
json (2.6.2)
|
@@ -119,7 +121,7 @@ GEM
|
|
119
121
|
tzinfo (2.0.5)
|
120
122
|
concurrent-ruby (~> 1.0)
|
121
123
|
unicode-display_width (2.2.0)
|
122
|
-
zeitwerk (2.6.
|
124
|
+
zeitwerk (2.6.8)
|
123
125
|
|
124
126
|
PLATFORMS
|
125
127
|
x86_64-darwin-18
|
data/README.md
CHANGED
@@ -29,21 +29,22 @@ Create the initializer with the following content:
|
|
29
29
|
``` rb
|
30
30
|
Rails.application.config.to_prepare do
|
31
31
|
RailsTransactionalOutbox.configure do |config|
|
32
|
-
config.database_connection_provider = ActiveRecord::Base # required
|
32
|
+
config.database_connection_provider = ActiveRecord::Base # required
|
33
33
|
config.transaction_provider = ActiveRecord::Base # required
|
34
34
|
config.logger = Rails.logger # required
|
35
35
|
config.outbox_model = OutboxEntry # required
|
36
|
-
config.error_handler = Sentry # non-required, but highly recommended, defaults to RailsTransactionalOutbox::ErrorHandlers::NullErrorHandler
|
37
|
-
|
36
|
+
config.error_handler = Sentry # non-required, but highly recommended, defaults to RailsTransactionalOutbox::ErrorHandlers::NullErrorHandler. When using Sentry, you will probably want to exclude SignalException `config.excluded_exceptions += ["SignalException"]`.
|
37
|
+
|
38
38
|
config.transactional_outbox_worker_sleep_seconds = 1 # optional, defaults to 0.5
|
39
|
-
config.transactional_outbox_worker_idle_delay_multiplier = 5 # optional, defaults to 1, if there are no outbox entries to be processed, then the sleep time for the thread will be equal to transactional_outbox_worker_idle_delay_multiplier * transactional_outbox_worker_sleep_seconds
|
39
|
+
config.transactional_outbox_worker_idle_delay_multiplier = 5 # optional, defaults to 1, if there are no outbox entries to be processed, then the sleep time for the thread will be equal to transactional_outbox_worker_idle_delay_multiplier * transactional_outbox_worker_sleep_seconds
|
40
40
|
config.outbox_batch_size = 100 # optional, defaults to 100
|
41
41
|
config.add_record_processor(MyCustomOperationProcerssor) # optional, by default it contains only one processor for ActiveRecord, but you could add more
|
42
|
-
|
42
|
+
config.raise_not_found_model_error = true # optional, defaults to true. Should the error be raised if outbox entry model is not found
|
43
|
+
|
43
44
|
config.lock_client = Redlock::Client.new([ENV["REDIS_URL"]]) # required if you want to use RailsTransactionalOutbox::OutboxEntriesProcessors::OrderedByCausalityKeyProcessor, defaults to RailsTransactionalOutbox::NullLockClient. Check its interface and the interface of `redlock` gem. To cut the long story short, when the lock is acquired, a hash with the structure outlined in RailsTransactionalOutbox::NullLockClient should be yielded, if the lock is not acquired, a nil should be yielded.
|
44
45
|
config.lock_expiry_time = 10_000 # not required, defaults to 10_000, the unit is milliseconds
|
45
46
|
config.outbox_entries_processor = `RailsTransactionalOutbox::OutboxEntriesProcessors::OrderedByCausalityKeyProcessor`.new # not required, defaults to RailsTransactionalOutbox::OutboxEntriesProcessors::NonOrderedProcessor.new
|
46
|
-
config.outbox_entry_causality_key_resolver = ->(model) { model.tenant_id } # not required, defaults to a lambda returning nil. Needed when using `outbox_entry_causality_key_resolver`
|
47
|
+
config.outbox_entry_causality_key_resolver = ->(model) { model.tenant_id } # not required, defaults to a lambda returning nil. Needed when using `outbox_entry_causality_key_resolver`
|
47
48
|
end
|
48
49
|
end
|
49
50
|
```
|
@@ -53,7 +54,7 @@ Create OutboxEntry model (or use a different name, just make sure to adjust conf
|
|
53
54
|
``` rb
|
54
55
|
class OutboxEntry < ApplicationRecord
|
55
56
|
include RailsTransactionalOutbox::OutboxModel
|
56
|
-
|
57
|
+
|
57
58
|
# optional, if you want to use encryption
|
58
59
|
crypt_keeper :changeset, :arguments, encryptor: :postgres_pgp, key: ENV.fetch("CRYPT_KEEPER_KEY"), encoding: "UTF-8"
|
59
60
|
outbox_encrypt_json_for :changeset, :arguments
|
@@ -93,7 +94,7 @@ end
|
|
93
94
|
|
94
95
|
Keep in mind that `arguments` and `changeset` are `text` columns here. If you don't want to use encryption, replace them with `jsonb` columns:
|
95
96
|
|
96
|
-
```rb
|
97
|
+
```rb
|
97
98
|
t.jsonb "arguments", null: false, default: {}
|
98
99
|
t.jsonb "changeset", null: false, default: {}
|
99
100
|
```
|
@@ -105,7 +106,7 @@ As the last step, include `RailsTransactionalOutbox::ReliableModel` module in th
|
|
105
106
|
``` ruby
|
106
107
|
class User < ActiveRecord::Base
|
107
108
|
include RailsTransactionalOutbox::ReliableModel
|
108
|
-
end
|
109
|
+
end
|
109
110
|
```
|
110
111
|
|
111
112
|
Now, you can just replace `after_commit` callbacks with `reliable_after_commit`. The interface is going to be the same as for `after_commit`:
|
@@ -247,9 +248,8 @@ end
|
|
247
248
|
|
248
249
|
### Health Checks
|
249
250
|
|
250
|
-
First, you need to set `REDIS_URL` ENV variable to provide the URL for Redis.
|
251
251
|
|
252
|
-
Then,
|
252
|
+
Then, Uou need to explicitly enable the health check (e.g. in the initializer):
|
253
253
|
|
254
254
|
``` rb
|
255
255
|
RailsTransactionalOutbox.enable_outbox_worker_healthcheck
|
@@ -261,8 +261,6 @@ To perform the actual health check, use `bin/rails_transactional_outbox_health_c
|
|
261
261
|
bundle exec rails_transactional_outbox_health_check
|
262
262
|
```
|
263
263
|
|
264
|
-
The logic is based on checking a special value in Redis that is set (and unset) for a given container when Outbox workers are initialized/stopped/processing messages.
|
265
|
-
|
266
264
|
It works for both readiness and liveness checks.
|
267
265
|
|
268
266
|
#### Events, hooks and monitors
|
@@ -5,7 +5,8 @@ class RailsTransactionalOutbox
|
|
5
5
|
attr_accessor :database_connection_provider, :logger, :outbox_model, :transaction_provider
|
6
6
|
attr_writer :error_handler, :transactional_outbox_worker_sleep_seconds,
|
7
7
|
:transactional_outbox_worker_idle_delay_multiplier, :outbox_batch_size, :outbox_entries_processor,
|
8
|
-
:lock_client, :lock_expiry_time, :outbox_entry_causality_key_resolver
|
8
|
+
:lock_client, :lock_expiry_time, :outbox_entry_causality_key_resolver,
|
9
|
+
:raise_not_found_model_error
|
9
10
|
|
10
11
|
def error_handler
|
11
12
|
@error_handler || RailsTransactionalOutbox::ErrorHandlers::NullErrorHandler
|
@@ -35,6 +36,14 @@ class RailsTransactionalOutbox
|
|
35
36
|
@outbox_entries_processor ||= RailsTransactionalOutbox::OutboxEntriesProcessors::NonOrderedProcessor.new
|
36
37
|
end
|
37
38
|
|
39
|
+
def raise_not_found_model_error
|
40
|
+
return @raise_not_found_model_error if defined?(@raise_not_found_model_error)
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :raise_not_found_model_error?, :raise_not_found_model_error
|
46
|
+
|
38
47
|
def lock_client
|
39
48
|
@lock_client || RailsTransactionalOutbox::NullLockClient
|
40
49
|
end
|
@@ -1,46 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "redis"
|
4
|
-
|
5
3
|
class RailsTransactionalOutbox
|
6
4
|
class HealthCheck
|
7
5
|
KEY_PREFIX = "__rails_transactional__outbox_worker__running__"
|
8
|
-
|
9
|
-
private_constant :KEY_PREFIX, :
|
6
|
+
TMP_DIR = "/tmp"
|
7
|
+
private_constant :KEY_PREFIX, :TMP_DIR
|
10
8
|
|
11
|
-
def self.check(
|
12
|
-
expiry_time_in_seconds:
|
13
|
-
new(redis_url: redis_url, hostname: hostname, expiry_time_in_seconds: expiry_time_in_seconds).check
|
9
|
+
def self.check(hostname: ENV.fetch("HOSTNAME", nil), expiry_time_in_seconds: 120)
|
10
|
+
new(hostname: hostname, expiry_time_in_seconds: expiry_time_in_seconds).check
|
14
11
|
end
|
15
12
|
|
16
|
-
attr_reader :
|
13
|
+
attr_reader :hostname, :expiry_time_in_seconds
|
17
14
|
|
18
|
-
def initialize(
|
19
|
-
expiry_time_in_seconds: 120)
|
20
|
-
@redis_client = Redis.new(url: redis_url)
|
15
|
+
def initialize(hostname: ENV.fetch("HOSTNAME", nil), expiry_time_in_seconds: 120)
|
21
16
|
@hostname = hostname
|
22
17
|
@expiry_time_in_seconds = expiry_time_in_seconds
|
23
18
|
end
|
24
19
|
|
25
20
|
def check
|
26
|
-
|
27
|
-
if value == VALUE
|
21
|
+
if healthcheck_storage.running?
|
28
22
|
""
|
29
23
|
else
|
30
|
-
"[Rails Transactional Outbox Worker
|
24
|
+
"[Rails Transactional Outbox Worker healthcheck failed]"
|
31
25
|
end
|
32
26
|
end
|
33
27
|
|
34
28
|
def register_heartbeat
|
35
|
-
|
29
|
+
healthcheck_storage.touch
|
36
30
|
end
|
37
31
|
|
38
32
|
def worker_stopped
|
39
|
-
|
33
|
+
healthcheck_storage.remove
|
40
34
|
end
|
41
35
|
|
42
36
|
private
|
43
37
|
|
38
|
+
def healthcheck_storage
|
39
|
+
@healthcheck_storage ||= FileBasedHealthcheck.new(directory: TMP_DIR, filename: key,
|
40
|
+
time_threshold: expiry_time_in_seconds)
|
41
|
+
end
|
42
|
+
|
44
43
|
def key
|
45
44
|
"#{KEY_PREFIX}#{hostname}"
|
46
45
|
end
|
@@ -15,7 +15,14 @@ class RailsTransactionalOutbox
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def call(record)
|
18
|
-
model = record.infer_model
|
18
|
+
model = record.infer_model
|
19
|
+
if model.nil?
|
20
|
+
if RailsTransactionalOutbox.configuration.raise_not_found_model_error?
|
21
|
+
raise CouldNotFindModelError.new(record)
|
22
|
+
end
|
23
|
+
|
24
|
+
return
|
25
|
+
end
|
19
26
|
model.previous_changes = record.transformed_changeset.with_indifferent_access
|
20
27
|
model.reliable_after_commit_callbacks.for_event_type(record.event_type).each do |callback|
|
21
28
|
callback.call(model)
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "activesupport", ">= 3.2"
|
35
35
|
spec.add_dependency "concurrent-ruby"
|
36
36
|
spec.add_dependency "dry-monitor"
|
37
|
-
spec.add_dependency "
|
37
|
+
spec.add_dependency "file-based-healthcheck"
|
38
38
|
spec.add_dependency "sigurd"
|
39
39
|
spec.add_dependency "zeitwerk"
|
40
40
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-transactional-outbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karol Galanciak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: file-based-healthcheck
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|