outboxer 0.1.11 → 1.0.0.pre.beta
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/README.md +81 -47
- data/db/migrate/create_outboxer_exceptions.rb +20 -0
- data/db/migrate/create_outboxer_frames.rb +16 -0
- data/db/migrate/create_outboxer_messages.rb +19 -0
- data/generators/message_publisher_generator.rb +11 -0
- data/generators/{outboxer/install_generator.rb → schema_generator.rb} +4 -9
- data/lib/outboxer/database.rb +44 -0
- data/lib/outboxer/logger.rb +17 -0
- data/lib/outboxer/message.rb +262 -13
- data/lib/outboxer/messages.rb +232 -0
- data/lib/outboxer/models/exception.rb +15 -0
- data/lib/outboxer/models/frame.rb +14 -0
- data/lib/outboxer/models/message.rb +46 -0
- data/lib/outboxer/models.rb +3 -0
- data/lib/outboxer/railtie.rb +2 -4
- data/lib/outboxer/version.rb +1 -1
- data/lib/outboxer/web/public/css/bootstrap-icons.css +2078 -0
- data/lib/outboxer/web/public/css/bootstrap-icons.min.css +5 -0
- data/lib/outboxer/web/public/css/bootstrap.css +12071 -0
- data/lib/outboxer/web/public/css/bootstrap.min.css +6 -0
- data/lib/outboxer/web/public/css/fonts/bootstrap-icons.woff +0 -0
- data/lib/outboxer/web/public/css/fonts/bootstrap-icons.woff2 +0 -0
- data/lib/outboxer/web/public/favicon.svg +3 -0
- data/lib/outboxer/web/public/js/bootstrap.bundle.js +6306 -0
- data/lib/outboxer/web/public/js/bootstrap.bundle.min.js +7 -0
- data/lib/outboxer/web/views/error.erb +63 -0
- data/lib/outboxer/web/views/home.erb +172 -0
- data/lib/outboxer/web/views/layout.erb +80 -0
- data/lib/outboxer/web/views/message.erb +81 -0
- data/lib/outboxer/web/views/messages/index.erb +60 -0
- data/lib/outboxer/web/views/messages/show.erb +31 -0
- data/lib/outboxer/web/views/messages.erb +262 -0
- data/lib/outboxer/web.rb +430 -0
- data/lib/outboxer.rb +9 -5
- metadata +279 -22
- data/.rspec +0 -3
- data/.rubocop.yml +0 -229
- data/CHANGELOG.md +0 -5
- data/CODE_OF_CONDUCT.md +0 -84
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -12
- data/generators/outboxer/templates/bin/publisher.rb +0 -11
- data/generators/outboxer/templates/migrations/create_outboxer_exceptions.rb +0 -15
- data/generators/outboxer/templates/migrations/create_outboxer_messages.rb +0 -13
- data/lib/outboxer/exception.rb +0 -9
- data/lib/outboxer/outboxable.rb +0 -21
- data/lib/outboxer/publisher.rb +0 -149
- data/lib/tasks/gem.rake +0 -58
- data/outboxer.gemspec +0 -33
- data/sig/outboxer.rbs +0 -19
data/Rakefile
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require_relative "../config/environment"
|
4
|
-
|
5
|
-
Outboxer::Publisher.publish do |message:, logger:|
|
6
|
-
logger.info("[#{message.id}] publishing")
|
7
|
-
|
8
|
-
HardJob.perform_async({ "message_id" => message.id })
|
9
|
-
|
10
|
-
logger.info("[#{message.id}] published")
|
11
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
class CreateOutboxerExceptions < ActiveRecord::Migration[6.1]
|
2
|
-
def change
|
3
|
-
create_table :outboxer_exceptions do |t|
|
4
|
-
t.references :message, null: false, foreign_key: { to_table: :outboxer_messages }
|
5
|
-
|
6
|
-
t.text :class_name, null: false
|
7
|
-
t.text :message_text, null: false
|
8
|
-
t.column :backtrace, :text, array: true
|
9
|
-
|
10
|
-
t.timestamps
|
11
|
-
end
|
12
|
-
|
13
|
-
remove_column :outboxer_exceptions, :updated_at
|
14
|
-
end
|
15
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
class CreateOutboxerMessages < ActiveRecord::Migration[6.1]
|
2
|
-
def change
|
3
|
-
create_table :outboxer_messages do |t|
|
4
|
-
t.references :message, polymorphic: true, null: false
|
5
|
-
t.text :status, null: false
|
6
|
-
|
7
|
-
t.timestamps
|
8
|
-
end
|
9
|
-
|
10
|
-
add_index :outboxer_messages, %i[status created_at]
|
11
|
-
add_index :outboxer_messages, %i[message_type message_id], unique: true
|
12
|
-
end
|
13
|
-
end
|
data/lib/outboxer/exception.rb
DELETED
data/lib/outboxer/outboxable.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
module Outboxer
|
2
|
-
module Outboxable
|
3
|
-
def self.included(base)
|
4
|
-
base.extend ClassMethods
|
5
|
-
|
6
|
-
base.class_eval do
|
7
|
-
has_one :message, class_name: "::Outboxer::Message", as: :outbox_message,
|
8
|
-
dependent: :destroy
|
9
|
-
|
10
|
-
after_create :create_outbox_message!
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def create_outbox_message!
|
15
|
-
Message.create!(message: self, status: Message::STATUS[:unpublished])
|
16
|
-
end
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/outboxer/publisher.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
require "logger"
|
2
|
-
|
3
|
-
module Outboxer
|
4
|
-
module Publisher
|
5
|
-
module_function
|
6
|
-
|
7
|
-
@publishing = false
|
8
|
-
@logger = Logger.new($stdout)
|
9
|
-
|
10
|
-
# Publishes messages from the Outboxer queue.
|
11
|
-
#
|
12
|
-
# This method will continue to publish messages from the queue as long
|
13
|
-
# as @publishing is set to true. It uses an ActiveRecord connection to
|
14
|
-
# handle database interactions.
|
15
|
-
#
|
16
|
-
# @param poll [Integer] the interval (in seconds) at which the method
|
17
|
-
# should poll the database for new messages.
|
18
|
-
# @param backoff [Proc] a callable object that defines how to increase the
|
19
|
-
# backoff time when an error occurs. It should accept the current backoff
|
20
|
-
# time as a parameter and return the new backoff time.
|
21
|
-
#
|
22
|
-
# @yield [Hash] once a message is dequeued, the method yields a hash to
|
23
|
-
# the given block. The hash contains the :message (the dequeued message)
|
24
|
-
# and :logger (the logger object).
|
25
|
-
#
|
26
|
-
# @yieldparam message [Message] the dequeued message from the Outboxer queue.
|
27
|
-
# @yieldparam logger [Logger] the logger instance to log any information or errors.
|
28
|
-
#
|
29
|
-
# @raise [StandardError] if an error occurs during the yield, it is rescued,
|
30
|
-
# logged, and then the failed method is invoked to handle the error. The
|
31
|
-
# method then moves on to the next message in the queue.
|
32
|
-
#
|
33
|
-
# @return [void] This method does not have a meaningful return value.
|
34
|
-
#
|
35
|
-
# @example Basic usage
|
36
|
-
# Outboxer::Publisher.publish(poll: 1) do |hash|
|
37
|
-
# puts hash[:message]
|
38
|
-
# puts hash[:logger]
|
39
|
-
# end
|
40
|
-
def publish(poll: 1, backoff: ->(current_backoff) { [current_backoff * 2, 5 * 60].min })
|
41
|
-
@publishing = true
|
42
|
-
|
43
|
-
ActiveRecord::Base.connection_pool.with_connection do
|
44
|
-
while @publishing
|
45
|
-
outboxer_message = dequeue(backoff: backoff)
|
46
|
-
|
47
|
-
if outboxer_message.nil?
|
48
|
-
sleep poll
|
49
|
-
|
50
|
-
next
|
51
|
-
end
|
52
|
-
|
53
|
-
begin
|
54
|
-
yield message: outboxer_message.message, logger: @logger
|
55
|
-
rescue StandardError => exception
|
56
|
-
failed(
|
57
|
-
outboxer_message: outboxer_message,
|
58
|
-
backoff: backoff,
|
59
|
-
exception: exception)
|
60
|
-
|
61
|
-
next
|
62
|
-
end
|
63
|
-
|
64
|
-
published(
|
65
|
-
outboxer_message: outboxer_message,
|
66
|
-
backoff: backoff)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def dequeue(backoff:)
|
72
|
-
retry_on_error(backoff: backoff) do
|
73
|
-
ActiveRecord::Base.transaction do
|
74
|
-
message = Message
|
75
|
-
.where(status: Message::STATUS[:unpublished])
|
76
|
-
.order(created_at: :asc)
|
77
|
-
.limit(1)
|
78
|
-
.lock("FOR UPDATE SKIP LOCKED")
|
79
|
-
.first
|
80
|
-
|
81
|
-
message&.update!(status: Message::STATUS[:publishing])
|
82
|
-
|
83
|
-
message
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def published(outboxer_message:, backoff:)
|
89
|
-
retry_on_error(backoff: backoff) do
|
90
|
-
outboxer_message.destroy!
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def failed(outboxer_message:, exception:, backoff:)
|
95
|
-
@logger.error(
|
96
|
-
"Exception raised: #{exception.class}: #{exception.message}\n" \
|
97
|
-
"#{exception.backtrace.join("\n")}")
|
98
|
-
|
99
|
-
retry_on_error(backoff: backoff) do
|
100
|
-
ActiveRecord::Base.transaction do
|
101
|
-
outboxer_message.update!(status: Message::STATUS[:failed])
|
102
|
-
|
103
|
-
outboxer_message.exceptions.create!(
|
104
|
-
class_name: exception.class.name,
|
105
|
-
message_text: exception.message,
|
106
|
-
backtrace: exception.backtrace)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def retry_on_error(backoff:, &block)
|
112
|
-
current_backoff = 1
|
113
|
-
|
114
|
-
begin
|
115
|
-
block.call
|
116
|
-
rescue StandardError => exception
|
117
|
-
@logger.fatal(
|
118
|
-
"Exception raised: #{exception.class}: #{exception.message}\n" \
|
119
|
-
"#{exception.backtrace.join("\n")}")
|
120
|
-
|
121
|
-
raise exception unless @publishing
|
122
|
-
|
123
|
-
sleep current_backoff
|
124
|
-
|
125
|
-
current_backoff = backoff.call(current_backoff)
|
126
|
-
|
127
|
-
retry
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# Stops the publishing process.
|
132
|
-
#
|
133
|
-
# @note This method will stop the current message publishing process
|
134
|
-
# It is a safe way to interrupt the publishing process at any point.
|
135
|
-
#
|
136
|
-
# @return [void]
|
137
|
-
def stop
|
138
|
-
@publishing = false
|
139
|
-
end
|
140
|
-
|
141
|
-
Signal.trap("TERM") do
|
142
|
-
@logger.info("Received SIGTERM, stopping...")
|
143
|
-
|
144
|
-
stop
|
145
|
-
end
|
146
|
-
|
147
|
-
private_class_method :retry_on_error, :dequeue, :published, :failed
|
148
|
-
end
|
149
|
-
end
|
data/lib/tasks/gem.rake
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
require "rake/packagetask"
|
2
|
-
|
3
|
-
namespace :gem do
|
4
|
-
desc "Bump version number"
|
5
|
-
task :bump, [:type] do |_t, args|
|
6
|
-
args.with_defaults(type: "patch")
|
7
|
-
|
8
|
-
unless %w[major minor patch].include?(args[:type])
|
9
|
-
raise "Invalid version type - choose from major/minor/patch"
|
10
|
-
end
|
11
|
-
|
12
|
-
version_file = File.expand_path("../../lib/outboxer/version.rb", __dir__)
|
13
|
-
version = ""
|
14
|
-
File.open(version_file, "r") do |file|
|
15
|
-
version = file.read.match(/VERSION = "(.*)"/)[1]
|
16
|
-
end
|
17
|
-
|
18
|
-
version_parts = version.split(".").map(&:to_i)
|
19
|
-
case args[:type]
|
20
|
-
when "major"
|
21
|
-
version_parts[0] += 1
|
22
|
-
version_parts[1] = 0
|
23
|
-
version_parts[2] = 0
|
24
|
-
when "minor"
|
25
|
-
version_parts[1] += 1
|
26
|
-
version_parts[2] = 0
|
27
|
-
when "patch"
|
28
|
-
version_parts[2] += 1
|
29
|
-
end
|
30
|
-
|
31
|
-
new_version = version_parts.join(".")
|
32
|
-
File.write(version_file, "module Outboxer\n VERSION = \"#{new_version}\".freeze\nend\n")
|
33
|
-
|
34
|
-
puts "Gem version bumped to #{new_version}"
|
35
|
-
end
|
36
|
-
|
37
|
-
desc "Build the gem"
|
38
|
-
task :build do
|
39
|
-
Outboxer.send(:remove_const, :VERSION) if Outboxer.const_defined?(:VERSION)
|
40
|
-
load "lib/outboxer/version.rb"
|
41
|
-
sh "gem build outboxer.gemspec"
|
42
|
-
|
43
|
-
puts "Gem built successfully."
|
44
|
-
end
|
45
|
-
|
46
|
-
desc "Push the gem to RubyGems"
|
47
|
-
task :push do
|
48
|
-
Outboxer.send(:remove_const, :VERSION) if Outboxer.const_defined?(:VERSION)
|
49
|
-
load "lib/outboxer/version.rb"
|
50
|
-
version = Outboxer::VERSION
|
51
|
-
sh "gem push outboxer-#{version}.gem"
|
52
|
-
|
53
|
-
puts "Gem pushed to RubyGems."
|
54
|
-
end
|
55
|
-
|
56
|
-
desc "Bump, build and push the gem to RubyGems"
|
57
|
-
task release: %i[bump build push]
|
58
|
-
end
|
data/outboxer.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# rubocop:disable Layout/LineLength
|
2
|
-
require_relative "lib/outboxer/version"
|
3
|
-
|
4
|
-
Gem::Specification.new do |spec|
|
5
|
-
spec.name = "outboxer"
|
6
|
-
spec.version = Outboxer::VERSION
|
7
|
-
spec.authors = ["Adam Mikulasev"]
|
8
|
-
spec.email = ["adam@fastprogrammer.co"]
|
9
|
-
|
10
|
-
spec.summary = "Transactional outbox implementation for event driven Ruby on Rails applications that use SQL"
|
11
|
-
spec.homepage = "https://github.com/fast-programmer/outboxer"
|
12
|
-
spec.license = "MIT"
|
13
|
-
spec.required_ruby_version = ">= 2.6.0"
|
14
|
-
|
15
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
-
spec.metadata["source_code_uri"] = "https://github.com/fast-programmer/outboxer"
|
17
|
-
spec.metadata["documentation_uri"] = "https://rubydoc.info/github/fast-programmer/outboxer/master"
|
18
|
-
spec.metadata["changelog_uri"] = "https://github.com/fast-programmer/outboxer/blob/master/CHANGELOG.md"
|
19
|
-
spec.metadata["rubygems_mfa_required"] = "true"
|
20
|
-
|
21
|
-
spec.files = Dir.chdir(__dir__) do
|
22
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
-
(File.expand_path(f) == __FILE__) ||
|
24
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
25
|
-
end
|
26
|
-
end
|
27
|
-
spec.bindir = "exe"
|
28
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
-
spec.require_paths = ["lib"]
|
30
|
-
|
31
|
-
spec.add_dependency "activerecord", "~> 7.0"
|
32
|
-
end
|
33
|
-
# rubocop:enable Layout/LineLength
|
data/sig/outboxer.rbs
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module Outboxer
|
2
|
-
VERSION: String
|
3
|
-
|
4
|
-
module Publisher
|
5
|
-
class Args
|
6
|
-
attr_reader message: untyped
|
7
|
-
attr_reader logger: Logger
|
8
|
-
|
9
|
-
def initialize: (untyped message, Logger logger) -> void
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.publish: (?Integer poll, ?Proc[Integer, Integer] backoff) { (Args) -> untyped } -> void
|
13
|
-
def self.dequeue: ({ backoff: Proc[Integer, Integer] }) -> untyped
|
14
|
-
def self.published: ({ outboxer_message: untyped, backoff: Proc[Integer, Integer] }) -> void
|
15
|
-
def self.failed: ({ outboxer_message: untyped, exception: Exception, backoff: Proc[Integer, Integer] }) -> void
|
16
|
-
def self.retry_on_error: ({ backoff: Proc[Integer, Integer] }) { () -> untyped } -> untyped
|
17
|
-
def self.stop: () -> void
|
18
|
-
end
|
19
|
-
end
|