outboxer 0.1.10 → 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 +86 -48
  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 -14
  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 -132
  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,14 +0,0 @@
1
- # require "bundler/gem_tasks"
2
- # require "bundler/setup"
3
- require "rspec/core/rake_task"
4
-
5
- RSpec::Core::RakeTask.new(:spec)
6
-
7
- require "pry"
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
13
-
14
- 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,132 +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
- Args = Struct.new(:message, :logger)
11
-
12
- # This method initiates the publishing process. It dequeues the messages one by one
13
- # and yields to a block that should contain the publishing logic.
14
- #
15
- # @param [Integer] poll
16
- # Sleep time in seconds between polling the queue when it's empty.
17
- #
18
- # @param [Proc] backoff
19
- # A Proc that takes the current backoff time and returns the new backoff time.
20
- #
21
- # @yieldparam [Args] args
22
- # An Args object containing the message to publish and a logger.
23
- def publish(poll: 1, backoff: ->(current_backoff) { [current_backoff * 2, 5 * 60].min })
24
- @publishing = true
25
-
26
- ActiveRecord::Base.connection_pool.with_connection do
27
- while @publishing
28
- outboxer_message = dequeue(backoff: backoff)
29
-
30
- if outboxer_message.nil?
31
- sleep poll
32
-
33
- next
34
- end
35
-
36
- begin
37
- yield Args.new(outboxer_message.message, @logger)
38
- rescue StandardError => exception
39
- failed(
40
- outboxer_message: outboxer_message,
41
- backoff: backoff,
42
- exception: exception)
43
-
44
- next
45
- end
46
-
47
- published(
48
- outboxer_message: outboxer_message,
49
- backoff: backoff)
50
- end
51
- end
52
- end
53
-
54
- def dequeue(backoff:)
55
- retry_on_error(backoff: backoff) do
56
- ActiveRecord::Base.transaction do
57
- message = Message
58
- .where(status: Message::STATUS[:unpublished])
59
- .order(created_at: :asc)
60
- .limit(1)
61
- .lock("FOR UPDATE SKIP LOCKED")
62
- .first
63
-
64
- message&.update!(status: Message::STATUS[:publishing])
65
-
66
- message
67
- end
68
- end
69
- end
70
-
71
- def published(outboxer_message:, backoff:)
72
- retry_on_error(backoff: backoff) do
73
- outboxer_message.destroy!
74
- end
75
- end
76
-
77
- def failed(outboxer_message:, exception:, backoff:)
78
- @logger.error(
79
- "Exception raised: #{exception.class}: #{exception.message}\n" \
80
- "#{exception.backtrace.join("\n")}")
81
-
82
- retry_on_error(backoff: backoff) do
83
- ActiveRecord::Base.transaction do
84
- outboxer_message.update!(status: Message::STATUS[:failed])
85
-
86
- outboxer_message.exceptions.create!(
87
- class_name: exception.class.name,
88
- message_text: exception.message,
89
- backtrace: exception.backtrace)
90
- end
91
- end
92
- end
93
-
94
- def retry_on_error(backoff:, &block)
95
- current_backoff = 1
96
-
97
- begin
98
- block.call
99
- rescue StandardError => exception
100
- @logger.fatal(
101
- "Exception raised: #{exception.class}: #{exception.message}\n" \
102
- "#{exception.backtrace.join("\n")}")
103
-
104
- raise exception unless @publishing
105
-
106
- sleep current_backoff
107
-
108
- current_backoff = backoff.call(current_backoff)
109
-
110
- retry
111
- end
112
- end
113
-
114
- # Stops the publishing process.
115
- #
116
- # @note This method will stop the current message publishing process
117
- # It is a safe way to interrupt the publishing process at any point.
118
- #
119
- # @return [void]
120
- def stop
121
- @publishing = false
122
- end
123
-
124
- Signal.trap("TERM") do
125
- @logger.info("Received SIGTERM, stopping...")
126
-
127
- stop
128
- end
129
-
130
- private_class_method :retry_on_error, :dequeue, :published, :failed
131
- end
132
- 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