pub_sub_model_sync 0.1.4 → 0.2.3
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/CHANGELOG.md +20 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +7 -3
- data/README.md +85 -15
- data/lib/pub_sub_model_sync.rb +3 -0
- data/lib/pub_sub_model_sync/config.rb +6 -3
- data/lib/pub_sub_model_sync/connector.rb +2 -0
- data/lib/pub_sub_model_sync/message_processor.rb +8 -4
- data/lib/pub_sub_model_sync/mock_kafka_service.rb +45 -0
- data/lib/pub_sub_model_sync/mock_rabbit_service.rb +4 -0
- data/lib/pub_sub_model_sync/publisher.rb +7 -2
- data/lib/pub_sub_model_sync/publisher_concern.rb +24 -3
- data/lib/pub_sub_model_sync/service_base.rb +36 -0
- data/lib/pub_sub_model_sync/service_google.rb +6 -7
- data/lib/pub_sub_model_sync/service_kafka.rb +77 -0
- data/lib/pub_sub_model_sync/service_rabbit.rb +23 -15
- 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: 3016ebc69eba9ccb8b168734db1b947b77a4c9400f48d8228f979a7c1ec9eab8
|
4
|
+
data.tar.gz: 82187606c0411ece3eb0d78db59a8794019e80acd5374ffd92b95b0eee4368f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4335695f26d69f091a2ea1726221aab4b07b712827318adfe16f2d1275cde21a0860ce7562ed266f6a0661eb2b8d634abea0956a4478ad55f70e6c199c8a364
|
7
|
+
data.tar.gz: 9252e45ab27b332d9b50afc93da62a0a039c707a1bb3b219ff4e60c1c4f3dae8eb7a85024fc3d1dab87c006e448e60cf3d1f3d953dad8cc8fced24bd375b47cd
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# 0.2.3 (April 15, 2020)
|
4
|
+
- Improve helper names
|
5
|
+
- feat: perform manual sync with custom settings
|
6
|
+
- fix for "IO timeout when reading 7 bytes" error (Rabbit)
|
7
|
+
- style: do not print processed message when failed
|
8
|
+
- feat: retry delivery message when failed (RabbitMQ)
|
9
|
+
|
10
|
+
|
11
|
+
# 0.2.2 (March 27, 2020)
|
12
|
+
- fix default value for cattr_accessor in ror < 5.2
|
13
|
+
- add callbacks when publishing a message
|
14
|
+
|
15
|
+
# 0.2.1
|
16
|
+
- Add on demand model sync method
|
17
|
+
|
18
|
+
# 0.2.0
|
19
|
+
- Add apache kafka support
|
20
|
+
- Add Service interface for future references
|
21
|
+
- Improve Services to use a single/common message performer
|
22
|
+
|
3
23
|
# 0.1.4
|
4
24
|
- Add attribute aliases when publishing, ```ps_publish(['name:full_name', 'email'])```
|
5
25
|
- Ability to retrieve publisher/subscriber crud settings
|
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.3)
|
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,6 +45,14 @@ 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
58
|
- Start subscribers to listen for publishers (Only in the app that has subscribers)
|
@@ -73,7 +83,10 @@ end
|
|
73
83
|
|
74
84
|
# Samples
|
75
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.new(name: 'test user').ps_perform_sync(:create) # similar to above to perform sync on demand
|
87
|
+
|
76
88
|
User.ps_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
|
89
|
+
PubSubModelSync::Publisher.new.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
|
77
90
|
```
|
78
91
|
|
79
92
|
## Advanced Example
|
@@ -84,9 +97,13 @@ class User < ActiveRecord::Base
|
|
84
97
|
include PubSubModelSync::PublisherConcern
|
85
98
|
ps_publish(%i[name:full_name email], actions: %i[update], as_klass: 'Client', id: :client_id)
|
86
99
|
|
87
|
-
def
|
100
|
+
def ps_skip_callback?(_action)
|
88
101
|
false # here logic with action to skip push message
|
89
102
|
end
|
103
|
+
|
104
|
+
def ps_skip_sync?(_action)
|
105
|
+
false # here logic with action to skip push message
|
106
|
+
end
|
90
107
|
end
|
91
108
|
|
92
109
|
# App 2 (Subscriber)
|
@@ -102,6 +119,61 @@ class User < ActiveRecord::Base
|
|
102
119
|
end
|
103
120
|
```
|
104
121
|
|
122
|
+
Note: Be careful with collision of names
|
123
|
+
```
|
124
|
+
class User
|
125
|
+
# ps_publish %i[name_data:name name:key] # key will be replaced with name_data
|
126
|
+
ps_publish %i[name_data:name key_data:key] # use alias to avoid collision
|
127
|
+
|
128
|
+
def key_data
|
129
|
+
name
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
## API
|
135
|
+
- Permit to cancel sync called after create/update/destroy (Before initializing sync service)
|
136
|
+
```model.ps_skip_callback?(action)```
|
137
|
+
Note: Return true to cancel sync
|
138
|
+
|
139
|
+
- Callback called before preparing data for sync (Permit to stop sync)
|
140
|
+
```model.ps_skip_sync?(action)```
|
141
|
+
Note: return true to cancel sync
|
142
|
+
|
143
|
+
- Callback called before sync (After preparing data)
|
144
|
+
```model.ps_before_sync(action, data_to_deliver)```
|
145
|
+
Note: If the method returns ```:cancel```, the sync will be stopped (message will not be published)
|
146
|
+
|
147
|
+
- Callback called after sync
|
148
|
+
```model.ps_after_sync(action, data_delivered)```
|
149
|
+
|
150
|
+
- Perform sync on demand (:create, :update, :destroy):
|
151
|
+
The target model will receive a notification to perform the indicated action
|
152
|
+
```my_model.ps_perform_sync(action_name, custom_settings = {})```
|
153
|
+
* custom_settings: override default settings defined for action_name ({ attrs: [], as_klass: nil, id: nil })
|
154
|
+
|
155
|
+
- Class level notification:
|
156
|
+
```User.ps_class_publish(data, action: action_name, as_klass: custom_klass_name)```
|
157
|
+
Target class ```User.action_name``` will be called when message is received
|
158
|
+
* data: (required, :hash) message value to deliver
|
159
|
+
* action_name: (required, :sim) same action name as defined in ps_class_subscribe(...)
|
160
|
+
* as_klass: (optional, :string) same class name as defined in ps_class_subscribe(...)
|
161
|
+
|
162
|
+
- Class level notification (Same as above: on demand call)
|
163
|
+
```PubSubModelSync::Publisher.new.publish_data(Klass_name, data, action_name)```
|
164
|
+
* klass_name: (required, Class) same class name as defined in ps_class_subscribe(...)
|
165
|
+
* data: (required, :hash) message value to deliver
|
166
|
+
* action_name: (required, :sim) same action name as defined in ps_class_subscribe(...)
|
167
|
+
|
168
|
+
- Get crud subscription configured for the class
|
169
|
+
```User.ps_subscriber(action_name)```
|
170
|
+
* action_name (default :create, :sym): can be :create, :update, :destroy
|
171
|
+
- Get crud publisher configured for the class
|
172
|
+
```User.ps_publisher(action_name)```
|
173
|
+
* action_name (default :create, :sym): can be :create, :update, :destroy
|
174
|
+
- Inspect all configured listeners
|
175
|
+
```PubSubModelSync::Config.listeners```
|
176
|
+
|
105
177
|
## Testing with RSpec
|
106
178
|
- Config: (spec/rails_helper.rb)
|
107
179
|
```ruby
|
@@ -109,15 +181,22 @@ end
|
|
109
181
|
# when using google service
|
110
182
|
require 'pub_sub_model_sync/mock_google_service'
|
111
183
|
config.before(:each) do
|
112
|
-
|
113
|
-
allow(Google::Cloud::Pubsub).to receive(:new).and_return(
|
184
|
+
google_mock = PubSubModelSync::MockGoogleService.new
|
185
|
+
allow(Google::Cloud::Pubsub).to receive(:new).and_return(google_mock)
|
114
186
|
end
|
115
187
|
|
116
188
|
# when using rabbitmq service
|
117
189
|
require 'pub_sub_model_sync/mock_rabbit_service'
|
118
190
|
config.before(:each) do
|
119
|
-
|
120
|
-
allow(Bunny).to receive(:new).and_return(
|
191
|
+
rabbit_mock = PubSubModelSync::MockRabbitService.new
|
192
|
+
allow(Bunny).to receive(:new).and_return(rabbit_mock)
|
193
|
+
end
|
194
|
+
|
195
|
+
# when using apache kafka service
|
196
|
+
require 'pub_sub_model_sync/mock_kafka_service'
|
197
|
+
config.before(:each) do
|
198
|
+
kafka_mock = PubSubModelSync::MockKafkaService.new
|
199
|
+
allow(Kafka).to receive(:new).and_return(kafka_mock)
|
121
200
|
end
|
122
201
|
|
123
202
|
```
|
@@ -160,15 +239,6 @@ end
|
|
160
239
|
expect_any_instance_of(publisher).to receive(:publish_data).with('User', data, action)
|
161
240
|
end
|
162
241
|
```
|
163
|
-
|
164
|
-
There are two special methods to extract crud configuration settings (attrs, id, ...):
|
165
|
-
|
166
|
-
Subscribers: ```User.ps_subscriber```
|
167
|
-
|
168
|
-
Publishers: ```User.ps_publisher```
|
169
|
-
|
170
|
-
Note: Inspect all configured listeners with:
|
171
|
-
``` PubSubModelSync::Config.listeners ```
|
172
242
|
|
173
243
|
## Contributing
|
174
244
|
|
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
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Config
|
5
|
-
cattr_accessor
|
6
|
-
cattr_accessor
|
7
|
-
cattr_accessor
|
5
|
+
cattr_accessor(:listeners) { [] }
|
6
|
+
cattr_accessor(:publishers) { [] }
|
7
|
+
cattr_accessor(:service_name) { :google }
|
8
8
|
cattr_accessor :logger
|
9
9
|
|
10
10
|
# google service
|
@@ -13,6 +13,9 @@ module PubSubModelSync
|
|
13
13
|
# rabbitmq service
|
14
14
|
cattr_accessor :bunny_connection, :queue_name, :topic_name
|
15
15
|
|
16
|
+
# kafka service
|
17
|
+
cattr_accessor :kafka_connection, :topic_name
|
18
|
+
|
16
19
|
def self.log(msg, kind = :info)
|
17
20
|
msg = "PS_MSYNC ==> #{msg}"
|
18
21
|
if logger == :raise_error
|
@@ -13,10 +13,13 @@ module PubSubModelSync
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def process
|
16
|
+
@failed = false
|
16
17
|
log 'processing message'
|
17
18
|
listeners = filter_listeners
|
18
|
-
|
19
|
-
|
19
|
+
return log 'Skipped: No listeners' unless listeners.any?
|
20
|
+
|
21
|
+
eval_message(listeners)
|
22
|
+
log 'processed message' unless @failed
|
20
23
|
end
|
21
24
|
|
22
25
|
private
|
@@ -36,6 +39,7 @@ module PubSubModelSync
|
|
36
39
|
model_class.send(listener[:action], data)
|
37
40
|
rescue => e
|
38
41
|
log("Error listener (#{listener}): #{e.message}", :error)
|
42
|
+
@failed = true
|
39
43
|
end
|
40
44
|
|
41
45
|
# support for: create, update, destroy
|
@@ -49,13 +53,13 @@ module PubSubModelSync
|
|
49
53
|
end
|
50
54
|
rescue => e
|
51
55
|
log("Error listener (#{listener}): #{e.message}", :error)
|
56
|
+
@failed = true
|
52
57
|
end
|
53
58
|
|
54
59
|
def find_model(listener)
|
55
60
|
model_class = listener[:klass].constantize
|
56
61
|
identifier = listener[:settings][:id] || :id
|
57
|
-
model_class.where(identifier => attrs[:id]).
|
58
|
-
model_class.new(identifier => attrs[:id])
|
62
|
+
model_class.where(identifier => attrs[:id]).first_or_initialize
|
59
63
|
end
|
60
64
|
|
61
65
|
def populate_model(model, listener)
|
@@ -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,19 +14,24 @@ 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
|
+
return if model.ps_skip_sync?(action)
|
18
|
+
|
17
19
|
settings ||= model.class.ps_publisher_info(action)
|
18
20
|
attributes = build_model_attrs(model, action, settings)
|
19
21
|
data = {}
|
20
22
|
data = build_model_data(model, settings[:attrs]) if action != :destroy
|
23
|
+
res_before = model.ps_before_sync(action, data)
|
24
|
+
return if res_before == :cancel
|
25
|
+
|
21
26
|
connector.publish(data.symbolize_keys, attributes)
|
27
|
+
model.ps_after_sync(action, data)
|
22
28
|
end
|
23
29
|
|
24
30
|
def self.build_attrs(klass, action, id = nil)
|
25
31
|
{
|
26
32
|
klass: klass.to_s,
|
27
33
|
action: action.to_sym,
|
28
|
-
id: id
|
29
|
-
service_model_sync: true
|
34
|
+
id: id
|
30
35
|
}
|
31
36
|
end
|
32
37
|
|
@@ -6,11 +6,30 @@ module PubSubModelSync
|
|
6
6
|
base.extend(ClassMethods)
|
7
7
|
end
|
8
8
|
|
9
|
-
#
|
10
|
-
def
|
9
|
+
# Before initializing sync service (callbacks: after create/update/destroy)
|
10
|
+
def ps_skip_callback?(_action)
|
11
11
|
false
|
12
12
|
end
|
13
13
|
|
14
|
+
# before preparing data to sync
|
15
|
+
def ps_skip_sync?(_action)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
# before delivering data
|
20
|
+
def ps_before_sync(_action, _data); end
|
21
|
+
|
22
|
+
# after delivering data
|
23
|
+
def ps_after_sync(_action, _data); end
|
24
|
+
|
25
|
+
# To perform sync on demand
|
26
|
+
# @param custom_settings (Hash): { attrs: [], as_klass: nil, id: nil }
|
27
|
+
def ps_perform_sync(action = :create, custom_settings = {})
|
28
|
+
service = self.class.ps_publisher_service
|
29
|
+
model_settings = self.class.ps_publisher_info(action) || {}
|
30
|
+
service.publish_model(self, action, model_settings.merge(custom_settings))
|
31
|
+
end
|
32
|
+
|
14
33
|
module ClassMethods
|
15
34
|
# Permit to publish crud actions (:create, :update, :destroy)
|
16
35
|
# @param settings (Hash): { actions: nil, as_klass: nil, id: nil }
|
@@ -23,12 +42,14 @@ module PubSubModelSync
|
|
23
42
|
end
|
24
43
|
end
|
25
44
|
|
45
|
+
# Publisher info for specific action
|
26
46
|
def ps_publisher_info(action = :create)
|
27
47
|
PubSubModelSync::Config.publishers.select do |listener|
|
28
48
|
listener[:klass] == name && listener[:action] == action
|
29
49
|
end.last
|
30
50
|
end
|
31
51
|
|
52
|
+
# On demand class level publisher
|
32
53
|
def ps_class_publish(data, action:, as_klass: nil)
|
33
54
|
as_klass = (as_klass || name).to_s
|
34
55
|
ps_publisher_service.publish_data(as_klass, data, action.to_sym)
|
@@ -42,7 +63,7 @@ module PubSubModelSync
|
|
42
63
|
|
43
64
|
def ps_register_callback(action, info)
|
44
65
|
after_commit(on: action) do |model|
|
45
|
-
unless model.
|
66
|
+
unless model.ps_skip_callback?(action)
|
46
67
|
service = model.class.ps_publisher_service
|
47
68
|
service.publish_model(model, action.to_sym, info)
|
48
69
|
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
|
@@ -6,7 +6,7 @@ rescue LoadError # rubocop:disable Lint/SuppressedException
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
|
-
class ServiceGoogle
|
9
|
+
class ServiceGoogle < ServiceBase
|
10
10
|
attr_accessor :service, :topic, :subscription, :config, :subscriber
|
11
11
|
|
12
12
|
def initialize
|
@@ -30,7 +30,9 @@ module PubSubModelSync
|
|
30
30
|
|
31
31
|
def publish(data, attributes)
|
32
32
|
log("Publishing message: #{[data, attributes]}")
|
33
|
-
|
33
|
+
|
34
|
+
payload = { data: data, attributes: attributes }.to_json
|
35
|
+
topic.publish(payload, { SERVICE_KEY => true })
|
34
36
|
end
|
35
37
|
|
36
38
|
def stop
|
@@ -47,12 +49,9 @@ module PubSubModelSync
|
|
47
49
|
|
48
50
|
def process_message(received_message)
|
49
51
|
message = received_message.message
|
50
|
-
|
51
|
-
return unless attrs[:service_model_sync]
|
52
|
+
return unless message.attributes[SERVICE_KEY]
|
52
53
|
|
53
|
-
|
54
|
-
args = [data, attrs[:klass], attrs[:action], attrs]
|
55
|
-
PubSubModelSync::MessageProcessor.new(*args).process
|
54
|
+
perform_message(message.data)
|
56
55
|
rescue => e
|
57
56
|
log("Error processing message: #{[received_message, e.message]}", :error)
|
58
57
|
ensure
|
@@ -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
|
@@ -6,10 +6,9 @@ rescue LoadError # rubocop:disable Lint/SuppressedException
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module PubSubModelSync
|
9
|
-
class ServiceRabbit
|
9
|
+
class ServiceRabbit < ServiceBase
|
10
10
|
attr_accessor :service, :channel, :queue, :topic
|
11
11
|
attr_accessor :config
|
12
|
-
SERVICE_KEY = 'service_model_sync'
|
13
12
|
|
14
13
|
def initialize
|
15
14
|
@config = PubSubModelSync::Config
|
@@ -29,9 +28,12 @@ module PubSubModelSync
|
|
29
28
|
|
30
29
|
def publish(data, attributes)
|
31
30
|
log("Publishing: #{[data, attributes]}")
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
deliver_data(data, attributes)
|
32
|
+
# TODO: max retry
|
33
|
+
rescue Timeout::Error => e
|
34
|
+
log("Error publishing (retrying....): #{e.message}", :error)
|
35
|
+
initialize
|
36
|
+
retry
|
35
37
|
rescue => e
|
36
38
|
info = [data, attributes, e.message, e.backtrace]
|
37
39
|
log("Error publishing: #{info}", :error)
|
@@ -44,24 +46,19 @@ module PubSubModelSync
|
|
44
46
|
|
45
47
|
private
|
46
48
|
|
49
|
+
def message_settings
|
50
|
+
{ routing_key: queue.name, type: SERVICE_KEY }
|
51
|
+
end
|
52
|
+
|
47
53
|
def process_message(_delivery_info, meta_info, payload)
|
48
54
|
return unless meta_info[:type] == SERVICE_KEY
|
49
55
|
|
50
|
-
|
51
|
-
args = [data, attrs[:klass], attrs[:action], attrs]
|
52
|
-
PubSubModelSync::MessageProcessor.new(*args).process
|
56
|
+
perform_message(payload)
|
53
57
|
rescue => e
|
54
58
|
error = [payload, e.message, e.backtrace]
|
55
59
|
log("Error processing message: #{error}", :error)
|
56
60
|
end
|
57
61
|
|
58
|
-
def parse_message_payload(payload)
|
59
|
-
message_payload = JSON.parse(payload).symbolize_keys
|
60
|
-
data = message_payload[:data].symbolize_keys
|
61
|
-
attrs = message_payload[:attributes].symbolize_keys
|
62
|
-
[data, attrs]
|
63
|
-
end
|
64
|
-
|
65
62
|
def subscribe_to_queue
|
66
63
|
service.start
|
67
64
|
@channel = service.create_channel
|
@@ -78,5 +75,16 @@ module PubSubModelSync
|
|
78
75
|
def log(msg, kind = :info)
|
79
76
|
config.log("Rabbit Service ==> #{msg}", kind)
|
80
77
|
end
|
78
|
+
|
79
|
+
def deliver_data(data, attributes)
|
80
|
+
subscribe_to_queue
|
81
|
+
payload = { data: data, attributes: attributes }
|
82
|
+
topic.publish(payload.to_json, message_settings)
|
83
|
+
|
84
|
+
# Ugly fix: "IO timeout when reading 7 bytes"
|
85
|
+
# https://stackoverflow.com/questions/39039129/rabbitmq-timeouterror-io-timeout-when-reading-7-bytes
|
86
|
+
channel.close
|
87
|
+
service.close
|
88
|
+
end
|
81
89
|
end
|
82
90
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Owen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-15 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
|