outboxable 0.1.8 → 1.0.2
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/Gemfile.lock +1 -1
- data/lib/generators/outboxable/install_generator.rb +15 -2
- data/lib/outboxable/configuration.rb +7 -7
- data/lib/outboxable/polling_publisher_worker.rb +15 -2
- data/lib/outboxable/version.rb +1 -1
- data/lib/outboxable/worker.rb +3 -2
- data/lib/outboxable.rb +1 -1
- data/lib/templates/{outbox.rb → activerecrod_outbox.rb} +1 -1
- data/lib/templates/mongoid_initializer.rb +36 -0
- data/lib/templates/mongoid_outbox.rb +71 -0
- metadata +5 -3
- /data/lib/templates/{initializer.rb → activerecord_initializer.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ff24c145223dcc558818028a2866583266372e1a52cbb0dc63661fdad5f94ea
|
4
|
+
data.tar.gz: 64e19fa685b3f77a85c10bc60f9754a32bbe8534b9aaa5cb153253aefb5bbcb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9dca7a575abbc63e9b033e3726229ce8abe6254ea049f220f66c9b675c4395efbfdac1a12da0dc5c31f50a65bfdab939ee23809c24d9e2182864fdd0b703754f
|
7
|
+
data.tar.gz: 6673eabf79e2daa9b36bd93c84589dc68f796a22c8b82cf93d035cd9eaaa1b3ba523ee16c9c121c719befe9b1527b3bf32a6c2467ee023b9bfd527b209575ef3
|
data/Gemfile.lock
CHANGED
@@ -3,24 +3,37 @@ module Outboxable
|
|
3
3
|
include Rails::Generators::Migration
|
4
4
|
|
5
5
|
source_root File.expand_path('../../templates', __dir__)
|
6
|
+
class_option :orm, type: :string, default: 'activerecord'
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
super(*)
|
10
|
+
|
11
|
+
@orm = options[:orm] || 'activerecord'
|
12
|
+
%w[activerecord mongoid].include?(@orm) || raise(ArgumentError, 'Invalid ORM. Only ActiveRecord and Mongoid are supported.')
|
13
|
+
end
|
6
14
|
|
7
15
|
# Copy initializer into user app
|
8
16
|
def copy_initializer
|
9
|
-
copy_file('
|
17
|
+
copy_file('activerecod_initializer.rb', 'config/initializers/z_outboxable.rb') if @orm == 'activerecord'
|
18
|
+
copy_file('mongoid_initializer.rb', 'config/initializers/z_outboxable.rb') if @orm == 'mongoid'
|
10
19
|
end
|
11
20
|
|
12
21
|
# Copy user information (model & Migrations) into user app
|
13
22
|
def create_user_model
|
14
23
|
target_path = 'app/models/outbox.rb'
|
24
|
+
|
15
25
|
if Rails.root.join(target_path).exist?
|
16
26
|
say_status('skipped', 'Model outbox already exists')
|
17
27
|
else
|
18
|
-
template('
|
28
|
+
template('activerecrod_outbox.rb', target_path) if @orm == 'activerecord'
|
29
|
+
template('mongoid_outbox.rb', target_path) if @orm == 'mongoid'
|
19
30
|
end
|
20
31
|
end
|
21
32
|
|
22
33
|
# Copy migrations
|
23
34
|
def copy_migrations
|
35
|
+
return if @orm == 'mongoid'
|
36
|
+
|
24
37
|
if self.class.migration_exists?('db/migrate', 'create_outboxable_outboxes')
|
25
38
|
say_status('skipped', 'Migration create_outboxable_outboxes already exists')
|
26
39
|
else
|
@@ -6,11 +6,17 @@ module Outboxable
|
|
6
6
|
def self.configure
|
7
7
|
self.configuration ||= Configuration.new
|
8
8
|
yield(configuration)
|
9
|
+
|
10
|
+
# In accordance to sidekiq-cron README: https://github.com/sidekiq-cron/sidekiq-cron#under-the-hood
|
11
|
+
Sidekiq::Options[:cron_poll_interval] = 5
|
12
|
+
|
13
|
+
# Create the cron job for the polling publisher
|
14
|
+
Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker', args: [{ orm: configuration.orm }])
|
9
15
|
end
|
10
16
|
|
11
17
|
class Configuration
|
12
18
|
ALLOWED_MESSAGE_BROKERS = %i[rabbitmq].freeze
|
13
|
-
ALLOWED_ORMS = %i[activerecord].freeze
|
19
|
+
ALLOWED_ORMS = %i[activerecord mongoid].freeze
|
14
20
|
|
15
21
|
attr_accessor :rabbitmq_host,
|
16
22
|
:rabbitmq_port,
|
@@ -26,12 +32,6 @@ module Outboxable
|
|
26
32
|
raise Error, 'Outboxable Gem only supports Rails but you application does not seem to be a Rails app' unless Object.const_defined?('Rails')
|
27
33
|
raise Error, 'Outboxable Gem only support Rails version 7 and newer' if Rails::VERSION::MAJOR < 7
|
28
34
|
raise Error, 'Outboxable Gem uses the sidekiq-cron Gem. Make sure you add it to your project' unless Object.const_defined?('Sidekiq::Cron')
|
29
|
-
|
30
|
-
# In accordance to sidekiq-cron README: https://github.com/sidekiq-cron/sidekiq-cron#under-the-hood
|
31
|
-
Sidekiq::Options[:cron_poll_interval] = 5
|
32
|
-
|
33
|
-
# Create the cron job for the polling publisher
|
34
|
-
Sidekiq::Cron::Job.create(name: 'OutboxablePollingPublisher', cron: '*/5 * * * * *', class: 'Outboxable::PollingPublisherWorker')
|
35
35
|
end
|
36
36
|
|
37
37
|
def message_broker=(message_broker)
|
@@ -3,14 +3,27 @@ module Outboxable
|
|
3
3
|
include Sidekiq::Job
|
4
4
|
sidekiq_options queue: 'critical'
|
5
5
|
|
6
|
-
def perform
|
6
|
+
def perform(args)
|
7
|
+
orm = args['orm']
|
8
|
+
orm == 'mongoid' ? perform_mongoid(orm) : perform_activerecord(orm)
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform_activerecord(orm)
|
7
12
|
Outbox.pending.where(last_attempted_at: [..Time.zone.now, nil]).find_in_batches(batch_size: 100).each do |batch|
|
8
13
|
batch.each do |outbox|
|
9
14
|
# This is to prevent a job from being retried too many times. Worst-case scenario is 1 minute delay in jobs.
|
10
|
-
Outboxable::Worker.perform_async(outbox.id)
|
15
|
+
Outboxable::Worker.perform_async(outbox.id, orm)
|
11
16
|
outbox.update(last_attempted_at: 1.minute.from_now, status: :processing, allow_publish: false)
|
12
17
|
end
|
13
18
|
end
|
14
19
|
end
|
20
|
+
|
21
|
+
def perform_mongoid(orm)
|
22
|
+
Outbox.pending.where(last_attempted_at: [..Time.zone.now, nil]).each do |outbox|
|
23
|
+
# This is to prevent a job from being retried too many times. Worst-case scenario is 1 minute delay in jobs.
|
24
|
+
Outboxable::Worker.perform_async(outbox.idempotency_key, orm)
|
25
|
+
outbox.update(last_attempted_at: 1.minute.from_now, status: :processing, allow_publish: false)
|
26
|
+
end
|
27
|
+
end
|
15
28
|
end
|
16
29
|
end
|
data/lib/outboxable/version.rb
CHANGED
data/lib/outboxable/worker.rb
CHANGED
@@ -4,8 +4,9 @@ module Outboxable
|
|
4
4
|
class Worker
|
5
5
|
include ::Sidekiq::Job
|
6
6
|
|
7
|
-
def perform(outbox_id)
|
8
|
-
Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id))
|
7
|
+
def perform(outbox_id, orm)
|
8
|
+
Outboxable::PublishingManager.publish(resource: Outbox.find(outbox_id)) if orm == 'activerecord'
|
9
|
+
Outboxable::PublishingManager.publish(resource: Outbox.find_by!(idempotency_key: outbox_id)) if orm == 'mongoid'
|
9
10
|
end
|
10
11
|
end
|
11
12
|
end
|
data/lib/outboxable.rb
CHANGED
@@ -20,7 +20,7 @@ module Outboxable
|
|
20
20
|
after_create :instantiate_outbox_for_create, if: proc { |object| object.check_outbox_condition(object:, operation: :create) }
|
21
21
|
after_update :instantiate_outbox_for_update, if: proc { |object| object.check_outbox_condition(object:, operation: :update) }
|
22
22
|
|
23
|
-
has_many :outboxes, as: :outboxable,
|
23
|
+
has_many :outboxes, as: :outboxable, dependent: :destroy
|
24
24
|
|
25
25
|
def instantiate_outbox(routing_key:)
|
26
26
|
outboxes.new(
|
@@ -4,7 +4,7 @@ class Outbox < ApplicationRecord
|
|
4
4
|
before_save :check_publishing
|
5
5
|
# Callbacks
|
6
6
|
before_create :set_last_attempted_at
|
7
|
-
|
7
|
+
after_save :publish, if: :allow_publish
|
8
8
|
# Enums
|
9
9
|
enum status: { pending: 0, processing: 1, published: 2, failed: 3 }
|
10
10
|
enum size: { single: 0, batch: 1 }
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This monkey patch allows you to customize the message format that you publish to your broker.
|
2
|
+
# By default, Outboxable publishes a CloudEvent message to your broker.
|
3
|
+
module Outboxable
|
4
|
+
module RabbitMq
|
5
|
+
class Publisher
|
6
|
+
# Override this method to customize the message format that you publish to your broker
|
7
|
+
# DO NOT CHANGE THE METHOD SIGNATURE
|
8
|
+
def to_envelope(resource:)
|
9
|
+
{
|
10
|
+
id: resource.id,
|
11
|
+
source: 'http://localhost:3000',
|
12
|
+
specversion: '1.0',
|
13
|
+
type: resource.routing_key,
|
14
|
+
datacontenttype: 'application/json',
|
15
|
+
data: resource.payload
|
16
|
+
}.to_json
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Outboxable.configure do |config|
|
23
|
+
# Specify the ORM you are using. For now, only ActiveRecord is supported.
|
24
|
+
config.orm = :mongoid
|
25
|
+
|
26
|
+
# Specify the message broker you are using. For now, only RabbitMQ is supported.
|
27
|
+
config.message_broker = :rabbitmq
|
28
|
+
|
29
|
+
# RabbitMQ configurations
|
30
|
+
config.rabbitmq_host = ENV.fetch('RABBITMQ__HOST')
|
31
|
+
config.rabbitmq_port = ENV.fetch('RABBITMQ__PORT', 5672)
|
32
|
+
config.rabbitmq_user = ENV.fetch('RABBITMQ__USERNAME')
|
33
|
+
config.rabbitmq_password = ENV.fetch('RABBITMQ__PASSWORD')
|
34
|
+
config.rabbitmq_vhost = ENV.fetch('RABBITMQ__VHOST')
|
35
|
+
config.rabbitmq_event_bus_exchange = ENV.fetch('EVENTBUS__EXCHANGE_NAME')
|
36
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Outbox
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
|
5
|
+
attr_writer :allow_publish
|
6
|
+
|
7
|
+
# Fields
|
8
|
+
field :status, type: String, default: 'pending'
|
9
|
+
field :size, type: String, default: 'single'
|
10
|
+
|
11
|
+
field :exchange, type: String, default: ''
|
12
|
+
field :routing_key, type: String, default: ''
|
13
|
+
|
14
|
+
field :attempts, type: Integer, default: 0
|
15
|
+
|
16
|
+
field :last_attempted_at, type: DateTime, default: nil
|
17
|
+
|
18
|
+
field :retry_at, type: DateTime, default: nil
|
19
|
+
|
20
|
+
field :idempotency_key, type: String
|
21
|
+
|
22
|
+
field :payload, type: Hash, default: {}
|
23
|
+
field :headers, type: Hash, default: {}
|
24
|
+
|
25
|
+
index({ idempotency_key: 1 }, { unique: true, name: 'idempotency_key_unique_index' })
|
26
|
+
|
27
|
+
before_save :check_publishing
|
28
|
+
before_create :set_idempotency_key
|
29
|
+
|
30
|
+
# Callbacks
|
31
|
+
before_create :set_last_attempted_at
|
32
|
+
after_save :publish, if: :allow_publish
|
33
|
+
|
34
|
+
# Validations
|
35
|
+
validates :payload, :exchange, :routing_key, presence: true
|
36
|
+
|
37
|
+
# Associations
|
38
|
+
belongs_to :outboxable, polymorphic: true, optional: true
|
39
|
+
|
40
|
+
def set_last_attempted_at
|
41
|
+
self.last_attempted_at = 10.seconds.from_now
|
42
|
+
end
|
43
|
+
|
44
|
+
def publish
|
45
|
+
Outboxable::Worker.perform_async(idempotency_key)
|
46
|
+
update(status: :processing, last_attempted_at: 1.minute.from_now, allow_publish: false)
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_idempotency_key
|
50
|
+
self.idempotency_key = SecureRandom.uuid if idempotency_key.blank?
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_publishing
|
54
|
+
self.allow_publish = false if published?
|
55
|
+
end
|
56
|
+
|
57
|
+
def allow_publish
|
58
|
+
return true if @allow_publish.nil?
|
59
|
+
|
60
|
+
@allow_publish
|
61
|
+
end
|
62
|
+
|
63
|
+
%w[pending processing published failed].each do |status|
|
64
|
+
define_method "#{status}?" do
|
65
|
+
self.status == status
|
66
|
+
end
|
67
|
+
|
68
|
+
# define scope
|
69
|
+
scope status, -> { where(status:) }
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: outboxable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brusk Awat
|
@@ -64,9 +64,11 @@ files:
|
|
64
64
|
- lib/outboxable/rabbitmq/publisher.rb
|
65
65
|
- lib/outboxable/version.rb
|
66
66
|
- lib/outboxable/worker.rb
|
67
|
+
- lib/templates/activerecord_initializer.rb
|
68
|
+
- lib/templates/activerecrod_outbox.rb
|
67
69
|
- lib/templates/create_outboxable_outboxes.rb
|
68
|
-
- lib/templates/
|
69
|
-
- lib/templates/
|
70
|
+
- lib/templates/mongoid_initializer.rb
|
71
|
+
- lib/templates/mongoid_outbox.rb
|
70
72
|
- sig/outboxable.rbs
|
71
73
|
homepage: https://github.com/broosk1993/outboxable
|
72
74
|
licenses:
|
File without changes
|