sbmt-outbox 5.0.0
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 +7 -0
- data/README.md +440 -0
- data/Rakefile +3 -0
- data/app/interactors/sbmt/outbox/base_create_item.rb +55 -0
- data/app/interactors/sbmt/outbox/create_inbox_item.rb +10 -0
- data/app/interactors/sbmt/outbox/create_outbox_item.rb +10 -0
- data/app/interactors/sbmt/outbox/dry_interactor.rb +16 -0
- data/app/interactors/sbmt/outbox/partition_strategies/hash_partitioning.rb +20 -0
- data/app/interactors/sbmt/outbox/partition_strategies/number_partitioning.rb +26 -0
- data/app/interactors/sbmt/outbox/process_item.rb +269 -0
- data/app/interactors/sbmt/outbox/retry_strategies/compacted_log.rb +41 -0
- data/app/interactors/sbmt/outbox/retry_strategies/exponential_backoff.rb +34 -0
- data/app/jobs/sbmt/outbox/base_delete_stale_items_job.rb +78 -0
- data/app/jobs/sbmt/outbox/delete_stale_inbox_items_job.rb +15 -0
- data/app/jobs/sbmt/outbox/delete_stale_outbox_items_job.rb +15 -0
- data/app/models/sbmt/outbox/base_item.rb +165 -0
- data/app/models/sbmt/outbox/base_item_config.rb +106 -0
- data/app/models/sbmt/outbox/inbox_item.rb +38 -0
- data/app/models/sbmt/outbox/inbox_item_config.rb +13 -0
- data/app/models/sbmt/outbox/outbox_item.rb +52 -0
- data/app/models/sbmt/outbox/outbox_item_config.rb +13 -0
- data/config/initializers/schked.rb +9 -0
- data/config/initializers/yabeda.rb +71 -0
- data/config/schedule.rb +9 -0
- data/exe/outbox +16 -0
- data/lib/generators/helpers/config.rb +46 -0
- data/lib/generators/helpers/initializer.rb +41 -0
- data/lib/generators/helpers/items.rb +17 -0
- data/lib/generators/helpers/migration.rb +73 -0
- data/lib/generators/helpers/paas.rb +17 -0
- data/lib/generators/helpers/values.rb +49 -0
- data/lib/generators/helpers.rb +8 -0
- data/lib/generators/outbox/install/USAGE +10 -0
- data/lib/generators/outbox/install/install_generator.rb +33 -0
- data/lib/generators/outbox/install/templates/Outboxfile +3 -0
- data/lib/generators/outbox/install/templates/outbox.rb +32 -0
- data/lib/generators/outbox/install/templates/outbox.yml +51 -0
- data/lib/generators/outbox/item/USAGE +12 -0
- data/lib/generators/outbox/item/item_generator.rb +94 -0
- data/lib/generators/outbox/item/templates/inbox_item.rb.tt +7 -0
- data/lib/generators/outbox/item/templates/outbox_item.rb.tt +16 -0
- data/lib/generators/outbox/transport/USAGE +19 -0
- data/lib/generators/outbox/transport/templates/inbox_transport.yml.erb +9 -0
- data/lib/generators/outbox/transport/templates/outbox_transport.yml.erb +10 -0
- data/lib/generators/outbox/transport/transport_generator.rb +60 -0
- data/lib/generators/outbox.rb +23 -0
- data/lib/sbmt/outbox/ascii_art.rb +62 -0
- data/lib/sbmt/outbox/cli.rb +100 -0
- data/lib/sbmt/outbox/database_switcher.rb +15 -0
- data/lib/sbmt/outbox/engine.rb +45 -0
- data/lib/sbmt/outbox/error_tracker.rb +26 -0
- data/lib/sbmt/outbox/errors.rb +14 -0
- data/lib/sbmt/outbox/instrumentation/open_telemetry_loader.rb +34 -0
- data/lib/sbmt/outbox/logger.rb +35 -0
- data/lib/sbmt/outbox/middleware/builder.rb +23 -0
- data/lib/sbmt/outbox/middleware/open_telemetry/tracing_create_item_middleware.rb +42 -0
- data/lib/sbmt/outbox/middleware/open_telemetry/tracing_item_process_middleware.rb +49 -0
- data/lib/sbmt/outbox/middleware/runner.rb +29 -0
- data/lib/sbmt/outbox/middleware/sentry/tracing_batch_process_middleware.rb +48 -0
- data/lib/sbmt/outbox/middleware/sentry/tracing_item_process_middleware.rb +65 -0
- data/lib/sbmt/outbox/middleware/sentry/transaction.rb +28 -0
- data/lib/sbmt/outbox/probes/probe.rb +38 -0
- data/lib/sbmt/outbox/redis_client_factory.rb +36 -0
- data/lib/sbmt/outbox/tasks/delete_failed_items.rake +17 -0
- data/lib/sbmt/outbox/tasks/retry_failed_items.rake +20 -0
- data/lib/sbmt/outbox/thread_pool.rb +108 -0
- data/lib/sbmt/outbox/throttler.rb +52 -0
- data/lib/sbmt/outbox/version.rb +7 -0
- data/lib/sbmt/outbox/worker.rb +233 -0
- data/lib/sbmt/outbox.rb +136 -0
- data/lib/sbmt-outbox.rb +3 -0
- metadata +594 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class BaseItemConfig
|
6
|
+
DEFAULT_BUCKET_SIZE = 16
|
7
|
+
DEFAULT_PARTITION_STRATEGY = :number
|
8
|
+
|
9
|
+
def initialize(box_name)
|
10
|
+
self.box_name = box_name
|
11
|
+
|
12
|
+
validate!
|
13
|
+
end
|
14
|
+
|
15
|
+
def owner
|
16
|
+
return @owner if defined? @owner
|
17
|
+
|
18
|
+
@owner = options[:owner].presence || Outbox.yaml_config[:owner].presence
|
19
|
+
end
|
20
|
+
|
21
|
+
def bucket_size
|
22
|
+
@bucket_size ||= (options[:bucket_size] || Outbox.yaml_config.fetch(:bucket_size, DEFAULT_BUCKET_SIZE)).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def partition_size
|
26
|
+
@partition_size ||= (options[:partition_size] || 1).to_i
|
27
|
+
end
|
28
|
+
|
29
|
+
def retention
|
30
|
+
@retention ||= ActiveSupport::Duration.parse(options[:retention] || "P1W")
|
31
|
+
end
|
32
|
+
|
33
|
+
def max_retries
|
34
|
+
@max_retries ||= (options[:max_retries] || 0).to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def minimal_retry_interval
|
38
|
+
@minimal_retry_interval ||= (options[:minimal_retry_interval] || 10).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def maximal_retry_interval
|
42
|
+
@maximal_retry_interval ||= (options[:maximal_retry_interval] || 600).to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def multiplier_retry_interval
|
46
|
+
@multiplier_retry_interval ||= (options[:multiplier_retry_interval] || 2).to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def retry_strategies
|
50
|
+
@retry_strategies ||= Array.wrap(options[:retry_strategies]).map do |str_name|
|
51
|
+
"Sbmt::Outbox::RetryStrategies::#{str_name.camelize}".constantize
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def partition_strategy
|
56
|
+
return @partition_strategy if defined?(@partition_strategy)
|
57
|
+
|
58
|
+
str_name = options.fetch(:partition_strategy, DEFAULT_PARTITION_STRATEGY)
|
59
|
+
@partition_strategy = "Sbmt::Outbox::PartitionStrategies::#{str_name.camelize}Partitioning".constantize
|
60
|
+
end
|
61
|
+
|
62
|
+
def transports
|
63
|
+
return @transports if defined?(@transports)
|
64
|
+
|
65
|
+
values = options.fetch(:transports, [])
|
66
|
+
|
67
|
+
if values.is_a?(Hash)
|
68
|
+
values = values.each_with_object([]) do |(key, params), memo|
|
69
|
+
memo << params.merge!(class: key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@transports = values.each_with_object({}) do |params, memo|
|
74
|
+
params = params.symbolize_keys
|
75
|
+
event_name = params.delete(:event_name) || :_all_
|
76
|
+
memo[event_name] ||= []
|
77
|
+
namespace = params.delete(:class)&.camelize
|
78
|
+
raise ArgumentError, "Transport name cannot be blank" if namespace.blank?
|
79
|
+
|
80
|
+
factory = "#{namespace}::OutboxTransportFactory".safe_constantize
|
81
|
+
memo[event_name] << if factory
|
82
|
+
factory.build(**params.symbolize_keys)
|
83
|
+
else
|
84
|
+
namespace.constantize.new(**params.symbolize_keys)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_accessor :box_name
|
92
|
+
|
93
|
+
def options
|
94
|
+
@options ||= lookup_config || {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def lookup_config
|
98
|
+
raise NotImplementedError
|
99
|
+
end
|
100
|
+
|
101
|
+
def validate!
|
102
|
+
raise ConfigError, "Bucket size should be greater or equal to partition size" if partition_size > bucket_size
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class InboxItem < BaseItem
|
6
|
+
self.abstract_class = true
|
7
|
+
|
8
|
+
class << self
|
9
|
+
alias_method :inbox_name, :box_name
|
10
|
+
|
11
|
+
def box_type
|
12
|
+
:inbox
|
13
|
+
end
|
14
|
+
|
15
|
+
def lookup_config
|
16
|
+
Sbmt::Outbox::InboxItemConfig
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
delegate :inbox_name, :config, to: "self.class"
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_options
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_log_details
|
29
|
+
{
|
30
|
+
uuid: uuid,
|
31
|
+
status: status,
|
32
|
+
created_at: created_at.to_datetime.rfc3339(6),
|
33
|
+
errors_count: errors_count
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class OutboxItem < BaseItem
|
6
|
+
self.abstract_class = true
|
7
|
+
|
8
|
+
IDEMPOTENCY_HEADER_NAME = "Idempotency-Key"
|
9
|
+
SEQUENCE_HEADER_NAME = "Sequence-ID"
|
10
|
+
EVENT_TIME_HEADER_NAME = "Created-At"
|
11
|
+
OUTBOX_HEADER_NAME = "Outbox-Name"
|
12
|
+
DISPATCH_TIME_HEADER_NAME = "Dispatched-At"
|
13
|
+
|
14
|
+
class << self
|
15
|
+
alias_method :outbox_name, :box_name
|
16
|
+
|
17
|
+
def box_type
|
18
|
+
:outbox
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup_config
|
22
|
+
Sbmt::Outbox::OutboxItemConfig
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
delegate :outbox_name, :config, to: "self.class"
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default_options
|
31
|
+
{
|
32
|
+
headers: {
|
33
|
+
OUTBOX_HEADER_NAME => outbox_name,
|
34
|
+
IDEMPOTENCY_HEADER_NAME => uuid,
|
35
|
+
SEQUENCE_HEADER_NAME => id.to_s,
|
36
|
+
EVENT_TIME_HEADER_NAME => created_at&.to_datetime&.rfc3339(6),
|
37
|
+
DISPATCH_TIME_HEADER_NAME => Time.current.to_datetime.rfc3339(6)
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_log_details
|
43
|
+
{
|
44
|
+
uuid: uuid,
|
45
|
+
status: status,
|
46
|
+
created_at: created_at.to_datetime.rfc3339(6),
|
47
|
+
errors_count: errors_count
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class OutboxItemConfig < BaseItemConfig
|
6
|
+
private
|
7
|
+
|
8
|
+
def lookup_config
|
9
|
+
Outbox.yaml_config.dig(:outbox_items, box_name) || Outbox.yaml_config.dig(:items, box_name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Yabeda.configure do
|
4
|
+
# error_counter retry_counter sent_counter fetch_error_counter discarded_counter
|
5
|
+
group :outbox do
|
6
|
+
counter :created_counter,
|
7
|
+
tags: %i[type name partition owner],
|
8
|
+
comment: "The total number of created messages"
|
9
|
+
|
10
|
+
counter :sent_counter,
|
11
|
+
tags: %i[type name partition owner],
|
12
|
+
comment: "The total number of processed messages"
|
13
|
+
|
14
|
+
counter :error_counter,
|
15
|
+
tags: %i[type name partition owner],
|
16
|
+
comment: "Errors (excepting retries) that occurred while processing messages"
|
17
|
+
|
18
|
+
counter :retry_counter,
|
19
|
+
tags: %i[type name partition owner],
|
20
|
+
comment: "Retries that occurred while processing messages"
|
21
|
+
|
22
|
+
counter :discarded_counter,
|
23
|
+
tags: %i[type name partition owner],
|
24
|
+
comment: "The total number of discarded messages"
|
25
|
+
|
26
|
+
counter :fetch_error_counter,
|
27
|
+
tags: %i[type name partition owner],
|
28
|
+
comment: "Errors that occurred while fetching messages"
|
29
|
+
|
30
|
+
gauge :last_stored_event_id,
|
31
|
+
tags: %i[type name partition owner],
|
32
|
+
comment: "The ID of the last stored event"
|
33
|
+
|
34
|
+
gauge :last_sent_event_id,
|
35
|
+
tags: %i[type name partition owner],
|
36
|
+
comment: "The ID of the last sent event. " \
|
37
|
+
"If the message order is not preserved, the value may be inaccurate"
|
38
|
+
|
39
|
+
histogram :process_latency,
|
40
|
+
tags: %i[type name partition owner],
|
41
|
+
unit: :seconds,
|
42
|
+
buckets: [1, 2.5, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 300, 600, 1200].freeze,
|
43
|
+
comment: "A histogram outbox process latency"
|
44
|
+
end
|
45
|
+
|
46
|
+
group :box_worker do
|
47
|
+
counter :job_counter,
|
48
|
+
tags: %i[type name partition worker_number state],
|
49
|
+
comment: "The total number of processed jobs"
|
50
|
+
|
51
|
+
counter :job_timeout_counter,
|
52
|
+
tags: %i[type name partition_key worker_number],
|
53
|
+
comment: "Requeue of a job that occurred while processing the batch"
|
54
|
+
|
55
|
+
counter :job_items_counter,
|
56
|
+
tags: %i[type name partition worker_number],
|
57
|
+
comment: "The total number of processed items in jobs"
|
58
|
+
|
59
|
+
histogram :job_execution_runtime,
|
60
|
+
comment: "A histogram of the job execution time",
|
61
|
+
unit: :seconds,
|
62
|
+
tags: %i[type name partition worker_number],
|
63
|
+
buckets: [0.5, 1, 2.5, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 300, 600]
|
64
|
+
|
65
|
+
histogram :item_execution_runtime,
|
66
|
+
comment: "A histogram of the item execution time",
|
67
|
+
unit: :seconds,
|
68
|
+
tags: %i[type name partition worker_number],
|
69
|
+
buckets: [0.5, 1, 2.5, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 300]
|
70
|
+
end
|
71
|
+
end
|
data/config/schedule.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
every "10m", as: "Sbmt::Outbox::DeleteStaleOutboxItemsJob", overlap: false, timeout: "60s" do
|
4
|
+
Sbmt::Outbox::DeleteStaleOutboxItemsJob.enqueue
|
5
|
+
end
|
6
|
+
|
7
|
+
every "10m", as: "Sbmt::Outbox::DeleteStaleInboxItemsJob", overlap: false, timeout: "60s" do
|
8
|
+
Sbmt::Outbox::DeleteStaleInboxItemsJob.enqueue
|
9
|
+
end
|
data/exe/outbox
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "sbmt/outbox/cli"
|
6
|
+
|
7
|
+
# rubocop:disable Lint/RescueException
|
8
|
+
begin
|
9
|
+
Sbmt::Outbox::CLI.start(ARGV)
|
10
|
+
rescue Exception => e
|
11
|
+
warn "Outbox exited with error"
|
12
|
+
warn(e.message) if e.respond_to?(:message)
|
13
|
+
warn(e.backtrace.join("\n")) if e.respond_to?(:backtrace) && e.backtrace.respond_to?(:join)
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
# rubocop:enable Lint/RescueException
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Outbox
|
4
|
+
module Generators
|
5
|
+
module Helpers
|
6
|
+
module Config
|
7
|
+
CONFIG_PATH = "config/outbox.yml"
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def config_exists?
|
12
|
+
File.exist?(CONFIG_PATH)
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_config!
|
16
|
+
return if config_exists?
|
17
|
+
|
18
|
+
if yes?("Seems like `config/outbox.yml` doesn't exist. Would you like to generate it?")
|
19
|
+
generate "outbox:install"
|
20
|
+
else
|
21
|
+
raise Rails::Generators::Error, "Something went wrong: `config/outbox.yml` is missing. " \
|
22
|
+
"Please generate one by running `bin/rails g outbox:install` " \
|
23
|
+
"or add it manually."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_item_to_config(config_block_name, item_template_data)
|
28
|
+
template_data_with_parent = <<~RUBY
|
29
|
+
#{config_block_name}:
|
30
|
+
#{optimize_indentation(item_template_data, 2)}
|
31
|
+
RUBY
|
32
|
+
|
33
|
+
data, after = if File.binread(CONFIG_PATH).match?(/^\s*#{config_block_name}:/)
|
34
|
+
# if config already contains non-empty/non-commented-out inbox_items/outbox_items block
|
35
|
+
[optimize_indentation(item_template_data, 4), /^\s*#{config_block_name}:\s*\n/]
|
36
|
+
else
|
37
|
+
# there is no config for our items
|
38
|
+
# so we just set it up initially
|
39
|
+
[optimize_indentation(template_data_with_parent, 2), /^default:.+?\n/]
|
40
|
+
end
|
41
|
+
inject_into_file CONFIG_PATH, data, after: after
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Outbox
|
4
|
+
module Generators
|
5
|
+
module Helpers
|
6
|
+
module Initializer
|
7
|
+
OUTBOX_INITIALIZER_PATH = "config/initializers/outbox.rb"
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def add_item_to_initializer(attr_name)
|
12
|
+
template_data_with_push = <<~RUBY
|
13
|
+
"#{namespaced_item_class_name}",
|
14
|
+
RUBY
|
15
|
+
|
16
|
+
template_data_with_append = <<~RUBY
|
17
|
+
#{attr_name} << "#{namespaced_item_class_name}"
|
18
|
+
RUBY
|
19
|
+
|
20
|
+
initial_template_data = <<~RUBY
|
21
|
+
#{attr_name}.push(
|
22
|
+
"#{namespaced_item_class_name}"
|
23
|
+
)
|
24
|
+
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
content = File.binread(OUTBOX_INITIALIZER_PATH)
|
28
|
+
data, after = if content.match?(/^\s*#{attr_name}\.push/)
|
29
|
+
[optimize_indentation(template_data_with_push, 4), /^\s*#{attr_name}\.push\(\n/]
|
30
|
+
elsif content.match?(/^\s*#{attr_name}\s+<</)
|
31
|
+
[optimize_indentation(template_data_with_append, 2), /^\s*#{attr_name}\s+<< ".+?\n/]
|
32
|
+
else
|
33
|
+
# there is no config for items, so set it up initially
|
34
|
+
[optimize_indentation(initial_template_data, 2), /^Rails.application.config.outbox.tap do.+?\n/]
|
35
|
+
end
|
36
|
+
inject_into_file OUTBOX_INITIALIZER_PATH, data, after: after
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Outbox
|
4
|
+
module Generators
|
5
|
+
module Helpers
|
6
|
+
module Migration
|
7
|
+
private
|
8
|
+
|
9
|
+
def create_migration_file(migration_class_name, migration_table_name)
|
10
|
+
return false if find_existing_migration(migration_class_name.tableize)
|
11
|
+
|
12
|
+
result = generate "rails:migration", migration_class_name, "--no-timestamps"
|
13
|
+
return unless result
|
14
|
+
|
15
|
+
migration_filepath = find_existing_migration(migration_class_name.tableize)
|
16
|
+
return unless migration_filepath
|
17
|
+
|
18
|
+
patch_migration_with_template_data(migration_filepath, migration_table_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_existing_migration(name)
|
22
|
+
base_path = "db/migrate"
|
23
|
+
found_files = Dir.glob("*_#{name}.rb", base: base_path)
|
24
|
+
return if found_files.size != 1
|
25
|
+
|
26
|
+
"#{base_path}/#{found_files[0]}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def patch_migration_with_template_data(migration_filepath, table_name)
|
30
|
+
data_to_replace = /^\s*create_table :#{table_name}.+?end\n/m
|
31
|
+
|
32
|
+
template_data = <<~RUBY
|
33
|
+
create_table :#{table_name} do |t|
|
34
|
+
t.uuid :uuid, null: false
|
35
|
+
t.string :event_key, null: false
|
36
|
+
t.integer :bucket, null: false
|
37
|
+
t.integer :status, null: false, default: 0
|
38
|
+
t.jsonb :options
|
39
|
+
t.binary :payload, null: false # when using mysql the column type should be mediumblob
|
40
|
+
t.integer :errors_count, null: false, default: 0
|
41
|
+
t.text :error_log
|
42
|
+
t.timestamp :processed_at
|
43
|
+
t.timestamps null: false
|
44
|
+
end
|
45
|
+
|
46
|
+
add_index :#{table_name}, :uuid, unique: true
|
47
|
+
add_index :#{table_name}, [:status, :bucket]
|
48
|
+
add_index :#{table_name}, :event_key
|
49
|
+
add_index :#{table_name}, :created_at
|
50
|
+
RUBY
|
51
|
+
|
52
|
+
gsub_file(migration_filepath, data_to_replace, optimize_indentation(template_data, 4))
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_inbox_model_file(path)
|
56
|
+
template "inbox_item.rb", File.join("app/models", "#{path}.rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_outbox_model_file(path)
|
60
|
+
template "outbox_item.rb", File.join("app/models", "#{path}.rb")
|
61
|
+
end
|
62
|
+
|
63
|
+
def migration_class_name
|
64
|
+
"Create" + namespaced_item_class_name.gsub("::", "").pluralize
|
65
|
+
end
|
66
|
+
|
67
|
+
def migration_table_name
|
68
|
+
namespaced_item_class_name.tableize.tr("/", "_")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Outbox
|
4
|
+
module Generators
|
5
|
+
module Helpers
|
6
|
+
module Values
|
7
|
+
VALUES_PATH = "configs/values.yaml"
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def add_item_to_values(deployment_name, item_path)
|
12
|
+
template_data = <<~RUBY
|
13
|
+
#{deployment_name}:
|
14
|
+
replicas:
|
15
|
+
_default: 1
|
16
|
+
prod: 2
|
17
|
+
command:
|
18
|
+
- /bin/sh
|
19
|
+
- -c
|
20
|
+
- exec bundle exec outbox start --box #{item_path} --concurrency 4
|
21
|
+
readinessProbe:
|
22
|
+
httpGet:
|
23
|
+
path: /readiness/outbox
|
24
|
+
port: SET-UP-YOUR-HEALTHCHECK-PORT-HERE
|
25
|
+
livenessProbe:
|
26
|
+
httpGet:
|
27
|
+
path: /liveness/outbox
|
28
|
+
port: SET-UP-YOUR-HEALTHCHECK-PORT-HERE
|
29
|
+
resources:
|
30
|
+
prod:
|
31
|
+
requests:
|
32
|
+
cpu: "500m"
|
33
|
+
memory: "512Mi"
|
34
|
+
limits:
|
35
|
+
cpu: "1"
|
36
|
+
memory: "1Gi"
|
37
|
+
|
38
|
+
RUBY
|
39
|
+
|
40
|
+
inject_into_file VALUES_PATH, optimize_indentation(template_data, 2), after: /^deployments:\s*\n/
|
41
|
+
end
|
42
|
+
|
43
|
+
def dasherize_item(item_path)
|
44
|
+
item_path.tr("/", "-").dasherize
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "generators/outbox"
|
4
|
+
|
5
|
+
module Outbox
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Base
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
class_option :skip_outboxfile, type: :boolean, default: false, desc: "Skip creating Outboxfile"
|
11
|
+
class_option :skip_initializer, type: :boolean, default: false, desc: "Skip creating config/initializers/outbox.rb"
|
12
|
+
class_option :skip_config, type: :boolean, default: false, desc: "Skip creating config/outbox.yml"
|
13
|
+
|
14
|
+
def create_outboxfile
|
15
|
+
return if options[:skip_outboxfile]
|
16
|
+
|
17
|
+
copy_file "Outboxfile", "Outboxfile"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_initializer
|
21
|
+
return if options[:skip_initializer]
|
22
|
+
|
23
|
+
copy_file "outbox.rb", OUTBOX_INITIALIZER_PATH
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_config
|
27
|
+
return if options[:skip_config]
|
28
|
+
|
29
|
+
copy_file "outbox.yml", CONFIG_PATH
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rails.application.config.outbox.tap do |config|
|
4
|
+
# setup custom ErrorTracker
|
5
|
+
# config.error_tracker = "ErrorTracker"
|
6
|
+
|
7
|
+
# customize redis
|
8
|
+
# config.redis = {url: ENV.fetch("REDIS_URL", "redis://127.0.0.1:6379")}
|
9
|
+
|
10
|
+
# setup custom batch process middlewares
|
11
|
+
# config.batch_process_middlewares << "MyBatchProcessMiddleware"
|
12
|
+
|
13
|
+
# setup custom item process middlewares
|
14
|
+
# config.item_process_middlewares << "MyItemProcessMiddleware"
|
15
|
+
|
16
|
+
# config.process_items.tap do |x|
|
17
|
+
# # maximum processing time of the batch, after which the batch will be considered hung and processing will be aborted
|
18
|
+
# x[:general_timeout] = 180
|
19
|
+
# # maximum patch processing time, after which the processing of the patch will be aborted in the current thread,
|
20
|
+
# # and the next thread that picks up the batch will start processing from the same place
|
21
|
+
# x[:cutoff_timeout] = 60
|
22
|
+
# # batch size
|
23
|
+
# x[:batch_size] = 200
|
24
|
+
# end
|
25
|
+
|
26
|
+
# config.worker.tap do |worker|
|
27
|
+
# # number of batches that one thread will process per rate interval
|
28
|
+
# worker[:rate_limit] = 10
|
29
|
+
# # rate interval in seconds
|
30
|
+
# worker[:rate_interval] = 60
|
31
|
+
# end
|
32
|
+
end
|