sbmt-outbox 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|