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
@@ -7,14 +7,20 @@ end
|
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceKafka < ServiceBase
|
10
|
+
QTY_WORKERS = 10
|
11
|
+
LISTEN_SETTINGS = {}.freeze
|
12
|
+
PUBLISH_SETTINGS = {}.freeze
|
13
|
+
PRODUCER_SETTINGS = { delivery_threshold: 200, delivery_interval: 30 }.freeze
|
10
14
|
cattr_accessor :producer
|
11
|
-
|
15
|
+
|
16
|
+
# @!attribute topic_names (Array): ['topic 1', 'topic 2']
|
17
|
+
attr_accessor :service, :consumer, :topic_names
|
12
18
|
|
13
19
|
def initialize
|
14
|
-
@config = PubSubModelSync::Config
|
15
20
|
settings = config.kafka_connection
|
16
21
|
settings[1][:client_id] ||= config.subscription_key
|
17
22
|
@service = Kafka.new(*settings)
|
23
|
+
@topic_names = ensure_topics(Array(config.topic_name || 'model_sync'))
|
18
24
|
end
|
19
25
|
|
20
26
|
def listen_messages
|
@@ -28,12 +34,10 @@ module PubSubModelSync
|
|
28
34
|
end
|
29
35
|
|
30
36
|
def publish(payload)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
producer.produce(payload.to_json, settings)
|
36
|
-
producer.deliver_messages
|
37
|
+
message_topics = Array(payload.headers[:topic_name] || config.default_topic_name)
|
38
|
+
message_topics.each do |topic_name|
|
39
|
+
producer.produce(encode_payload(payload), message_settings(payload, topic_name))
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
def stop
|
@@ -43,22 +47,45 @@ module PubSubModelSync
|
|
43
47
|
|
44
48
|
private
|
45
49
|
|
50
|
+
def message_settings(payload, topic_name)
|
51
|
+
{
|
52
|
+
topic: ensure_topics(topic_name),
|
53
|
+
partition_key: payload.headers[:ordering_key],
|
54
|
+
headers: { SERVICE_KEY => true }
|
55
|
+
}.merge(PUBLISH_SETTINGS)
|
56
|
+
end
|
57
|
+
|
46
58
|
def start_consumer
|
47
|
-
|
48
|
-
consumer.
|
59
|
+
subscription_key = config.subscription_key
|
60
|
+
@consumer = service.consumer(group_id: subscription_key)
|
61
|
+
topic_names.each do |topic_name|
|
62
|
+
log("Subscribed to topic: #{topic_name} as #{subscription_key}")
|
63
|
+
consumer.subscribe(topic_name)
|
64
|
+
end
|
49
65
|
end
|
50
66
|
|
51
67
|
def producer
|
52
68
|
return self.class.producer if self.class.producer
|
53
69
|
|
54
70
|
at_exit { self.class.producer.shutdown }
|
55
|
-
self.class.producer = service.
|
71
|
+
self.class.producer = service.async_producer(PRODUCER_SETTINGS)
|
56
72
|
end
|
57
73
|
|
58
74
|
def process_message(message)
|
59
|
-
|
75
|
+
super(message.value) if message.headers[SERVICE_KEY]
|
76
|
+
end
|
60
77
|
|
61
|
-
|
78
|
+
# Check topic existence, create if missing topic
|
79
|
+
# @param names (Array<String>|String)
|
80
|
+
# @return (Array|String) return @param names
|
81
|
+
def ensure_topics(names)
|
82
|
+
missing_topics = Array(names) - (@known_topics || service.topics)
|
83
|
+
missing_topics.each do |name|
|
84
|
+
service.create_topic(name)
|
85
|
+
end
|
86
|
+
@known_topics ||= [] # cache service.topics to reduce verification time
|
87
|
+
@known_topics = (@known_topics + Array(names)).uniq
|
88
|
+
names
|
62
89
|
end
|
63
90
|
end
|
64
91
|
end
|
@@ -7,18 +7,26 @@ end
|
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceRabbit < ServiceBase
|
10
|
-
|
10
|
+
QUEUE_SETTINGS = { durable: true, auto_delete: false }.freeze
|
11
|
+
LISTEN_SETTINGS = { manual_ack: false }.freeze
|
12
|
+
PUBLISH_SETTINGS = {}.freeze
|
13
|
+
|
14
|
+
# @!attribute topic_names (Array): ['Topic 1', 'Topic 2']
|
15
|
+
# @!attribute channels (Array): [Channel1]
|
16
|
+
# @!attribute exchanges (Hash<key: Exchange>): {topic_name: Exchange1}
|
17
|
+
attr_accessor :service, :topic_names, :channels, :exchanges
|
11
18
|
|
12
19
|
def initialize
|
13
|
-
@config = PubSubModelSync::Config
|
14
20
|
@service = Bunny.new(*config.bunny_connection)
|
21
|
+
@topic_names = Array(config.topic_name || 'model_sync')
|
22
|
+
@channels = []
|
23
|
+
@exchanges = {}
|
15
24
|
end
|
16
25
|
|
17
26
|
def listen_messages
|
18
27
|
log('Listener starting...')
|
19
|
-
|
28
|
+
subscribe_to_queues { |queue| queue.subscribe(LISTEN_SETTINGS, &method(:process_message)) }
|
20
29
|
log('Listener started')
|
21
|
-
queue.subscribe(subscribe_settings, &method(:process_message))
|
22
30
|
loop { sleep 5 }
|
23
31
|
rescue PubSubModelSync::Runner::ShutDown
|
24
32
|
log('Listener stopped')
|
@@ -40,54 +48,54 @@ module PubSubModelSync
|
|
40
48
|
|
41
49
|
def stop
|
42
50
|
log('Listener stopping...')
|
43
|
-
|
51
|
+
channels.each(&:close)
|
44
52
|
service.close
|
45
53
|
end
|
46
54
|
|
47
55
|
private
|
48
56
|
|
49
|
-
def message_settings
|
57
|
+
def message_settings(payload)
|
50
58
|
{
|
51
|
-
routing_key:
|
59
|
+
routing_key: payload.headers[:ordering_key],
|
52
60
|
type: SERVICE_KEY,
|
53
61
|
persistent: true
|
54
62
|
}.merge(PUBLISH_SETTINGS)
|
55
63
|
end
|
56
64
|
|
57
|
-
def queue_settings
|
58
|
-
{ durable: true, auto_delete: false }
|
59
|
-
end
|
60
|
-
|
61
|
-
def subscribe_settings
|
62
|
-
{ manual_ack: false }.merge(LISTEN_SETTINGS)
|
63
|
-
end
|
64
|
-
|
65
65
|
def process_message(_delivery_info, meta_info, payload)
|
66
|
-
|
67
|
-
|
68
|
-
super(payload)
|
66
|
+
super(payload) if meta_info[:type] == SERVICE_KEY
|
69
67
|
end
|
70
68
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
69
|
+
def subscribe_to_queues(&block)
|
70
|
+
@channels = []
|
71
|
+
topic_names.each do |topic_name|
|
72
|
+
subscribe_to_exchange(topic_name) do |channel, exchange|
|
73
|
+
queue = channel.queue(config.subscription_key, QUEUE_SETTINGS)
|
74
|
+
queue.bind(exchange)
|
75
|
+
@channels << channel
|
76
|
+
log("Subscribed to topic: #{topic_name} as #{queue.name}")
|
77
|
+
block.call(queue)
|
78
|
+
end
|
79
|
+
end
|
76
80
|
end
|
77
81
|
|
78
|
-
def subscribe_to_exchange
|
79
|
-
|
80
|
-
|
82
|
+
def subscribe_to_exchange(topic_name, &block)
|
83
|
+
topic_name = topic_name.to_s
|
84
|
+
exchanges[topic_name] ||= begin
|
85
|
+
service.start
|
86
|
+
channel = service.create_channel
|
87
|
+
channel.fanout(topic_name)
|
88
|
+
end
|
89
|
+
block.call(channel, exchanges[topic_name])
|
81
90
|
end
|
82
91
|
|
83
92
|
def deliver_data(payload)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
service.close
|
93
|
+
message_topics = Array(payload.headers[:topic_name] || config.default_topic_name)
|
94
|
+
message_topics.each do |topic_name|
|
95
|
+
subscribe_to_exchange(topic_name) do |_channel, exchange|
|
96
|
+
exchange.publish(encode_payload(payload), message_settings(payload))
|
97
|
+
end
|
98
|
+
end
|
91
99
|
end
|
92
100
|
end
|
93
101
|
end
|
@@ -1,74 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
|
-
class Subscriber
|
5
|
-
attr_accessor :klass, :action, :
|
6
|
-
attr_reader :payload
|
7
|
-
|
8
|
-
# @param
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
class Subscriber < PubSubModelSync::Base
|
5
|
+
attr_accessor :klass, :action, :mapping, :settings, :from_klass, :mode
|
6
|
+
attr_reader :payload, :model
|
7
|
+
|
8
|
+
# @param klass (String) class name
|
9
|
+
# @param action (Symbol) @refer SubscriberConcern.ps_subscribe
|
10
|
+
# @param mapping (Array<String>) @refer SubscriberConcern.ps_subscribe
|
11
|
+
# @param settings (Hash): @refer SubscriberConcern.ps_subscribe
|
12
|
+
def initialize(klass, action, mapping: [], settings: {})
|
13
|
+
def_settings = { from_klass: klass, to_action: action, id: :id, if: nil, unless: nil, mode: :model }
|
13
14
|
@klass = klass
|
14
|
-
@
|
15
|
-
@attrs = attrs
|
15
|
+
@mapping = mapping
|
16
16
|
@settings = def_settings.merge(settings)
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
def process!(payload)
|
21
|
-
@payload = payload
|
22
|
-
if settings[:direct_mode]
|
23
|
-
run_class_message
|
24
|
-
else
|
25
|
-
run_model_message
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def run_class_message
|
32
|
-
model_class = klass.constantize
|
33
|
-
model_class.send(action, payload.data)
|
34
|
-
end
|
35
|
-
|
36
|
-
# support for: create, update, destroy
|
37
|
-
def run_model_message
|
38
|
-
model = find_model
|
39
|
-
model.ps_processed_payload = payload
|
40
|
-
|
41
|
-
if action == :destroy
|
42
|
-
model.destroy! if ensure_sync(model)
|
43
|
-
else
|
44
|
-
populate_model(model)
|
45
|
-
model.save! if ensure_sync(model)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def ensure_sync(model)
|
50
|
-
config = PubSubModelSync::Config
|
51
|
-
cancelled = model.ps_before_save_sync(payload) == :cancel
|
52
|
-
config.log("Cancelled sync with ps_before_save_sync: #{[payload]}") if cancelled && config.debug
|
53
|
-
!cancelled
|
54
|
-
end
|
55
|
-
|
56
|
-
def find_model
|
57
|
-
model_class = klass.constantize
|
58
|
-
return model_class.ps_find_model(payload.data) if model_class.respond_to?(:ps_find_model)
|
59
|
-
|
60
|
-
model_class.where(model_identifiers).first_or_initialize
|
61
|
-
end
|
62
|
-
|
63
|
-
def model_identifiers
|
64
|
-
identifiers.map { |key| [key, payload.data[key.to_sym]] }.to_h
|
65
|
-
end
|
66
|
-
|
67
|
-
def populate_model(model)
|
68
|
-
values = payload.data.slice(*attrs).except(*identifiers)
|
69
|
-
values.each do |attr, value|
|
70
|
-
model.send("#{attr}=", value)
|
71
|
-
end
|
17
|
+
@action = action.to_sym
|
18
|
+
@from_klass = @settings[:from_klass].to_s
|
19
|
+
@mode = @settings[:mode].to_sym
|
72
20
|
end
|
73
21
|
end
|
74
22
|
end
|
@@ -4,41 +4,41 @@ module PubSubModelSync
|
|
4
4
|
module SubscriberConcern
|
5
5
|
def self.included(base)
|
6
6
|
base.extend(ClassMethods)
|
7
|
-
base.send(:attr_accessor, :
|
7
|
+
base.send(:attr_accessor, :ps_processing_payload)
|
8
|
+
base.send(:cattr_accessor, :ps_processing_payload)
|
8
9
|
end
|
9
10
|
|
10
|
-
# permit to apply custom actions before applying sync
|
11
|
-
# @return (nil|:cancel): nil to continue sync OR :cancel to skip sync
|
12
|
-
def ps_before_save_sync(_payload); end
|
13
|
-
|
14
11
|
module ClassMethods
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
# @param actions (Symbol|Array<Symbol>) Notification.action name: save|create|update|destroy|<any_other_action>
|
13
|
+
# @param mapping (Array<String,Symbol>) Attributes mapping with aliasing support, sample: ["id", "full_name:name"]
|
14
|
+
# @param settings (Hash<:from_klass, :to_action, :id, :if, :unless>)
|
15
|
+
# from_klass (String) Notification.class name
|
16
|
+
# to_action (Symbol|Proc):
|
17
|
+
# Symbol: Method to process the notification
|
18
|
+
# Proc: Block to process the notification
|
19
|
+
# id (Symbol|Array<Symbol|String>) attribute(s) DB primary identifier(s). Supports for mapping format.
|
20
|
+
# if (Symbol|Proc|Array<Symbol>) Method or block called as the conformation before calling the callback
|
21
|
+
# unless (Symbol|Proc|Array<Symbol>) Method or block called as the negation before calling the callback
|
22
|
+
def ps_subscribe(actions, mapping = [], settings = {}, &block)
|
23
|
+
settings[:to_action] ||= block if block
|
24
|
+
Array(actions).map do |action|
|
25
|
+
add_ps_subscriber(action, mapping, settings)
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
settings[:
|
27
|
-
add_ps_subscriber(action, nil, settings)
|
28
|
-
end
|
29
|
-
|
30
|
-
def ps_subscriber(action = :create)
|
31
|
-
PubSubModelSync::Config.subscribers.find do |subscriber|
|
32
|
-
subscriber.klass == name && subscriber.action == action
|
33
|
-
end
|
29
|
+
# @param action (Symbol) Notification.action name
|
30
|
+
# @param settings (Hash) @refer ps_subscribe.settings except(:id)
|
31
|
+
def ps_class_subscribe(action, settings = {}, &block)
|
32
|
+
settings[:to_action] ||= block if block
|
33
|
+
add_ps_subscriber(action, nil, settings.merge(mode: :klass))
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
38
|
# @param settings (Hash): refer to PubSubModelSync::Subscriber.settings
|
39
|
-
def add_ps_subscriber(action,
|
39
|
+
def add_ps_subscriber(action, mapping, settings = {})
|
40
40
|
klass = PubSubModelSync::Subscriber
|
41
|
-
subscriber = klass.new(name, action,
|
41
|
+
subscriber = klass.new(name, action, mapping: mapping, settings: settings)
|
42
42
|
PubSubModelSync::Config.subscribers.push(subscriber) && subscriber
|
43
43
|
end
|
44
44
|
end
|
@@ -3,6 +3,17 @@
|
|
3
3
|
namespace :pub_sub_model_sync do
|
4
4
|
desc 'Start listening syncs'
|
5
5
|
task start: :environment do
|
6
|
+
# https://github.com/zendesk/ruby-kafka#consumer-groups
|
7
|
+
# Each consumer process will be assigned one or more partitions from each topic that the group
|
8
|
+
# subscribes to. In order to handle more messages, simply start more processes.
|
9
|
+
if PubSubModelSync::Config.service_name == :kafka
|
10
|
+
(PubSubModelSync::ServiceKafka::QTY_WORKERS - 1).times.each do
|
11
|
+
Thread.new do
|
12
|
+
Thread.current.abort_on_exception = true
|
13
|
+
PubSubModelSync::Runner.new.run
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
6
17
|
PubSubModelSync::Runner.new.run
|
7
18
|
end
|
8
19
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class Transaction < Base
|
5
|
+
PUBLISHER_KLASS = PubSubModelSync::MessagePublisher
|
6
|
+
attr_accessor :key, :payloads, :max_buffer, :root, :children, :finished
|
7
|
+
|
8
|
+
# @param key (String|nil) Transaction key, if empty will use the ordering_key from first payload
|
9
|
+
# @param max_buffer (Integer) Once this quantity of notifications is reached, then all notifications
|
10
|
+
# will immediately be delivered.
|
11
|
+
# Note: There is no way to rollback delivered notifications if current transaction fails
|
12
|
+
def initialize(key, max_buffer: config.transactions_max_buffer)
|
13
|
+
@key = key
|
14
|
+
@max_buffer = max_buffer
|
15
|
+
@children = []
|
16
|
+
@payloads = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param payload (Payload)
|
20
|
+
def add_payload(payload)
|
21
|
+
payloads << payload
|
22
|
+
log("Payload added to current transaction: #{payload.inspect}") if config.debug
|
23
|
+
return unless payloads.count >= max_buffer
|
24
|
+
|
25
|
+
log("Payloads buffer was filled, delivering current payloads: #{payloads.count}")
|
26
|
+
deliver_payloads
|
27
|
+
end
|
28
|
+
|
29
|
+
def finish # rubocop:disable Metrics/AbcSize
|
30
|
+
if root
|
31
|
+
root.children = root.children.reject { |t| t == self }
|
32
|
+
root.deliver_all if root.finished && root.children.empty?
|
33
|
+
end
|
34
|
+
self.finished = true
|
35
|
+
deliver_all if children.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_transaction(transaction)
|
39
|
+
transaction.root = self
|
40
|
+
children << transaction
|
41
|
+
transaction
|
42
|
+
end
|
43
|
+
|
44
|
+
def rollback
|
45
|
+
log("Rollback #{payloads.count} notifications", :warn) if children.any? && debug?
|
46
|
+
self.children = []
|
47
|
+
root&.rollback
|
48
|
+
clean_publisher
|
49
|
+
end
|
50
|
+
|
51
|
+
def clean_publisher
|
52
|
+
PUBLISHER_KLASS.current_transaction = nil if !root && children.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def deliver_all
|
56
|
+
deliver_payloads
|
57
|
+
clean_publisher
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def deliver_payloads
|
63
|
+
payloads.each do |payload|
|
64
|
+
begin # rubocop:disable Style/RedundantBegin (ruby 2.4 support)
|
65
|
+
PUBLISHER_KLASS.connector_publish(payload)
|
66
|
+
rescue => e
|
67
|
+
PUBLISHER_KLASS.send(:notify_error, e, payload)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
self.payloads = []
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|