outboxer 0.1.11 → 1.0.0.pre.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -47
  3. data/db/migrate/create_outboxer_exceptions.rb +20 -0
  4. data/db/migrate/create_outboxer_frames.rb +16 -0
  5. data/db/migrate/create_outboxer_messages.rb +19 -0
  6. data/generators/message_publisher_generator.rb +11 -0
  7. data/generators/{outboxer/install_generator.rb → schema_generator.rb} +4 -9
  8. data/lib/outboxer/database.rb +44 -0
  9. data/lib/outboxer/logger.rb +17 -0
  10. data/lib/outboxer/message.rb +262 -13
  11. data/lib/outboxer/messages.rb +232 -0
  12. data/lib/outboxer/models/exception.rb +15 -0
  13. data/lib/outboxer/models/frame.rb +14 -0
  14. data/lib/outboxer/models/message.rb +46 -0
  15. data/lib/outboxer/models.rb +3 -0
  16. data/lib/outboxer/railtie.rb +2 -4
  17. data/lib/outboxer/version.rb +1 -1
  18. data/lib/outboxer/web/public/css/bootstrap-icons.css +2078 -0
  19. data/lib/outboxer/web/public/css/bootstrap-icons.min.css +5 -0
  20. data/lib/outboxer/web/public/css/bootstrap.css +12071 -0
  21. data/lib/outboxer/web/public/css/bootstrap.min.css +6 -0
  22. data/lib/outboxer/web/public/css/fonts/bootstrap-icons.woff +0 -0
  23. data/lib/outboxer/web/public/css/fonts/bootstrap-icons.woff2 +0 -0
  24. data/lib/outboxer/web/public/favicon.svg +3 -0
  25. data/lib/outboxer/web/public/js/bootstrap.bundle.js +6306 -0
  26. data/lib/outboxer/web/public/js/bootstrap.bundle.min.js +7 -0
  27. data/lib/outboxer/web/views/error.erb +63 -0
  28. data/lib/outboxer/web/views/home.erb +172 -0
  29. data/lib/outboxer/web/views/layout.erb +80 -0
  30. data/lib/outboxer/web/views/message.erb +81 -0
  31. data/lib/outboxer/web/views/messages/index.erb +60 -0
  32. data/lib/outboxer/web/views/messages/show.erb +31 -0
  33. data/lib/outboxer/web/views/messages.erb +262 -0
  34. data/lib/outboxer/web.rb +430 -0
  35. data/lib/outboxer.rb +9 -5
  36. metadata +279 -22
  37. data/.rspec +0 -3
  38. data/.rubocop.yml +0 -229
  39. data/CHANGELOG.md +0 -5
  40. data/CODE_OF_CONDUCT.md +0 -84
  41. data/LICENSE.txt +0 -21
  42. data/Rakefile +0 -12
  43. data/generators/outboxer/templates/bin/publisher.rb +0 -11
  44. data/generators/outboxer/templates/migrations/create_outboxer_exceptions.rb +0 -15
  45. data/generators/outboxer/templates/migrations/create_outboxer_messages.rb +0 -13
  46. data/lib/outboxer/exception.rb +0 -9
  47. data/lib/outboxer/outboxable.rb +0 -21
  48. data/lib/outboxer/publisher.rb +0 -149
  49. data/lib/tasks/gem.rake +0 -58
  50. data/outboxer.gemspec +0 -33
  51. data/sig/outboxer.rbs +0 -19
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require "rspec/core/rake_task"
2
-
3
- RSpec::Core::RakeTask.new(:spec)
4
-
5
- require "pry"
6
- require "rubocop/rake_task"
7
-
8
- RuboCop::RakeTask.new
9
-
10
- task default: %i[spec rubocop]
11
-
12
- load "lib/tasks/gem.rake"
@@ -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
@@ -1,9 +0,0 @@
1
- require "active_record"
2
-
3
- module Outboxer
4
- class Exception < ::ActiveRecord::Base
5
- self.table_name = :outboxer_exceptions
6
-
7
- belongs_to :message, class_name: "::Outboxer::Message"
8
- end
9
- end
@@ -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
@@ -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