pub_sub_model_sync 0.4.0 → 0.5.0.1
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/ruby.yml +30 -8
- data/.rubocop.yml +6 -1
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +25 -23
- data/README.md +55 -29
- data/gemfiles/Gemfile_4 +16 -0
- data/gemfiles/Gemfile_5 +14 -0
- data/gemfiles/Gemfile_6 +14 -0
- data/lib/pub_sub_model_sync.rb +1 -0
- data/lib/pub_sub_model_sync/base.rb +17 -0
- data/lib/pub_sub_model_sync/config.rb +18 -3
- data/lib/pub_sub_model_sync/connector.rb +1 -0
- data/lib/pub_sub_model_sync/message_processor.rb +27 -21
- data/lib/pub_sub_model_sync/message_publisher.rb +25 -9
- data/lib/pub_sub_model_sync/mock_rabbit_service.rb +5 -0
- data/lib/pub_sub_model_sync/payload.rb +45 -0
- data/lib/pub_sub_model_sync/publisher.rb +1 -0
- data/lib/pub_sub_model_sync/publisher_concern.rb +3 -1
- data/lib/pub_sub_model_sync/service_base.rb +25 -13
- data/lib/pub_sub_model_sync/service_google.rb +4 -18
- data/lib/pub_sub_model_sync/service_kafka.rb +4 -17
- data/lib/pub_sub_model_sync/service_rabbit.rb +20 -27
- data/lib/pub_sub_model_sync/subscriber.rb +3 -3
- data/lib/pub_sub_model_sync/subscriber_concern.rb +6 -0
- data/lib/pub_sub_model_sync/version.rb +1 -1
- data/pub_sub_model_sync.gemspec +1 -1
- metadata +11 -29
- data/.idea/.gitignore +0 -8
- data/.idea/.rakeTasks +0 -7
- data/.idea/codeStyles/codeStyleConfig.xml +0 -5
- data/.idea/encodings.xml +0 -4
- data/.idea/inspectionProfiles/Project_Default.xml +0 -16
- data/.idea/misc.xml +0 -7
- data/.idea/modules.xml +0 -8
- data/.idea/pub_sub_model_sync.iml +0 -96
- data/.idea/vcs.xml +0 -6
data/gemfiles/Gemfile_5
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem 'rubocop'
|
4
|
+
gem 'bunny' # rabbit-mq
|
5
|
+
gem 'google-cloud-pubsub' # google pub/sub
|
6
|
+
gem 'ruby-kafka' # kafka pub/sub
|
7
|
+
gem 'rails', '~> 5'
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'database_cleaner-active_record'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Specify your gem's dependencies in pub_sub_model_sync.gemspec
|
14
|
+
gemspec
|
data/gemfiles/Gemfile_6
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem 'rubocop'
|
4
|
+
gem 'bunny' # rabbit-mq
|
5
|
+
gem 'google-cloud-pubsub' # google pub/sub
|
6
|
+
gem 'ruby-kafka' # kafka pub/sub
|
7
|
+
gem 'rails', '~> 6'
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'database_cleaner-active_record'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Specify your gem's dependencies in pub_sub_model_sync.gemspec
|
14
|
+
gemspec
|
data/lib/pub_sub_model_sync.rb
CHANGED
@@ -5,6 +5,7 @@ require 'active_support'
|
|
5
5
|
|
6
6
|
require 'pub_sub_model_sync/railtie'
|
7
7
|
require 'pub_sub_model_sync/config'
|
8
|
+
require 'pub_sub_model_sync/base'
|
8
9
|
require 'pub_sub_model_sync/subscriber_concern'
|
9
10
|
require 'pub_sub_model_sync/message_publisher'
|
10
11
|
require 'pub_sub_model_sync/publisher_concern'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class Base
|
5
|
+
delegate :config, :log, to: self
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def config
|
9
|
+
PubSubModelSync::Config
|
10
|
+
end
|
11
|
+
|
12
|
+
def log(message, kind = :info)
|
13
|
+
config.log message, kind
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -5,16 +5,26 @@ module PubSubModelSync
|
|
5
5
|
cattr_accessor(:subscribers) { [] }
|
6
6
|
cattr_accessor(:publishers) { [] }
|
7
7
|
cattr_accessor(:service_name) { :google }
|
8
|
-
|
8
|
+
|
9
|
+
# customizable callbacks
|
10
|
+
cattr_accessor(:debug) { false }
|
11
|
+
cattr_accessor :logger # LoggerInst
|
12
|
+
|
13
|
+
cattr_accessor(:on_process_success) { ->(_payload, _subscriber) {} }
|
14
|
+
cattr_accessor(:on_process_error) { ->(_exception, _payload) {} }
|
15
|
+
cattr_accessor(:on_before_publish) { ->(_payload) {} }
|
16
|
+
cattr_accessor(:on_after_publish) { ->(_payload) {} }
|
17
|
+
cattr_accessor(:on_publish_error) { ->(_exception, _payload) {} }
|
18
|
+
cattr_accessor(:disabled) { false }
|
9
19
|
|
10
20
|
# google service
|
11
21
|
cattr_accessor :project, :credentials, :topic_name, :subscription_name
|
12
22
|
|
13
23
|
# rabbitmq service
|
14
|
-
cattr_accessor :bunny_connection, :queue_name, :topic_name
|
24
|
+
cattr_accessor :bunny_connection, :queue_name, :topic_name, :subscription_name
|
15
25
|
|
16
26
|
# kafka service
|
17
|
-
cattr_accessor :kafka_connection, :topic_name
|
27
|
+
cattr_accessor :kafka_connection, :topic_name, :subscription_name
|
18
28
|
|
19
29
|
def self.log(msg, kind = :info)
|
20
30
|
msg = "PS_MSYNC ==> #{msg}"
|
@@ -24,5 +34,10 @@ module PubSubModelSync
|
|
24
34
|
logger ? logger.send(kind, msg) : puts(msg)
|
25
35
|
end
|
26
36
|
end
|
37
|
+
|
38
|
+
def self.subscription_key
|
39
|
+
subscription_name ||
|
40
|
+
(Rails.application.class.parent_name rescue '') # rubocop:disable Style/RescueModifier
|
41
|
+
end
|
27
42
|
end
|
28
43
|
end
|
@@ -1,40 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
|
-
class MessageProcessor
|
5
|
-
attr_accessor :
|
6
|
-
|
7
|
-
# @param
|
8
|
-
def initialize(data, klass, action)
|
9
|
-
|
10
|
-
@
|
11
|
-
|
4
|
+
class MessageProcessor < PubSubModelSync::Base
|
5
|
+
attr_accessor :payload
|
6
|
+
|
7
|
+
# @param payload (Payload): payload to be delivered
|
8
|
+
# @Deprecated: def initialize(data, klass, action)
|
9
|
+
def initialize(payload, klass = nil, action = nil)
|
10
|
+
@payload = payload
|
11
|
+
return if @payload.is_a?(Payload)
|
12
|
+
|
13
|
+
# support for deprecated
|
14
|
+
log('Deprecated: Use Payload instead of new(data, klass, action)')
|
15
|
+
@payload = PubSubModelSync::Payload.new(payload, { klass: klass, action: action })
|
12
16
|
end
|
13
17
|
|
14
18
|
def process
|
15
|
-
|
16
|
-
subscribers.each { |subscriber| run_subscriber(subscriber) }
|
19
|
+
filter_subscribers.each(&method(:run_subscriber))
|
17
20
|
end
|
18
21
|
|
19
22
|
private
|
20
23
|
|
21
24
|
def run_subscriber(subscriber)
|
22
|
-
subscriber.eval_message(data)
|
23
|
-
|
25
|
+
subscriber.eval_message(payload.data)
|
26
|
+
config.on_process_success.call(payload, subscriber)
|
27
|
+
log "processed message with: #{payload}"
|
24
28
|
rescue => e
|
25
|
-
|
26
|
-
log("error processing message: #{info}", :error)
|
29
|
+
print_subscriber_error(e)
|
27
30
|
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
# @param error (Error)
|
33
|
+
def print_subscriber_error(error)
|
34
|
+
info = [payload, error.message, error.backtrace]
|
35
|
+
res = config.on_process_error.call(error, payload)
|
36
|
+
log("Error processing message: #{info}", :error) if res != :skip_log
|
34
37
|
end
|
35
38
|
|
36
|
-
def
|
37
|
-
|
39
|
+
def filter_subscribers
|
40
|
+
config.subscribers.select do |subscriber|
|
41
|
+
subscriber.settings[:from_klass].to_s == payload.klass.to_s &&
|
42
|
+
subscriber.settings[:from_action].to_s == payload.action.to_s
|
43
|
+
end
|
38
44
|
end
|
39
45
|
end
|
40
46
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
|
-
class MessagePublisher
|
4
|
+
class MessagePublisher < PubSubModelSync::Base
|
5
5
|
class << self
|
6
|
-
delegate :publish, to: :connector
|
7
|
-
|
8
6
|
def connector
|
9
7
|
@connector ||= PubSubModelSync::Connector.new
|
10
8
|
end
|
11
9
|
|
12
10
|
def publish_data(klass, data, action)
|
13
|
-
|
14
|
-
publish(
|
11
|
+
payload = PubSubModelSync::Payload.new(data, { klass: klass, action: action.to_sym })
|
12
|
+
publish(payload)
|
15
13
|
end
|
16
14
|
|
17
15
|
# @param model: ActiveRecord model
|
@@ -21,12 +19,30 @@ module PubSubModelSync
|
|
21
19
|
return if model.ps_skip_sync?(action)
|
22
20
|
|
23
21
|
publisher ||= model.class.ps_publisher(action)
|
24
|
-
|
25
|
-
|
22
|
+
payload_info = publisher.payload(model, action)
|
23
|
+
payload = PubSubModelSync::Payload.new(payload_info[:data], payload_info[:attrs])
|
24
|
+
res_before = model.ps_before_sync(action, payload.data)
|
26
25
|
return if res_before == :cancel
|
27
26
|
|
28
|
-
publish(payload
|
29
|
-
model.ps_after_sync(action, payload
|
27
|
+
publish(payload)
|
28
|
+
model.ps_after_sync(action, payload.data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def publish(payload)
|
32
|
+
log("Publishing message: #{[payload]}") if config.debug
|
33
|
+
config.on_before_publish.call(payload)
|
34
|
+
connector.publish(payload)
|
35
|
+
config.on_after_publish.call(payload)
|
36
|
+
rescue => e
|
37
|
+
notify_error(e, payload)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def notify_error(exception, payload)
|
43
|
+
info = [payload, exception.message, exception.backtrace]
|
44
|
+
res = config.on_publish_error.call(exception, payload)
|
45
|
+
log("Error publishing: #{info}", :error) if res != :skip_log
|
30
46
|
end
|
31
47
|
end
|
32
48
|
end
|
@@ -20,12 +20,17 @@ module PubSubModelSync
|
|
20
20
|
def name
|
21
21
|
'name'
|
22
22
|
end
|
23
|
+
|
24
|
+
def publish(*_args)
|
25
|
+
true
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
class MockChannel
|
26
30
|
def queue(*_args)
|
27
31
|
@queue ||= MockQueue.new
|
28
32
|
end
|
33
|
+
alias fanout queue
|
29
34
|
|
30
35
|
def topic(*_args)
|
31
36
|
@topic ||= MockTopic.new
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class Payload
|
5
|
+
attr_reader :data, :attributes, :headers
|
6
|
+
|
7
|
+
# @param data (Hash: { any value }):
|
8
|
+
# @param attributes (Hash: { klass: string, action: :sym }):
|
9
|
+
def initialize(data, attributes, headers = {})
|
10
|
+
@data = data
|
11
|
+
@attributes = attributes
|
12
|
+
@headers = headers
|
13
|
+
build_headers
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{ data: data, attributes: attributes, headers: headers }
|
18
|
+
end
|
19
|
+
|
20
|
+
def klass
|
21
|
+
attributes[:klass]
|
22
|
+
end
|
23
|
+
|
24
|
+
def action
|
25
|
+
attributes[:action]
|
26
|
+
end
|
27
|
+
|
28
|
+
def process!
|
29
|
+
publisher = PubSubModelSync::MessageProcessor.new(self)
|
30
|
+
publisher.process
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish!
|
34
|
+
klass = PubSubModelSync::MessagePublisher
|
35
|
+
klass.publish(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_headers
|
41
|
+
headers[:uuid] ||= SecureRandom.uuid
|
42
|
+
headers[:app_key] ||= PubSubModelSync::Config.subscription_key
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -11,6 +11,7 @@ module PubSubModelSync
|
|
11
11
|
false
|
12
12
|
end
|
13
13
|
|
14
|
+
# TODO: make it using respond_to?(:ps_skip_sync?)
|
14
15
|
# before preparing data to sync
|
15
16
|
def ps_skip_sync?(_action)
|
16
17
|
false
|
@@ -63,7 +64,8 @@ module PubSubModelSync
|
|
63
64
|
|
64
65
|
def ps_register_callback(action, publisher)
|
65
66
|
after_commit(on: action) do |model|
|
66
|
-
|
67
|
+
disabled = PubSubModelSync::Config.disabled
|
68
|
+
if !disabled && !model.ps_skip_callback?(action)
|
67
69
|
klass = PubSubModelSync::MessagePublisher
|
68
70
|
klass.publish_model(model, action.to_sym, publisher)
|
69
71
|
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pub_sub_model_sync/payload'
|
3
4
|
module PubSubModelSync
|
4
|
-
class ServiceBase
|
5
|
+
class ServiceBase < PubSubModelSync::Base
|
5
6
|
SERVICE_KEY = 'service_model_sync'
|
6
7
|
|
7
8
|
def listen_messages
|
8
9
|
raise 'method :listen_messages must be defined in service'
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
+
# @param _payload (Payload)
|
13
|
+
def publish(_payload)
|
12
14
|
raise 'method :publish must be defined in service'
|
13
15
|
end
|
14
16
|
|
@@ -18,19 +20,29 @@ module PubSubModelSync
|
|
18
20
|
|
19
21
|
private
|
20
22
|
|
21
|
-
# @param
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
# @param (String: Payload in json format)
|
24
|
+
def process_message(payload_info)
|
25
|
+
payload = parse_payload(payload_info)
|
26
|
+
log("Received message: #{[payload]}") if config.debug
|
27
|
+
if same_app_message?(payload)
|
28
|
+
log("Skip message from same origin: #{[payload]}") if config.debug
|
29
|
+
else
|
30
|
+
payload.process!
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
error = [payload, e.message, e.backtrace]
|
34
|
+
log("Error parsing received message: #{error}", :error)
|
27
35
|
end
|
28
36
|
|
29
|
-
def
|
30
|
-
|
31
|
-
data
|
32
|
-
|
33
|
-
|
37
|
+
def parse_payload(payload_info)
|
38
|
+
info = JSON.parse(payload_info).deep_symbolize_keys
|
39
|
+
::PubSubModelSync::Payload.new(info[:data], info[:attributes], info[:headers])
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param payload (Payload)
|
43
|
+
def same_app_message?(payload)
|
44
|
+
key = payload.headers[:app_key]
|
45
|
+
key && key == config.subscription_key
|
34
46
|
end
|
35
47
|
end
|
36
48
|
end
|
@@ -7,10 +7,9 @@ end
|
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceGoogle < ServiceBase
|
10
|
-
attr_accessor :service, :topic, :subscription, :
|
10
|
+
attr_accessor :service, :topic, :subscription, :subscriber
|
11
11
|
|
12
12
|
def initialize
|
13
|
-
@config = PubSubModelSync::Config
|
14
13
|
@service = Google::Cloud::Pubsub.new(project: config.project,
|
15
14
|
credentials: config.credentials)
|
16
15
|
@topic = service.topic(config.topic_name) ||
|
@@ -28,13 +27,8 @@ module PubSubModelSync
|
|
28
27
|
log('Listener stopped')
|
29
28
|
end
|
30
29
|
|
31
|
-
def publish(
|
32
|
-
|
33
|
-
payload = { data: data, attributes: attributes }.to_json
|
34
|
-
topic.publish(payload, { SERVICE_KEY => true })
|
35
|
-
rescue => e
|
36
|
-
info = [data, attributes, e.message, e.backtrace]
|
37
|
-
log("Error publishing: #{info}", :error)
|
30
|
+
def publish(payload)
|
31
|
+
topic.publish(payload.to_json, { SERVICE_KEY => true })
|
38
32
|
end
|
39
33
|
|
40
34
|
def stop
|
@@ -51,17 +45,9 @@ module PubSubModelSync
|
|
51
45
|
|
52
46
|
def process_message(received_message)
|
53
47
|
message = received_message.message
|
54
|
-
|
55
|
-
|
56
|
-
perform_message(message.data)
|
57
|
-
rescue => e
|
58
|
-
log("Error processing message: #{[received_message, e.message]}", :error)
|
48
|
+
super(message.data) if message.attributes[SERVICE_KEY]
|
59
49
|
ensure
|
60
50
|
received_message.acknowledge!
|
61
51
|
end
|
62
|
-
|
63
|
-
def log(msg, kind = :info)
|
64
|
-
config.log("Google Service ==> #{msg}", kind)
|
65
|
-
end
|
66
52
|
end
|
67
53
|
end
|
@@ -8,9 +8,8 @@ end
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceKafka < ServiceBase
|
10
10
|
cattr_accessor :producer
|
11
|
+
attr_accessor :config, :service, :consumer
|
11
12
|
|
12
|
-
attr_accessor :service, :consumer
|
13
|
-
attr_accessor :config
|
14
13
|
CONSUMER_GROUP = 'service_model_sync'
|
15
14
|
|
16
15
|
def initialize
|
@@ -23,19 +22,14 @@ module PubSubModelSync
|
|
23
22
|
start_consumer
|
24
23
|
consumer.each_message(&method(:process_message))
|
25
24
|
rescue PubSubModelSync::Runner::ShutDown
|
26
|
-
|
25
|
+
log('Listener stopped')
|
27
26
|
rescue => e
|
28
27
|
log("Error listening message: #{[e.message, e.backtrace]}", :error)
|
29
28
|
end
|
30
29
|
|
31
|
-
def publish(
|
32
|
-
log("Publishing: #{[data, attributes]}")
|
33
|
-
payload = { data: data, attributes: attributes }
|
30
|
+
def publish(payload)
|
34
31
|
producer.produce(payload.to_json, message_settings)
|
35
32
|
producer.deliver_messages
|
36
|
-
rescue => e
|
37
|
-
info = [data, attributes, e.message, e.backtrace]
|
38
|
-
log("Error publishing: #{info}", :error)
|
39
33
|
end
|
40
34
|
|
41
35
|
def stop
|
@@ -64,14 +58,7 @@ module PubSubModelSync
|
|
64
58
|
def process_message(message)
|
65
59
|
return unless message.headers[SERVICE_KEY]
|
66
60
|
|
67
|
-
|
68
|
-
rescue => e
|
69
|
-
error = [message, e.message, e.backtrace]
|
70
|
-
log("Error processing message: #{error}", :error)
|
71
|
-
end
|
72
|
-
|
73
|
-
def log(msg, kind = :info)
|
74
|
-
config.log("Kafka Service ==> #{msg}", kind)
|
61
|
+
super(message.value)
|
75
62
|
end
|
76
63
|
end
|
77
64
|
end
|