pub_sub_model_sync 0.5.10 → 1.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 +4 -4
- data/.github/workflows/release.yml +43 -0
- data/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +34 -1
- data/Dockerfile +6 -0
- data/Gemfile.lock +150 -134
- data/README.md +372 -192
- data/docker-compose.yaml +12 -0
- data/docs/notifications-diagram.png +0 -0
- data/lib/pub_sub_model_sync.rb +3 -1
- data/lib/pub_sub_model_sync/base.rb +4 -7
- data/lib/pub_sub_model_sync/config.rb +17 -8
- data/lib/pub_sub_model_sync/initializers/before_commit.rb +23 -0
- data/lib/pub_sub_model_sync/message_processor.rb +34 -10
- data/lib/pub_sub_model_sync/message_publisher.rb +90 -29
- data/lib/pub_sub_model_sync/mock_google_service.rb +4 -0
- data/lib/pub_sub_model_sync/mock_kafka_service.rb +13 -0
- data/lib/pub_sub_model_sync/payload.rb +35 -16
- data/lib/pub_sub_model_sync/payload_builder.rb +62 -0
- data/lib/pub_sub_model_sync/publisher_concern.rb +77 -47
- data/lib/pub_sub_model_sync/railtie.rb +6 -0
- data/lib/pub_sub_model_sync/run_subscriber.rb +108 -0
- data/lib/pub_sub_model_sync/service_base.rb +19 -37
- data/lib/pub_sub_model_sync/service_google.rb +53 -17
- data/lib/pub_sub_model_sync/service_kafka.rb +40 -13
- data/lib/pub_sub_model_sync/service_rabbit.rb +41 -33
- data/lib/pub_sub_model_sync/subscriber.rb +14 -66
- data/lib/pub_sub_model_sync/subscriber_concern.rb +23 -23
- data/lib/pub_sub_model_sync/tasks/worker.rake +11 -0
- data/lib/pub_sub_model_sync/transaction.rb +73 -0
- data/lib/pub_sub_model_sync/version.rb +1 -1
- data/samples/README.md +50 -0
- data/samples/app1/.gitattributes +8 -0
- data/samples/app1/.gitignore +28 -0
- data/samples/app1/Dockerfile +13 -0
- data/samples/app1/Gemfile +37 -0
- data/samples/app1/Gemfile.lock +171 -0
- data/samples/app1/README.md +24 -0
- data/samples/app1/Rakefile +6 -0
- data/samples/app1/app/models/application_record.rb +3 -0
- data/samples/app1/app/models/concerns/.keep +0 -0
- data/samples/app1/app/models/post.rb +19 -0
- data/samples/app1/app/models/user.rb +29 -0
- data/samples/app1/bin/bundle +114 -0
- data/samples/app1/bin/rails +5 -0
- data/samples/app1/bin/rake +5 -0
- data/samples/app1/bin/setup +33 -0
- data/samples/app1/bin/spring +14 -0
- data/samples/app1/config.ru +6 -0
- data/samples/app1/config/application.rb +40 -0
- data/samples/app1/config/boot.rb +4 -0
- data/samples/app1/config/credentials.yml.enc +1 -0
- data/samples/app1/config/database.yml +25 -0
- data/samples/app1/config/environment.rb +5 -0
- data/samples/app1/config/environments/development.rb +63 -0
- data/samples/app1/config/environments/production.rb +105 -0
- data/samples/app1/config/environments/test.rb +57 -0
- data/samples/app1/config/initializers/application_controller_renderer.rb +8 -0
- data/samples/app1/config/initializers/backtrace_silencers.rb +8 -0
- data/samples/app1/config/initializers/cors.rb +16 -0
- data/samples/app1/config/initializers/filter_parameter_logging.rb +6 -0
- data/samples/app1/config/initializers/inflections.rb +16 -0
- data/samples/app1/config/initializers/mime_types.rb +4 -0
- data/samples/app1/config/initializers/pubsub.rb +4 -0
- data/samples/app1/config/initializers/wrap_parameters.rb +14 -0
- data/samples/app1/config/locales/en.yml +33 -0
- data/samples/app1/config/puma.rb +43 -0
- data/samples/app1/config/routes.rb +3 -0
- data/samples/app1/config/spring.rb +6 -0
- data/samples/app1/db/migrate/20210513080700_create_users.rb +12 -0
- data/samples/app1/db/migrate/20210513134332_create_posts.rb +11 -0
- data/samples/app1/db/schema.rb +34 -0
- data/samples/app1/db/seeds.rb +7 -0
- data/samples/app1/docker-compose.yml +32 -0
- data/samples/app1/log/.keep +0 -0
- data/samples/app2/.gitattributes +8 -0
- data/samples/app2/.gitignore +28 -0
- data/samples/app2/Dockerfile +13 -0
- data/samples/app2/Gemfile +37 -0
- data/samples/app2/Gemfile.lock +171 -0
- data/samples/app2/README.md +24 -0
- data/samples/app2/Rakefile +6 -0
- data/samples/app2/app/models/application_record.rb +9 -0
- data/samples/app2/app/models/concerns/.keep +0 -0
- data/samples/app2/app/models/customer.rb +28 -0
- data/samples/app2/app/models/post.rb +10 -0
- data/samples/app2/bin/bundle +114 -0
- data/samples/app2/bin/rails +5 -0
- data/samples/app2/bin/rake +5 -0
- data/samples/app2/bin/setup +33 -0
- data/samples/app2/bin/spring +14 -0
- data/samples/app2/config.ru +6 -0
- data/samples/app2/config/application.rb +40 -0
- data/samples/app2/config/boot.rb +4 -0
- data/samples/app2/config/credentials.yml.enc +1 -0
- data/samples/app2/config/database.yml +25 -0
- data/samples/app2/config/environment.rb +5 -0
- data/samples/app2/config/environments/development.rb +63 -0
- data/samples/app2/config/environments/production.rb +105 -0
- data/samples/app2/config/environments/test.rb +57 -0
- data/samples/app2/config/initializers/application_controller_renderer.rb +8 -0
- data/samples/app2/config/initializers/backtrace_silencers.rb +8 -0
- data/samples/app2/config/initializers/cors.rb +16 -0
- data/samples/app2/config/initializers/filter_parameter_logging.rb +6 -0
- data/samples/app2/config/initializers/inflections.rb +16 -0
- data/samples/app2/config/initializers/mime_types.rb +4 -0
- data/samples/app2/config/initializers/pubsub.rb +4 -0
- data/samples/app2/config/initializers/wrap_parameters.rb +14 -0
- data/samples/app2/config/locales/en.yml +33 -0
- data/samples/app2/config/puma.rb +43 -0
- data/samples/app2/config/routes.rb +3 -0
- data/samples/app2/config/spring.rb +6 -0
- data/samples/app2/db/migrate/20210513080956_create_customers.rb +10 -0
- data/samples/app2/db/migrate/20210513135203_create_posts.rb +10 -0
- data/samples/app2/db/schema.rb +31 -0
- data/samples/app2/db/seeds.rb +7 -0
- data/samples/app2/docker-compose.yml +20 -0
- data/samples/app2/log/.keep +0 -0
- metadata +97 -3
- data/lib/pub_sub_model_sync/publisher.rb +0 -40
data/docker-compose.yaml
ADDED
Binary file
|
data/lib/pub_sub_model_sync.rb
CHANGED
@@ -10,10 +10,12 @@ require 'pub_sub_model_sync/subscriber_concern'
|
|
10
10
|
require 'pub_sub_model_sync/message_publisher'
|
11
11
|
require 'pub_sub_model_sync/publisher_concern'
|
12
12
|
require 'pub_sub_model_sync/runner'
|
13
|
+
require 'pub_sub_model_sync/transaction'
|
13
14
|
require 'pub_sub_model_sync/connector'
|
14
15
|
require 'pub_sub_model_sync/message_processor'
|
16
|
+
require 'pub_sub_model_sync/run_subscriber'
|
15
17
|
|
16
|
-
require 'pub_sub_model_sync/
|
18
|
+
require 'pub_sub_model_sync/payload_builder'
|
17
19
|
require 'pub_sub_model_sync/subscriber'
|
18
20
|
|
19
21
|
require 'pub_sub_model_sync/service_base'
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Base
|
5
|
-
delegate :config, :log, to: self
|
5
|
+
delegate :config, :log, :debug?, to: self
|
6
6
|
|
7
7
|
class << self
|
8
8
|
def config
|
@@ -12,13 +12,10 @@ module PubSubModelSync
|
|
12
12
|
def log(message, kind = :info)
|
13
13
|
config.log message, kind
|
14
14
|
end
|
15
|
-
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
rescue error_klass => _e
|
21
|
-
(retries += 1) <= qty ? retry : raise
|
16
|
+
def debug?
|
17
|
+
config.debug
|
18
|
+
end
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
@@ -3,12 +3,13 @@
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Config
|
5
5
|
cattr_accessor(:subscribers) { [] }
|
6
|
-
cattr_accessor(:publishers) { [] }
|
7
6
|
cattr_accessor(:service_name) { :google }
|
8
7
|
|
9
8
|
# customizable callbacks
|
10
9
|
cattr_accessor(:debug) { false }
|
11
10
|
cattr_accessor :logger # LoggerInst
|
11
|
+
cattr_accessor(:transactions_max_buffer) { 100 }
|
12
|
+
cattr_accessor(:enable_rails4_before_commit) { Rails::VERSION::MAJOR == 4 }
|
12
13
|
|
13
14
|
cattr_accessor(:on_before_processing) { ->(_payload, _info) {} } # return :cancel to skip
|
14
15
|
cattr_accessor(:on_success_processing) { ->(_payload, _info) {} }
|
@@ -16,29 +17,37 @@ module PubSubModelSync
|
|
16
17
|
cattr_accessor(:on_before_publish) { ->(_payload) {} } # return :cancel to skip
|
17
18
|
cattr_accessor(:on_after_publish) { ->(_payload) {} }
|
18
19
|
cattr_accessor(:on_error_publish) { ->(_exception, _info) {} }
|
19
|
-
cattr_accessor(:disabled_callback_publisher) { ->(_model, _action) { false } }
|
20
20
|
|
21
21
|
# google service
|
22
|
-
cattr_accessor :project, :credentials, :topic_name, :subscription_name
|
22
|
+
cattr_accessor :project, :credentials, :topic_name, :subscription_name, :default_topic_name
|
23
23
|
|
24
24
|
# rabbitmq service
|
25
|
-
cattr_accessor :bunny_connection, :
|
25
|
+
cattr_accessor :bunny_connection, :topic_name, :subscription_name, :default_topic_name
|
26
26
|
|
27
27
|
# kafka service
|
28
|
-
cattr_accessor :kafka_connection, :topic_name, :subscription_name
|
28
|
+
cattr_accessor :kafka_connection, :topic_name, :subscription_name, :default_topic_name
|
29
29
|
|
30
30
|
def self.log(msg, kind = :info)
|
31
31
|
msg = "PS_MSYNC ==> #{msg}"
|
32
32
|
if logger == :raise_error
|
33
|
-
kind == :error ? raise(msg) : puts(msg)
|
33
|
+
kind == :error ? raise(StandardError, msg) : puts(msg)
|
34
34
|
else
|
35
35
|
logger ? logger.send(kind, msg) : puts(msg)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.subscription_key
|
40
|
-
|
41
|
-
|
40
|
+
klass = Rails.application.class
|
41
|
+
app_name = klass.respond_to?(:module_parent_name) ? klass.module_parent_name : klass.parent_name
|
42
|
+
subscription_name || app_name
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
alias default_topic_name_old default_topic_name
|
47
|
+
|
48
|
+
def default_topic_name
|
49
|
+
default_topic_name_old || Array(topic_name).first
|
50
|
+
end
|
42
51
|
end
|
43
52
|
end
|
44
53
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Rails 4 backward compatibility (Add "simple" ps_before_*_commit callbacks)
|
4
|
+
ActiveRecord::ConnectionAdapters::RealTransaction.class_eval do
|
5
|
+
alias_method :commit_without_before_commit, :commit
|
6
|
+
|
7
|
+
def commit
|
8
|
+
call_before_commit_records if PubSubModelSync::Config.enable_rails4_before_commit
|
9
|
+
commit_without_before_commit
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def call_before_commit_records
|
15
|
+
ite = records.uniq
|
16
|
+
ite.each do |record|
|
17
|
+
action = record.previous_changes.include?(:id) ? :create : :update
|
18
|
+
action = :destroy if record.destroyed?
|
19
|
+
callback_name = "ps_before_#{action}_commit".to_sym
|
20
|
+
record.send(callback_name) if record.respond_to?(callback_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -16,25 +16,29 @@ module PubSubModelSync
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def process!
|
19
|
-
filter_subscribers
|
19
|
+
subscribers = filter_subscribers
|
20
|
+
payload_info = { klass: payload.klass, action: payload.action, mode: payload.mode }
|
21
|
+
log("No subscribers found for #{payload_info}", :warn) if config.debug && subscribers.empty?
|
22
|
+
subscribers.each(&method(:run_subscriber))
|
20
23
|
end
|
21
24
|
|
22
25
|
def process
|
26
|
+
retries ||= 0
|
23
27
|
process!
|
24
28
|
rescue => e
|
25
|
-
notify_error(e)
|
29
|
+
retry_process?(e, retries += 1) ? retry : notify_error(e)
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
34
|
def run_subscriber(subscriber)
|
35
|
+
processor = PubSubModelSync::RunSubscriber.new(subscriber, payload)
|
31
36
|
return unless processable?(subscriber)
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
+
log("Processing message #{[subscriber, payload]}...") if config.debug
|
39
|
+
processor.call
|
40
|
+
res = config.on_success_processing.call(payload, { subscriber: subscriber })
|
41
|
+
log "processed message with: #{payload.inspect}" if res != :skip_log
|
38
42
|
end
|
39
43
|
|
40
44
|
def processable?(subscriber)
|
@@ -43,17 +47,37 @@ module PubSubModelSync
|
|
43
47
|
!cancel
|
44
48
|
end
|
45
49
|
|
46
|
-
# @param error (
|
50
|
+
# @param error (StandardError)
|
47
51
|
def notify_error(error)
|
48
52
|
info = [payload, error.message, error.backtrace]
|
49
53
|
res = config.on_error_processing.call(error, { payload: payload })
|
50
54
|
log("Error processing message: #{info}", :error) if res != :skip_log
|
51
55
|
end
|
52
56
|
|
57
|
+
def lost_db_connection?(error)
|
58
|
+
connection_lost_classes = %w[ActiveRecord::ConnectionTimeoutError PG::UnableToSend]
|
59
|
+
connection_lost_classes.include?(error.class.name) || error.message.match?(/lost connection/i)
|
60
|
+
end
|
61
|
+
|
62
|
+
def retry_process?(error, retries) # rubocop:disable Metrics/MethodLength
|
63
|
+
error_payload = [payload, error.message, error.backtrace]
|
64
|
+
return false unless lost_db_connection?(error)
|
65
|
+
|
66
|
+
if retries <= 5
|
67
|
+
sleep(retries)
|
68
|
+
log("Error processing message: (retrying #{retries}/5): #{error_payload}", :error)
|
69
|
+
ActiveRecord::Base.connection.reconnect! rescue nil # rubocop:disable Style/RescueModifier
|
70
|
+
true
|
71
|
+
else
|
72
|
+
log("Retried 5 times and error persists, exiting...: #{error_payload}", :error)
|
73
|
+
Process.exit!(true)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return (Array<PubSubModelSync::Subscriber>)
|
53
78
|
def filter_subscribers
|
54
79
|
config.subscribers.select do |subscriber|
|
55
|
-
subscriber.
|
56
|
-
subscriber.settings[:from_action].to_s == payload.action.to_s
|
80
|
+
subscriber.from_klass == payload.klass && subscriber.action == payload.action && payload.mode == subscriber.mode
|
57
81
|
end
|
58
82
|
end
|
59
83
|
end
|
@@ -3,65 +3,126 @@
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class MessagePublisher < PubSubModelSync::Base
|
5
5
|
class << self
|
6
|
+
class MissingPublisher < StandardError; end
|
7
|
+
attr_accessor :current_transaction
|
8
|
+
|
6
9
|
def connector
|
7
10
|
@connector ||= PubSubModelSync::Connector.new
|
8
11
|
end
|
9
12
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
# Permits to group all payloads with the same ordering_key and be processed in the same order
|
14
|
+
# they are published by the subscribers. Grouping by ordering_key allows us to enable
|
15
|
+
# multiple workers in our Pub/Sub service(s), and still guarantee that related payloads will
|
16
|
+
# be processed in the correct order, despite of the multiple threads. This thanks to the fact
|
17
|
+
# that Pub/Sub services will always send messages with the same `ordering_key` into the same
|
18
|
+
# worker/thread.
|
19
|
+
# @see Transaction.new(...)
|
20
|
+
# @param key (String|Nil)
|
21
|
+
# @param block (Yield) block to be executed
|
22
|
+
def transaction(key, settings = {}, &block)
|
23
|
+
t = init_transaction(key, settings)
|
24
|
+
block.call
|
25
|
+
t.finish
|
26
|
+
rescue
|
27
|
+
t.rollback
|
28
|
+
raise
|
29
|
+
ensure
|
30
|
+
t.clean_publisher
|
18
31
|
end
|
19
32
|
|
20
|
-
#
|
21
|
-
# @param
|
22
|
-
# @
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
# Starts a new transaction
|
34
|
+
# @param key (String|Nil)
|
35
|
+
# @return (Transaction)
|
36
|
+
def init_transaction(key, settings = {})
|
37
|
+
new_transaction = PubSubModelSync::Transaction.new(key, settings)
|
38
|
+
if current_transaction
|
39
|
+
current_transaction.add_transaction(new_transaction)
|
40
|
+
else
|
41
|
+
self.current_transaction = new_transaction
|
42
|
+
end
|
43
|
+
new_transaction
|
44
|
+
end
|
31
45
|
|
46
|
+
# Publishes a class level notification via pubsub
|
47
|
+
# @refer PublisherConcern.ps_class_publish
|
48
|
+
# @return Payload
|
49
|
+
def publish_data(klass, data, action, headers: {})
|
50
|
+
info = { klass: klass.to_s, action: action.to_sym, mode: :klass }
|
51
|
+
log("Building payload for: #{info.inspect}") if config.debug
|
52
|
+
payload = PubSubModelSync::Payload.new(data, info, headers)
|
53
|
+
define_transaction_key(payload)
|
32
54
|
publish(payload)
|
33
|
-
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param model (ActiveRecord::Base)
|
58
|
+
# @param action (Symbol: @see PublishConcern::ps_publish)
|
59
|
+
# @param settings (Hash: @see PayloadBuilder.settings)
|
60
|
+
def publish_model(model, action, settings = {})
|
61
|
+
log("Building payload for: #{[model, action].inspect}") if config.debug
|
62
|
+
payload = PubSubModelSync::PayloadBuilder.new(model, action, settings).call
|
63
|
+
define_transaction_key(payload)
|
64
|
+
transaction(payload.headers[:ordering_key]) do # catch and group all :ps_before_publish syncs
|
65
|
+
publish(payload) { model.ps_after_publish(action, payload) } if ensure_model_publish(model, action, payload)
|
66
|
+
end
|
34
67
|
end
|
35
68
|
|
36
69
|
# Publishes payload to pubsub
|
37
|
-
# @
|
70
|
+
# @param payload (PubSubModelSync::Payload)
|
71
|
+
# @return Payload
|
38
72
|
# Raises error if exist
|
39
|
-
def publish!(payload)
|
40
|
-
|
41
|
-
|
42
|
-
return
|
43
|
-
end
|
73
|
+
def publish!(payload, &block)
|
74
|
+
payload.headers[:ordering_key] = ordering_key_for(payload)
|
75
|
+
return unless ensure_publish(payload)
|
44
76
|
|
45
|
-
|
77
|
+
current_transaction ? current_transaction.add_payload(payload) : connector_publish(payload)
|
78
|
+
block&.call
|
79
|
+
payload
|
80
|
+
end
|
81
|
+
|
82
|
+
def connector_publish(payload)
|
83
|
+
log("Publishing message #{payload.inspect}...") if config.debug
|
46
84
|
connector.publish(payload)
|
85
|
+
log("Published message: #{[payload]}")
|
47
86
|
config.on_after_publish.call(payload)
|
48
87
|
end
|
49
88
|
|
50
89
|
# Similar to :publish! method
|
51
90
|
# Notifies error via :on_error_publish instead of raising error
|
52
|
-
|
53
|
-
|
91
|
+
# @return Payload
|
92
|
+
def publish(payload, &block)
|
93
|
+
publish!(payload, &block)
|
54
94
|
rescue => e
|
55
95
|
notify_error(e, payload)
|
56
96
|
end
|
57
97
|
|
58
98
|
private
|
59
99
|
|
100
|
+
def ensure_publish(payload)
|
101
|
+
cancelled = config.on_before_publish.call(payload) == :cancel
|
102
|
+
log("Publish cancelled by config.on_before_publish: #{payload}") if config.debug && cancelled
|
103
|
+
!cancelled
|
104
|
+
end
|
105
|
+
|
106
|
+
def ordering_key_for(payload)
|
107
|
+
payload.headers[:forced_ordering_key] || current_transaction&.key || payload.headers[:ordering_key]
|
108
|
+
end
|
109
|
+
|
110
|
+
def ensure_model_publish(model, action, payload)
|
111
|
+
res_before = model.ps_before_publish(action, payload)
|
112
|
+
cancelled = res_before == :cancel
|
113
|
+
log("Publish cancelled by model.ps_before_publish: #{payload}") if config.debug && cancelled
|
114
|
+
!cancelled
|
115
|
+
end
|
116
|
+
|
60
117
|
def notify_error(exception, payload)
|
61
118
|
info = [payload, exception.message, exception.backtrace]
|
62
119
|
res = config.on_error_publish.call(exception, { payload: payload })
|
63
120
|
log("Error publishing: #{info}", :error) if res != :skip_log
|
64
121
|
end
|
122
|
+
|
123
|
+
def define_transaction_key(payload)
|
124
|
+
current_transaction&.key ||= payload.headers[:ordering_key]
|
125
|
+
end
|
65
126
|
end
|
66
127
|
end
|
67
128
|
end
|
@@ -28,16 +28,29 @@ module PubSubModelSync
|
|
28
28
|
def subscribe(*_args)
|
29
29
|
true
|
30
30
|
end
|
31
|
+
|
32
|
+
def mark_message_as_processed(*_args)
|
33
|
+
true
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
def producer(*_args)
|
34
38
|
MockProducer.new
|
35
39
|
end
|
40
|
+
alias async_producer producer
|
36
41
|
|
37
42
|
def consumer(*_args)
|
38
43
|
MockConsumer.new
|
39
44
|
end
|
40
45
|
|
46
|
+
def topics
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_topic(_name)
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
41
54
|
def close
|
42
55
|
true
|
43
56
|
end
|
@@ -3,30 +3,48 @@
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Payload
|
5
5
|
class MissingInfo < StandardError; end
|
6
|
-
attr_reader :data, :
|
6
|
+
attr_reader :data, :info, :headers
|
7
7
|
|
8
8
|
# @param data (Hash: { any value }):
|
9
|
-
# @param
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
# @param info (Hash):
|
10
|
+
# klass: (String, required) Notification class name
|
11
|
+
# action: (Symbol, required) Notification action name
|
12
|
+
# mode: (:model|:klass, default :model): :model for instance and :klass for class notifications
|
13
|
+
# @param headers (Hash):
|
14
|
+
# key (String): identifier of the payload, default:
|
15
|
+
# <klass/action>: when class message
|
16
|
+
# <klass/action/model.id>: when model message
|
17
|
+
# ordering_key (String): messages with the same key are processed in the same order they
|
18
|
+
# were delivered, default:
|
19
|
+
# <klass>: when class message
|
20
|
+
# <klass/id>: when model message
|
21
|
+
# topic_name (String|Array<String>): Specific topic name to be used when delivering the
|
22
|
+
# message (default first topic)
|
23
|
+
# forced_ordering_key (String, optional): Will force to use this value as the ordering_key,
|
24
|
+
# even withing transactions. Default nil.
|
25
|
+
def initialize(data, info, headers = {})
|
26
|
+
@data = data.deep_symbolize_keys
|
27
|
+
@info = info.deep_symbolize_keys
|
28
|
+
@headers = headers.deep_symbolize_keys
|
15
29
|
build_headers
|
16
30
|
validate!
|
17
31
|
end
|
18
32
|
|
19
33
|
# @return Hash: payload data
|
20
34
|
def to_h
|
21
|
-
{ data: data,
|
35
|
+
{ data: data.clone, info: info.clone, headers: headers.clone }
|
22
36
|
end
|
23
37
|
|
24
38
|
def klass
|
25
|
-
|
39
|
+
info[:klass].to_s
|
26
40
|
end
|
27
41
|
|
28
42
|
def action
|
29
|
-
|
43
|
+
info[:action].to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
def mode
|
47
|
+
(info[:mode] || :model).to_sym
|
30
48
|
end
|
31
49
|
|
32
50
|
# Process payload data
|
@@ -58,22 +76,23 @@ module PubSubModelSync
|
|
58
76
|
end
|
59
77
|
|
60
78
|
# convert payload data into Payload
|
61
|
-
# @param data [Hash]: payload data (:data, :
|
79
|
+
# @param data [Hash]: payload data (:data, :info, :headers)
|
62
80
|
def self.from_payload_data(data)
|
63
|
-
data = data.
|
64
|
-
new(data[:data], data[:attributes], data[:headers])
|
81
|
+
data = data.symbolize_keys
|
82
|
+
new(data[:data], data[:info] || data[:attributes], data[:headers])
|
65
83
|
end
|
66
84
|
|
67
85
|
private
|
68
86
|
|
69
87
|
def build_headers
|
70
|
-
headers[:uuid] ||= SecureRandom.uuid
|
71
88
|
headers[:app_key] ||= PubSubModelSync::Config.subscription_key
|
72
|
-
headers[:key] ||= [klass
|
89
|
+
headers[:key] ||= [klass, action].join('/')
|
90
|
+
headers[:ordering_key] ||= klass
|
91
|
+
headers[:uuid] ||= SecureRandom.uuid
|
73
92
|
end
|
74
93
|
|
75
94
|
def validate!
|
76
|
-
raise MissingInfo if !
|
95
|
+
raise MissingInfo if !info[:klass] || !info[:action]
|
77
96
|
end
|
78
97
|
end
|
79
98
|
end
|