pub_sub_model_sync 0.1.1 → 0.2.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/.travis.yml +1 -1
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +7 -3
- data/README.md +43 -25
- data/lib/pub_sub_model_sync.rb +3 -0
- data/lib/pub_sub_model_sync/config.rb +9 -1
- data/lib/pub_sub_model_sync/connector.rb +2 -0
- data/lib/pub_sub_model_sync/message_processor.rb +0 -6
- data/lib/pub_sub_model_sync/mock_kafka_service.rb +45 -0
- data/lib/pub_sub_model_sync/publisher.rb +14 -6
- data/lib/pub_sub_model_sync/publisher_concern.rb +19 -16
- data/lib/pub_sub_model_sync/service_base.rb +36 -0
- data/lib/pub_sub_model_sync/service_google.rb +14 -11
- data/lib/pub_sub_model_sync/service_kafka.rb +77 -0
- data/lib/pub_sub_model_sync/service_rabbit.rb +18 -19
- data/lib/pub_sub_model_sync/subscriber_concern.rb +13 -10
- data/lib/pub_sub_model_sync/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6108e077451c0ffc3d3a4b277528ccf036a64edf85c84bb810bc7ce2769bdc35
|
4
|
+
data.tar.gz: 63924273967917c2b5f740f0f917fa772341bc7fbd7c77b52037e9c264e5a080
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 480a5c3d69cf06837e443ed6abfef5d927ddfc3df1c48e9948487e470666cdbb11711bb73e17c85d594c29c176a3fc0108867563a14d630152f078f2034d54af
|
7
|
+
data.tar.gz: 57ec430feef5ed8a5b2318703620b2d5f0d6faba14d1227f108a5411ee6fde587219e00fd3cedb8735211c9ff24dac5127af4b88428749164ce3631eb5691e45
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# 0.2.0
|
4
|
+
- Add apache kafka support
|
5
|
+
- Add Service interface for future references
|
6
|
+
- Improve Services to use a single/common message performer
|
7
|
+
|
8
|
+
# 0.1.4
|
9
|
+
- Add attribute aliases when publishing, ```ps_publish(['name:full_name', 'email'])```
|
10
|
+
- Ability to retrieve publisher/subscriber crud settings
|
11
|
+
|
12
|
+
# 0.1.3
|
13
|
+
- shorter publisher/subscriber methods: ps_msync_subscribe into ps_subscribe
|
14
|
+
|
15
|
+
# 0.1.2
|
16
|
+
- fix not found pub/sub library (buggy)
|
17
|
+
|
3
18
|
# 0.1.1
|
4
19
|
- Add rabbitmq pub/sub service support
|
5
20
|
- Reformat to support multiple pub/sub services
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pub_sub_model_sync (0.
|
4
|
+
pub_sub_model_sync (0.2.0)
|
5
5
|
activesupport
|
6
6
|
rails
|
7
7
|
|
@@ -60,6 +60,7 @@ GEM
|
|
60
60
|
concurrent-ruby (1.1.6)
|
61
61
|
crass (1.0.6)
|
62
62
|
diff-lcs (1.3)
|
63
|
+
digest-crc (0.5.1)
|
63
64
|
erubi (1.9.0)
|
64
65
|
faraday (0.17.3)
|
65
66
|
multipart-post (>= 1.2, < 3)
|
@@ -81,7 +82,7 @@ GEM
|
|
81
82
|
googleauth (>= 0.6.2, < 0.10.0)
|
82
83
|
grpc (>= 1.7.2, < 2.0)
|
83
84
|
rly (~> 0.2.3)
|
84
|
-
google-protobuf (3.11.4
|
85
|
+
google-protobuf (3.11.4)
|
85
86
|
googleapis-common-protos (1.3.9)
|
86
87
|
google-protobuf (~> 3.0)
|
87
88
|
googleapis-common-protos-types (~> 1.0)
|
@@ -95,7 +96,7 @@ GEM
|
|
95
96
|
multi_json (~> 1.11)
|
96
97
|
os (>= 0.9, < 2.0)
|
97
98
|
signet (~> 0.7)
|
98
|
-
grpc (1.27.0
|
99
|
+
grpc (1.27.0)
|
99
100
|
google-protobuf (~> 3.11)
|
100
101
|
googleapis-common-protos-types (~> 1.0)
|
101
102
|
grpc-google-iam-v1 (0.6.9)
|
@@ -180,6 +181,8 @@ GEM
|
|
180
181
|
rexml
|
181
182
|
ruby-progressbar (~> 1.7)
|
182
183
|
unicode-display_width (>= 1.4.0, < 1.7)
|
184
|
+
ruby-kafka (1.0.0)
|
185
|
+
digest-crc
|
183
186
|
ruby-progressbar (1.10.1)
|
184
187
|
signet (0.11.0)
|
185
188
|
addressable (~> 2.3)
|
@@ -214,6 +217,7 @@ DEPENDENCIES
|
|
214
217
|
rake
|
215
218
|
rspec
|
216
219
|
rubocop
|
220
|
+
ruby-kafka
|
217
221
|
sqlite3
|
218
222
|
|
219
223
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# PubSubModelSync
|
2
|
-
Permit to sync models data and make calls between rails apps using google or rabbitmq pub/sub service.
|
2
|
+
Permit to sync models data and make calls between rails apps using google or rabbitmq or apache kafka pub/sub service.
|
3
3
|
|
4
4
|
Note: This gem is based on [MultipleMan](https://github.com/influitive/multiple_man) which for now looks unmaintained.
|
5
5
|
|
@@ -14,8 +14,10 @@ Note: This gem is based on [MultipleMan](https://github.com/influitive/multiple_
|
|
14
14
|
Add this line to your application's Gemfile:
|
15
15
|
```ruby
|
16
16
|
gem 'pub_sub_model_sync'
|
17
|
+
|
17
18
|
gem 'google-cloud-pubsub' # to use google pub/sub service
|
18
19
|
gem 'bunny' # to use rabbit-mq pub/sub service
|
20
|
+
gem 'ruby-kafka' # to use apache kafka pub/sub service
|
19
21
|
```
|
20
22
|
And then execute: $ bundle install
|
21
23
|
|
@@ -43,9 +45,17 @@ And then execute: $ bundle install
|
|
43
45
|
```
|
44
46
|
See details here: https://github.com/ruby-amqp/bunny
|
45
47
|
|
48
|
+
- configuration for Apache Kafka (You need kafka installed)
|
49
|
+
```ruby
|
50
|
+
PubSubModelSync::Config.service_name = :kafka
|
51
|
+
PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], logger: Rails.logger]
|
52
|
+
PubSubModelSync::Config.topic_name = 'sample-topic'
|
53
|
+
```
|
54
|
+
See details here: https://github.com/zendesk/ruby-kafka
|
55
|
+
|
46
56
|
- Add publishers/subscribers to your models (See examples below)
|
47
57
|
|
48
|
-
- Start
|
58
|
+
- Start subscribers to listen for publishers (Only in the app that has subscribers)
|
49
59
|
```ruby
|
50
60
|
rake pub_sub_model_sync:start
|
51
61
|
```
|
@@ -53,18 +63,18 @@ And then execute: $ bundle install
|
|
53
63
|
|
54
64
|
## Examples
|
55
65
|
```ruby
|
56
|
-
# App 1
|
66
|
+
# App 1 (Publisher)
|
57
67
|
# attributes: name email age
|
58
68
|
class User < ActiveRecord::Base
|
59
69
|
include PubSubModelSync::PublisherConcern
|
60
|
-
|
70
|
+
ps_publish(%i[name email])
|
61
71
|
end
|
62
72
|
|
63
|
-
# App 2
|
73
|
+
# App 2 (Subscriber)
|
64
74
|
class User < ActiveRecord::Base
|
65
75
|
include PubSubModelSync::SubscriberConcern
|
66
|
-
|
67
|
-
|
76
|
+
ps_subscribe(%i[name])
|
77
|
+
ps_class_subscribe(:greeting)
|
68
78
|
|
69
79
|
def self.greeting(data)
|
70
80
|
puts 'Class message called'
|
@@ -72,29 +82,30 @@ class User < ActiveRecord::Base
|
|
72
82
|
end
|
73
83
|
|
74
84
|
# Samples
|
75
|
-
User.create(name: 'test user') # Review your App 2 to see the created user (only name will be saved)
|
76
|
-
User.
|
85
|
+
User.create(name: 'test user', email: 'sample@gmail.com') # Review your App 2 to see the created user (only name will be saved)
|
86
|
+
User.ps_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
|
87
|
+
PubSubModelSync::Publisher.new.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
|
77
88
|
```
|
78
89
|
|
79
90
|
## Advanced Example
|
80
91
|
```ruby
|
81
|
-
# App 1
|
92
|
+
# App 1 (Publisher)
|
82
93
|
class User < ActiveRecord::Base
|
83
94
|
self.table_name = 'publisher_users'
|
84
95
|
include PubSubModelSync::PublisherConcern
|
85
|
-
|
96
|
+
ps_publish(%i[name:full_name email], actions: %i[update], as_klass: 'Client', id: :client_id)
|
86
97
|
|
87
|
-
def
|
98
|
+
def ps_skip_for?(_action)
|
88
99
|
false # here logic with action to skip push message
|
89
100
|
end
|
90
101
|
end
|
91
102
|
|
92
|
-
# App 2
|
103
|
+
# App 2 (Subscriber)
|
93
104
|
class User < ActiveRecord::Base
|
94
105
|
self.table_name = 'subscriber_users'
|
95
106
|
include PubSubModelSync::SubscriberConcern
|
96
|
-
|
97
|
-
|
107
|
+
ps_subscribe(%i[name], actions: %i[update], as_klass: 'Client', id: :custom_id)
|
108
|
+
ps_class_subscribe(:greeting, as_action: :custom_greeting, as_klass: 'CustomUser')
|
98
109
|
|
99
110
|
def self.greeting(data)
|
100
111
|
puts 'Class message called through custom_greeting'
|
@@ -102,22 +113,29 @@ class User < ActiveRecord::Base
|
|
102
113
|
end
|
103
114
|
```
|
104
115
|
|
105
|
-
## Testing
|
106
|
-
-
|
116
|
+
## Testing with RSpec
|
117
|
+
- Config: (spec/rails_helper.rb)
|
107
118
|
```ruby
|
108
119
|
|
109
120
|
# when using google service
|
110
121
|
require 'pub_sub_model_sync/mock_google_service'
|
111
122
|
config.before(:each) do
|
112
|
-
|
113
|
-
allow(Google::Cloud::Pubsub).to receive(:new).and_return(
|
123
|
+
google_mock = PubSubModelSync::MockGoogleService.new
|
124
|
+
allow(Google::Cloud::Pubsub).to receive(:new).and_return(google_mock)
|
114
125
|
end
|
115
126
|
|
116
127
|
# when using rabbitmq service
|
117
128
|
require 'pub_sub_model_sync/mock_rabbit_service'
|
118
129
|
config.before(:each) do
|
119
|
-
|
120
|
-
allow(Bunny).to receive(:new).and_return(
|
130
|
+
rabbit_mock = PubSubModelSync::MockRabbitService.new
|
131
|
+
allow(Bunny).to receive(:new).and_return(rabbit_mock)
|
132
|
+
end
|
133
|
+
|
134
|
+
# when using apache kafka service
|
135
|
+
require 'pub_sub_model_sync/mock_kafka_service'
|
136
|
+
config.before(:each) do
|
137
|
+
kafka_mock = PubSubModelSync::MockKafkaService.new
|
138
|
+
allow(Kafka).to receive(:new).and_return(kafka_mock)
|
121
139
|
end
|
122
140
|
|
123
141
|
```
|
@@ -147,7 +165,7 @@ end
|
|
147
165
|
publisher = PubSubModelSync::Publisher
|
148
166
|
data = { name: 'hello'}
|
149
167
|
action = :create
|
150
|
-
User.
|
168
|
+
User.ps_class_publish(data, action: action)
|
151
169
|
user = User.create(name: 'name', email: 'email')
|
152
170
|
expect_any_instance_of(publisher).to receive(:publish_model).with(user, :create, anything)
|
153
171
|
end
|
@@ -156,16 +174,16 @@ end
|
|
156
174
|
publisher = PubSubModelSync::Publisher
|
157
175
|
data = {msg: 'hello'}
|
158
176
|
action = :greeting
|
159
|
-
User.
|
177
|
+
User.ps_class_publish(data, action: action)
|
160
178
|
expect_any_instance_of(publisher).to receive(:publish_data).with('User', data, action)
|
161
179
|
end
|
162
180
|
```
|
163
181
|
|
164
182
|
There are two special methods to extract crud configuration settings (attrs, id, ...):
|
165
183
|
|
166
|
-
Subscribers: ```User.
|
184
|
+
Subscribers: ```User.ps_subscriber```
|
167
185
|
|
168
|
-
Publishers: ```User.
|
186
|
+
Publishers: ```User.ps_publisher```
|
169
187
|
|
170
188
|
Note: Inspect all configured listeners with:
|
171
189
|
``` PubSubModelSync::Config.listeners ```
|
data/lib/pub_sub_model_sync.rb
CHANGED
@@ -11,8 +11,11 @@ require 'pub_sub_model_sync/publisher_concern'
|
|
11
11
|
require 'pub_sub_model_sync/runner'
|
12
12
|
require 'pub_sub_model_sync/connector'
|
13
13
|
require 'pub_sub_model_sync/message_processor'
|
14
|
+
|
15
|
+
require 'pub_sub_model_sync/service_base'
|
14
16
|
require 'pub_sub_model_sync/service_google'
|
15
17
|
require 'pub_sub_model_sync/service_rabbit'
|
18
|
+
require 'pub_sub_model_sync/service_kafka'
|
16
19
|
|
17
20
|
module PubSubModelSync
|
18
21
|
class Error < StandardError; end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Config
|
5
5
|
cattr_accessor :listeners, default: []
|
6
|
+
cattr_accessor :publishers, default: []
|
6
7
|
cattr_accessor :service_name, default: :google
|
7
8
|
cattr_accessor :logger
|
8
9
|
|
@@ -12,9 +13,16 @@ module PubSubModelSync
|
|
12
13
|
# rabbitmq service
|
13
14
|
cattr_accessor :bunny_connection, :queue_name, :topic_name
|
14
15
|
|
16
|
+
# kafka service
|
17
|
+
cattr_accessor :kafka_connection, :topic_name
|
18
|
+
|
15
19
|
def self.log(msg, kind = :info)
|
16
20
|
msg = "PS_MSYNC ==> #{msg}"
|
17
|
-
|
21
|
+
if logger == :raise_error
|
22
|
+
kind == :error ? raise(msg) : puts(msg)
|
23
|
+
else
|
24
|
+
logger ? logger.send(kind, msg) : puts(msg)
|
25
|
+
end
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
@@ -40,7 +40,6 @@ module PubSubModelSync
|
|
40
40
|
|
41
41
|
# support for: create, update, destroy
|
42
42
|
def call_listener(listener)
|
43
|
-
listener_add_crud_settings(listener)
|
44
43
|
model = find_model(listener)
|
45
44
|
if attrs[:action].to_sym == :destroy
|
46
45
|
model.destroy!
|
@@ -74,11 +73,6 @@ module PubSubModelSync
|
|
74
73
|
end
|
75
74
|
end
|
76
75
|
|
77
|
-
def listener_add_crud_settings(listener)
|
78
|
-
model_class = listener[:klass].constantize
|
79
|
-
listener[:settings] = model_class.ps_msync_subscriber_settings
|
80
|
-
end
|
81
|
-
|
82
76
|
def log(message, kind = :info)
|
83
77
|
PubSubModelSync::Config.log "#{message} ==> #{[data, attrs]}", kind
|
84
78
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class MockKafkaService
|
5
|
+
class MockProducer
|
6
|
+
def produce(*_args)
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def deliver_messages(*_args)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def shutdown
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class MockConsumer
|
20
|
+
def each_message(*_args)
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop(*_args)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def subscribe(*_args)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def producer(*_args)
|
34
|
+
MockProducer.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def consumer(*_args)
|
38
|
+
MockConsumer.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -14,12 +14,10 @@ module PubSubModelSync
|
|
14
14
|
|
15
15
|
# @param settings (Hash): { attrs: [], as_klass: nil, id: nil }
|
16
16
|
def publish_model(model, action, settings = nil)
|
17
|
-
settings ||= model.class.
|
17
|
+
settings ||= model.class.ps_publisher_info(action)
|
18
18
|
attributes = build_model_attrs(model, action, settings)
|
19
19
|
data = {}
|
20
|
-
if action !=
|
21
|
-
data = model.as_json(only: settings[:attrs], methods: settings[:attrs])
|
22
|
-
end
|
20
|
+
data = build_model_data(model, settings[:attrs]) if action != :destroy
|
23
21
|
connector.publish(data.symbolize_keys, attributes)
|
24
22
|
end
|
25
23
|
|
@@ -27,13 +25,23 @@ module PubSubModelSync
|
|
27
25
|
{
|
28
26
|
klass: klass.to_s,
|
29
27
|
action: action.to_sym,
|
30
|
-
id: id
|
31
|
-
service_model_sync: true
|
28
|
+
id: id
|
32
29
|
}
|
33
30
|
end
|
34
31
|
|
35
32
|
private
|
36
33
|
|
34
|
+
def build_model_data(model, model_props)
|
35
|
+
source_props = model_props.map { |prop| prop.to_s.split(':').first }
|
36
|
+
data = model.as_json(only: source_props, methods: source_props)
|
37
|
+
aliased_props = model_props.select { |prop| prop.to_s.include?(':') }
|
38
|
+
aliased_props.each do |prop|
|
39
|
+
source, target = prop.to_s.split(':')
|
40
|
+
data[target] = data.delete(source)
|
41
|
+
end
|
42
|
+
data.symbolize_keys
|
43
|
+
end
|
44
|
+
|
37
45
|
def build_model_attrs(model, action, settings)
|
38
46
|
as_klass = (settings[:as_klass] || model.class.name).to_s
|
39
47
|
id_val = model.send(settings[:id] || :id)
|
@@ -7,41 +7,44 @@ module PubSubModelSync
|
|
7
7
|
end
|
8
8
|
|
9
9
|
# Permit to skip a publish callback
|
10
|
-
def
|
10
|
+
def ps_skip_for?(_action)
|
11
11
|
false
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
15
|
# Permit to publish crud actions (:create, :update, :destroy)
|
16
16
|
# @param settings (Hash): { actions: nil, as_klass: nil, id: nil }
|
17
|
-
def
|
17
|
+
def ps_publish(attrs, settings = {})
|
18
18
|
actions = settings.delete(:actions) || %i[create update destroy]
|
19
|
-
|
20
|
-
|
19
|
+
actions.each do |action|
|
20
|
+
info = settings.merge(klass: name, action: action, attrs: attrs)
|
21
|
+
PubSubModelSync::Config.publishers << info
|
22
|
+
ps_register_callback(action.to_sym, info)
|
23
|
+
end
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def ps_publisher_info(action = :create)
|
27
|
+
PubSubModelSync::Config.publishers.select do |listener|
|
28
|
+
listener[:klass] == name && listener[:action] == action
|
29
|
+
end.last
|
25
30
|
end
|
26
31
|
|
27
|
-
def
|
32
|
+
def ps_class_publish(data, action:, as_klass: nil)
|
28
33
|
as_klass = (as_klass || name).to_s
|
29
|
-
|
34
|
+
ps_publisher_service.publish_data(as_klass, data, action.to_sym)
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
37
|
+
def ps_publisher_service
|
33
38
|
PubSubModelSync::Publisher.new
|
34
39
|
end
|
35
40
|
|
36
41
|
private
|
37
42
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
publisher.publish_model(model, action.to_sym)
|
44
|
-
end
|
43
|
+
def ps_register_callback(action, info)
|
44
|
+
after_commit(on: action) do |model|
|
45
|
+
unless model.ps_skip_for?(action)
|
46
|
+
service = model.class.ps_publisher_service
|
47
|
+
service.publish_model(model, action.to_sym, info)
|
45
48
|
end
|
46
49
|
end
|
47
50
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PubSubModelSync
|
4
|
+
class ServiceBase
|
5
|
+
SERVICE_KEY = 'service_model_sync'
|
6
|
+
|
7
|
+
def listen_messages
|
8
|
+
raise 'method :listen_messages must be defined in service'
|
9
|
+
end
|
10
|
+
|
11
|
+
def publish(_data, _attributes)
|
12
|
+
raise 'method :publish must be defined in service'
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
raise 'method :stop must be defined in service'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @param payload (String JSON): '{"data":{},"attributes":{..}}'
|
22
|
+
# refer: PubSubModelSync::Publisher (.publish_model | .publish_data)
|
23
|
+
def perform_message(payload)
|
24
|
+
data, attrs = parse_message_payload(payload)
|
25
|
+
args = [data, attrs[:klass], attrs[:action], attrs]
|
26
|
+
PubSubModelSync::MessageProcessor.new(*args).process
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_message_payload(payload)
|
30
|
+
message_payload = JSON.parse(payload).symbolize_keys
|
31
|
+
data = message_payload[:data].symbolize_keys
|
32
|
+
attrs = message_payload[:attributes].symbolize_keys
|
33
|
+
[data, attrs]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
require 'google/cloud/pubsub'
|
5
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
6
|
+
end
|
7
|
+
|
4
8
|
module PubSubModelSync
|
5
|
-
class ServiceGoogle
|
9
|
+
class ServiceGoogle < ServiceBase
|
6
10
|
attr_accessor :service, :topic, :subscription, :config, :subscriber
|
7
11
|
|
8
12
|
def initialize
|
@@ -26,7 +30,9 @@ module PubSubModelSync
|
|
26
30
|
|
27
31
|
def publish(data, attributes)
|
28
32
|
log("Publishing message: #{[data, attributes]}")
|
29
|
-
|
33
|
+
|
34
|
+
payload = { data: data, attributes: attributes }.to_json
|
35
|
+
topic.publish(payload, { SERVICE_KEY => true })
|
30
36
|
end
|
31
37
|
|
32
38
|
def stop
|
@@ -43,20 +49,17 @@ module PubSubModelSync
|
|
43
49
|
|
44
50
|
def process_message(received_message)
|
45
51
|
message = received_message.message
|
46
|
-
|
47
|
-
return unless attrs[:service_model_sync]
|
52
|
+
return unless message.attributes[SERVICE_KEY]
|
48
53
|
|
49
|
-
|
50
|
-
args = [data, attrs[:klass], attrs[:action], attrs]
|
51
|
-
PubSubModelSync::MessageProcessor.new(*args).process
|
54
|
+
perform_message(message.data)
|
52
55
|
rescue => e
|
53
|
-
log("Error processing message: #{[received_message, e.message]}")
|
56
|
+
log("Error processing message: #{[received_message, e.message]}", :error)
|
54
57
|
ensure
|
55
58
|
received_message.acknowledge!
|
56
59
|
end
|
57
60
|
|
58
|
-
def log(msg)
|
59
|
-
config.log("Google Service ==> #{msg}")
|
61
|
+
def log(msg, kind = :info)
|
62
|
+
config.log("Google Service ==> #{msg}", kind)
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'kafka'
|
5
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
6
|
+
end
|
7
|
+
|
8
|
+
module PubSubModelSync
|
9
|
+
class ServiceKafka < ServiceBase
|
10
|
+
cattr_accessor :producer
|
11
|
+
|
12
|
+
attr_accessor :service, :consumer
|
13
|
+
attr_accessor :config
|
14
|
+
CONSUMER_GROUP = 'service_model_sync'
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@config = PubSubModelSync::Config
|
18
|
+
@service = Kafka.new(*config.kafka_connection)
|
19
|
+
end
|
20
|
+
|
21
|
+
def listen_messages
|
22
|
+
log('Listener starting...')
|
23
|
+
start_consumer
|
24
|
+
consumer.each_message(&method(:process_message))
|
25
|
+
rescue PubSubModelSync::Runner::ShutDown
|
26
|
+
raise
|
27
|
+
rescue => e
|
28
|
+
log("Error listening message: #{[e.message, e.backtrace]}", :error)
|
29
|
+
end
|
30
|
+
|
31
|
+
def publish(data, attributes)
|
32
|
+
log("Publishing: #{[data, attributes]}")
|
33
|
+
payload = { data: data, attributes: attributes }
|
34
|
+
producer.produce(payload.to_json, message_settings)
|
35
|
+
producer.deliver_messages
|
36
|
+
rescue => e
|
37
|
+
info = [data, attributes, e.message, e.backtrace]
|
38
|
+
log("Error publishing: #{info}", :error)
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop
|
42
|
+
log('Listener stopping...')
|
43
|
+
consumer.stop
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def message_settings
|
49
|
+
{ topic: config.topic_name, headers: { SERVICE_KEY => true } }
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_consumer
|
53
|
+
@consumer = service.consumer(group_id: CONSUMER_GROUP)
|
54
|
+
consumer.subscribe(config.topic_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def producer
|
58
|
+
return self.class.producer if self.class.producer
|
59
|
+
|
60
|
+
at_exit { self.class.producer.shutdown }
|
61
|
+
self.class.producer = service.producer
|
62
|
+
end
|
63
|
+
|
64
|
+
def process_message(message)
|
65
|
+
return unless message.headers[SERVICE_KEY]
|
66
|
+
|
67
|
+
perform_message(message.value)
|
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)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
require 'bunny'
|
5
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
6
|
+
end
|
7
|
+
|
4
8
|
module PubSubModelSync
|
5
|
-
class ServiceRabbit
|
9
|
+
class ServiceRabbit < ServiceBase
|
6
10
|
attr_accessor :service, :channel, :queue, :topic
|
7
11
|
attr_accessor :config
|
8
|
-
SERVICE_KEY = 'service_model_sync'
|
9
12
|
|
10
13
|
def initialize
|
11
14
|
@config = PubSubModelSync::Config
|
@@ -20,16 +23,17 @@ module PubSubModelSync
|
|
20
23
|
rescue PubSubModelSync::Runner::ShutDown
|
21
24
|
raise
|
22
25
|
rescue => e
|
23
|
-
log("Error listening message: #{[e.message, e.backtrace]}")
|
26
|
+
log("Error listening message: #{[e.message, e.backtrace]}", :error)
|
24
27
|
end
|
25
28
|
|
26
29
|
def publish(data, attributes)
|
27
30
|
log("Publishing: #{[data, attributes]}")
|
28
31
|
subscribe_to_queue
|
29
32
|
payload = { data: data, attributes: attributes }
|
30
|
-
topic.publish(payload.to_json,
|
33
|
+
topic.publish(payload.to_json, message_settings)
|
31
34
|
rescue => e
|
32
|
-
|
35
|
+
info = [data, attributes, e.message, e.backtrace]
|
36
|
+
log("Error publishing: #{info}", :error)
|
33
37
|
end
|
34
38
|
|
35
39
|
def stop
|
@@ -39,22 +43,17 @@ module PubSubModelSync
|
|
39
43
|
|
40
44
|
private
|
41
45
|
|
46
|
+
def message_settings
|
47
|
+
{ routing_key: queue.name, type: SERVICE_KEY }
|
48
|
+
end
|
49
|
+
|
42
50
|
def process_message(_delivery_info, meta_info, payload)
|
43
51
|
return unless meta_info[:type] == SERVICE_KEY
|
44
52
|
|
45
|
-
|
46
|
-
args = [data, attrs[:klass], attrs[:action], attrs]
|
47
|
-
PubSubModelSync::MessageProcessor.new(*args).process
|
53
|
+
perform_message(payload)
|
48
54
|
rescue => e
|
49
55
|
error = [payload, e.message, e.backtrace]
|
50
|
-
log("Error processing message: #{error}")
|
51
|
-
end
|
52
|
-
|
53
|
-
def parse_message_payload(payload)
|
54
|
-
message_payload = JSON.parse(payload).symbolize_keys
|
55
|
-
data = message_payload[:data].symbolize_keys
|
56
|
-
attrs = message_payload[:attributes].symbolize_keys
|
57
|
-
[data, attrs]
|
56
|
+
log("Error processing message: #{error}", :error)
|
58
57
|
end
|
59
58
|
|
60
59
|
def subscribe_to_queue
|
@@ -70,8 +69,8 @@ module PubSubModelSync
|
|
70
69
|
queue.bind(topic, routing_key: queue.name)
|
71
70
|
end
|
72
71
|
|
73
|
-
def log(msg)
|
74
|
-
config.log("Rabbit Service ==> #{msg}")
|
72
|
+
def log(msg, kind = :info)
|
73
|
+
config.log("Rabbit Service ==> #{msg}", kind)
|
75
74
|
end
|
76
75
|
end
|
77
76
|
end
|
@@ -8,32 +8,35 @@ module PubSubModelSync
|
|
8
8
|
|
9
9
|
module ClassMethods
|
10
10
|
# @param settings (Hash): { as_klass: nil, actions: nil, id: nil }
|
11
|
-
def
|
12
|
-
|
11
|
+
def ps_subscribe(attrs, settings = {})
|
12
|
+
as_klass = (settings[:as_klass] || name).to_s
|
13
13
|
actions = settings.delete(:actions) || %i[create update destroy]
|
14
|
-
|
14
|
+
settings = settings.merge(attrs: attrs)
|
15
15
|
actions.each do |action|
|
16
|
-
|
16
|
+
add_ps_subscriber(as_klass, action, action, false, settings)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def ps_class_subscribe(action, as_action: nil, as_klass: nil)
|
21
|
+
add_ps_subscriber(as_klass, action, as_action, true, {})
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def ps_subscriber(action = :create)
|
25
|
+
PubSubModelSync::Config.listeners.select do |listener|
|
26
|
+
listener[:klass] == name && listener[:action] == action
|
27
|
+
end.last
|
26
28
|
end
|
27
29
|
|
28
30
|
private
|
29
31
|
|
30
|
-
def
|
32
|
+
def add_ps_subscriber(as_klass, action, as_action, direct_mode, settings)
|
31
33
|
listener = {
|
32
34
|
klass: name,
|
33
35
|
as_klass: (as_klass || name).to_s,
|
34
36
|
action: action.to_sym,
|
35
37
|
as_action: (as_action || action).to_sym,
|
36
|
-
direct_mode: direct_mode
|
38
|
+
direct_mode: direct_mode,
|
39
|
+
settings: settings
|
37
40
|
}
|
38
41
|
PubSubModelSync::Config.listeners << listener
|
39
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pub_sub_model_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Owen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-03-
|
11
|
+
date: 2020-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -119,12 +119,15 @@ files:
|
|
119
119
|
- lib/pub_sub_model_sync/connector.rb
|
120
120
|
- lib/pub_sub_model_sync/message_processor.rb
|
121
121
|
- lib/pub_sub_model_sync/mock_google_service.rb
|
122
|
+
- lib/pub_sub_model_sync/mock_kafka_service.rb
|
122
123
|
- lib/pub_sub_model_sync/mock_rabbit_service.rb
|
123
124
|
- lib/pub_sub_model_sync/publisher.rb
|
124
125
|
- lib/pub_sub_model_sync/publisher_concern.rb
|
125
126
|
- lib/pub_sub_model_sync/railtie.rb
|
126
127
|
- lib/pub_sub_model_sync/runner.rb
|
128
|
+
- lib/pub_sub_model_sync/service_base.rb
|
127
129
|
- lib/pub_sub_model_sync/service_google.rb
|
130
|
+
- lib/pub_sub_model_sync/service_kafka.rb
|
128
131
|
- lib/pub_sub_model_sync/service_rabbit.rb
|
129
132
|
- lib/pub_sub_model_sync/subscriber_concern.rb
|
130
133
|
- lib/pub_sub_model_sync/tasks/worker.rake
|