pub_sub_model_sync 0.5.0 → 0.5.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 +18 -0
- data/Gemfile.lock +5 -5
- data/README.md +27 -15
- data/lib/pub_sub_model_sync/config.rb +6 -5
- data/lib/pub_sub_model_sync/message_processor.rb +12 -4
- data/lib/pub_sub_model_sync/message_publisher.rb +9 -6
- data/lib/pub_sub_model_sync/mock_rabbit_service.rb +4 -0
- data/lib/pub_sub_model_sync/publisher.rb +1 -1
- data/lib/pub_sub_model_sync/publisher_concern.rb +2 -3
- data/lib/pub_sub_model_sync/service_google.rb +2 -2
- data/lib/pub_sub_model_sync/service_kafka.rb +4 -4
- data/lib/pub_sub_model_sync/service_rabbit.rb +8 -3
- data/lib/pub_sub_model_sync/subscriber.rb +20 -16
- data/lib/pub_sub_model_sync/subscriber_concern.rb +4 -0
- data/lib/pub_sub_model_sync/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 608c6430e2e37529a6a219d09db34b7a540d89bed73da7e272c1624ad7ba821f
|
4
|
+
data.tar.gz: 99eaf893d6c66d3e7429d6ba8758b7e44d9ca0a29069b72f260bccecd7a207f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9a0d769a2c18b9097d7f9d4b10f8f11567e743a39bcf10fe39ed4bd3daf156fb028685be4f8100d92d9bb0fd8ea742a1c0a5dca8fe0743c0c4e21b3b23fd552
|
7
|
+
data.tar.gz: 4ff19cc02656fd8ddae04e061808c89ec61eae3e86cd889a3c2abc3fed0c2975ed8eed995363ad834d294f75ac340fdeb454c1de21e05e2f4080458664f8d181
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# 0.5.3 (December 30, 2020)
|
4
|
+
- fix: kafka consume all messages from different apps
|
5
|
+
- style: use the correct consumer key
|
6
|
+
|
7
|
+
# 0.5.2 (December 30, 2020)
|
8
|
+
- fix: rabbitmq deliver messages to all subscribers
|
9
|
+
- fix: rabbitmq persist messages to recover after restarting
|
10
|
+
|
11
|
+
# 0.5.1.1 (December 29, 2020)
|
12
|
+
- Hotfix: auto convert class name into string
|
13
|
+
|
14
|
+
# 0.5.1 (December 24, 2020)
|
15
|
+
- feat: rename publisher callbacks to be more understandable
|
16
|
+
- feat: add callbacks to listen when processing a message (before saving sync)
|
17
|
+
|
18
|
+
# 0.5.0.1 (December 22, 2020)
|
19
|
+
- fix: add missing rabbit mock method
|
20
|
+
|
3
21
|
# 0.5.0 (December 22, 2020)
|
4
22
|
- feat: add :publish! and :process! methods to payloads
|
5
23
|
- feat: add ability to disable publisher globally
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pub_sub_model_sync (0.5.
|
4
|
+
pub_sub_model_sync (0.5.3)
|
5
5
|
rails
|
6
6
|
|
7
7
|
GEM
|
@@ -77,7 +77,7 @@ GEM
|
|
77
77
|
database_cleaner (~> 1.8.0)
|
78
78
|
diff-lcs (1.3)
|
79
79
|
digest-crc (0.5.1)
|
80
|
-
erubi (1.
|
80
|
+
erubi (1.10.0)
|
81
81
|
faraday (0.17.3)
|
82
82
|
multipart-post (>= 1.2, < 3)
|
83
83
|
globalid (0.4.2)
|
@@ -121,7 +121,7 @@ GEM
|
|
121
121
|
i18n (1.8.2)
|
122
122
|
concurrent-ruby (~> 1.0)
|
123
123
|
jwt (2.2.1)
|
124
|
-
loofah (2.
|
124
|
+
loofah (2.8.0)
|
125
125
|
crass (~> 1.0.2)
|
126
126
|
nokogiri (>= 1.5.9)
|
127
127
|
mail (2.7.1)
|
@@ -136,7 +136,7 @@ GEM
|
|
136
136
|
minitest (5.14.0)
|
137
137
|
multi_json (1.14.1)
|
138
138
|
multipart-post (2.1.1)
|
139
|
-
nio4r (2.5.
|
139
|
+
nio4r (2.5.4)
|
140
140
|
nokogiri (1.10.10)
|
141
141
|
mini_portile2 (~> 2.4.0)
|
142
142
|
os (1.0.1)
|
@@ -213,7 +213,7 @@ GEM
|
|
213
213
|
sprockets (4.0.2)
|
214
214
|
concurrent-ruby (~> 1.0)
|
215
215
|
rack (> 1, < 3)
|
216
|
-
sprockets-rails (3.2.
|
216
|
+
sprockets-rails (3.2.2)
|
217
217
|
actionpack (>= 4.0)
|
218
218
|
activesupport (>= 4.0)
|
219
219
|
sprockets (>= 3.0.0)
|
data/README.md
CHANGED
@@ -28,10 +28,9 @@ And then execute: $ bundle install
|
|
28
28
|
```ruby
|
29
29
|
# initializers/pub_sub_config.rb
|
30
30
|
PubSubModelSync::Config.service_name = :google
|
31
|
-
PubSubModelSync::Config.project = 'project-id'
|
31
|
+
PubSubModelSync::Config.project = 'google-project-id'
|
32
32
|
PubSubModelSync::Config.credentials = 'path-to-the-config'
|
33
33
|
PubSubModelSync::Config.topic_name = 'sample-topic'
|
34
|
-
PubSubModelSync::Config.subscription_name = 'p1-subscriber'
|
35
34
|
```
|
36
35
|
See details here:
|
37
36
|
https://github.com/googleapis/google-cloud-ruby/tree/master/google-cloud-pubsub
|
@@ -40,7 +39,7 @@ And then execute: $ bundle install
|
|
40
39
|
```ruby
|
41
40
|
PubSubModelSync::Config.service_name = :rabbitmq
|
42
41
|
PubSubModelSync::Config.bunny_connection = 'amqp://guest:guest@localhost'
|
43
|
-
PubSubModelSync::Config.queue_name = ''
|
42
|
+
PubSubModelSync::Config.queue_name = 'model-sync'
|
44
43
|
PubSubModelSync::Config.topic_name = 'sample-topic'
|
45
44
|
```
|
46
45
|
See details here: https://github.com/ruby-amqp/bunny
|
@@ -48,7 +47,7 @@ And then execute: $ bundle install
|
|
48
47
|
- configuration for Apache Kafka (You need kafka installed)
|
49
48
|
```ruby
|
50
49
|
PubSubModelSync::Config.service_name = :kafka
|
51
|
-
PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], logger: Rails.logger]
|
50
|
+
PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], { logger: Rails.logger }]
|
52
51
|
PubSubModelSync::Config.topic_name = 'sample-topic'
|
53
52
|
```
|
54
53
|
See details here: https://github.com/zendesk/ruby-kafka
|
@@ -59,7 +58,13 @@ And then execute: $ bundle install
|
|
59
58
|
```ruby
|
60
59
|
rake pub_sub_model_sync:start
|
61
60
|
```
|
62
|
-
Note: Publishers do not need todo this
|
61
|
+
Note: Publishers do not need todo this
|
62
|
+
Note2 (Rails 6+): Due to Zeitwerk, you need to load listeners manually when syncing outside ```rake pub_sub_model_sync:start```
|
63
|
+
```ruby
|
64
|
+
# PubSubModelSync::Config.subscribers ==> []
|
65
|
+
Rails.application.try(:eager_load!)
|
66
|
+
# PubSubModelSync::Config.subscribers ==> [#<PubSubModelSync::Subscriber:0x000.. @klass="Article", @action=:create..., ....]
|
67
|
+
```
|
63
68
|
|
64
69
|
- Check the service status with:
|
65
70
|
```PubSubModelSync::MessagePublisher.publish_data('Test message', {sample_value: 10}, :create)```
|
@@ -163,6 +168,9 @@ Note: Be careful with collision of names
|
|
163
168
|
```.ps_subscriber_changed?(data)```
|
164
169
|
By default: ```model.changed?```
|
165
170
|
|
171
|
+
- Permit to perform custom actions before saving sync of the model (:cancel can be returned to skip sync)
|
172
|
+
```.ps_before_save_sync(payload)```
|
173
|
+
|
166
174
|
### Publishers
|
167
175
|
- Permit to configure crud publishers
|
168
176
|
```ps_publish(attrs, actions: nil, as_klass: nil)```
|
@@ -274,21 +282,25 @@ config = PubSubModelSync::Config
|
|
274
282
|
config.debug = true
|
275
283
|
```
|
276
284
|
|
277
|
-
-
|
285
|
+
- ```.subscription_name = 'app-2'```
|
286
|
+
Permit to define a custom consumer identifier (Default: Rails application name)
|
287
|
+
- ```.debug = true```
|
278
288
|
(true/false*) => show advanced log messages
|
279
|
-
-
|
289
|
+
- ```.logger = Rails.logger```
|
280
290
|
(Logger) => define custom logger
|
281
|
-
-
|
282
|
-
(true/false*) => if true, does not
|
283
|
-
-
|
291
|
+
- ```.disabled_callback_publisher = ->(_model, _action) { false }```
|
292
|
+
(true/false*) => if true, does not listen model callbacks for auto sync (Create/Update/Destroy)
|
293
|
+
- ```.on_before_processing = ->(payload, subscriber) { puts payload }```
|
294
|
+
(Proc) => called before processing received message (:cancel can be returned to skip processing)
|
295
|
+
- ```.on_success_processing = ->(payload, subscriber) { puts payload }```
|
284
296
|
(Proc) => called when a message was successfully processed
|
285
|
-
-
|
297
|
+
- ```.on_error_processing = ->(exception, payload) { sleep 1; payload.process! }```
|
286
298
|
(Proc) => called when a message failed when processing
|
287
|
-
-
|
288
|
-
(Proc) => called before publishing a message
|
289
|
-
-
|
299
|
+
- ```.on_before_publish = ->(payload) { puts payload }```
|
300
|
+
(Proc) => called before publishing a message (:cancel can be returned to skip publishing)
|
301
|
+
- ```.on_after_publish = ->(payload) { puts payload }```
|
290
302
|
(Proc) => called after publishing a message
|
291
|
-
-
|
303
|
+
- ```.on_error_publish = ->(exception, payload) { sleep 1; payload.publish! }```
|
292
304
|
(Proc) => called when failed publishing a message
|
293
305
|
|
294
306
|
## TODO
|
@@ -10,12 +10,13 @@ module PubSubModelSync
|
|
10
10
|
cattr_accessor(:debug) { false }
|
11
11
|
cattr_accessor :logger # LoggerInst
|
12
12
|
|
13
|
-
cattr_accessor(:
|
14
|
-
cattr_accessor(:
|
15
|
-
cattr_accessor(:
|
13
|
+
cattr_accessor(:on_before_processing) { ->(_payload, _subscriber) {} } # return :cancel to skip
|
14
|
+
cattr_accessor(:on_success_processing) { ->(_payload, _subscriber) {} }
|
15
|
+
cattr_accessor(:on_error_processing) { ->(_exception, _payload) {} }
|
16
|
+
cattr_accessor(:on_before_publish) { ->(_payload) {} } # return :cancel to skip
|
16
17
|
cattr_accessor(:on_after_publish) { ->(_payload) {} }
|
17
|
-
cattr_accessor(:
|
18
|
-
cattr_accessor(:
|
18
|
+
cattr_accessor(:on_error_publish) { ->(_exception, _payload) {} }
|
19
|
+
cattr_accessor(:disabled_callback_publisher) { ->(_model, _action) { false } }
|
19
20
|
|
20
21
|
# google service
|
21
22
|
cattr_accessor :project, :credentials, :topic_name, :subscription_name
|
@@ -22,17 +22,25 @@ module PubSubModelSync
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def run_subscriber(subscriber)
|
25
|
-
subscriber
|
26
|
-
|
27
|
-
|
25
|
+
return unless processable?(subscriber)
|
26
|
+
|
27
|
+
subscriber.process!(payload)
|
28
|
+
res = config.on_success_processing.call(payload, subscriber)
|
29
|
+
log "processed message with: #{payload.inspect}" if res != :skip_log
|
28
30
|
rescue => e
|
29
31
|
print_subscriber_error(e)
|
30
32
|
end
|
31
33
|
|
34
|
+
def processable?(subscriber)
|
35
|
+
cancel = config.on_before_processing.call(payload, subscriber) == :cancel
|
36
|
+
log("process message cancelled: #{payload}") if cancel && config.debug
|
37
|
+
!cancel
|
38
|
+
end
|
39
|
+
|
32
40
|
# @param error (Error)
|
33
41
|
def print_subscriber_error(error)
|
34
42
|
info = [payload, error.message, error.backtrace]
|
35
|
-
res = config.
|
43
|
+
res = config.on_error_processing.call(error, payload)
|
36
44
|
log("Error processing message: #{info}", :error) if res != :skip_log
|
37
45
|
end
|
38
46
|
|
@@ -8,7 +8,7 @@ module PubSubModelSync
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def publish_data(klass, data, action)
|
11
|
-
payload = PubSubModelSync::Payload.new(data, { klass: klass, action: action.to_sym })
|
11
|
+
payload = PubSubModelSync::Payload.new(data, { klass: klass.to_s, action: action.to_sym })
|
12
12
|
publish(payload)
|
13
13
|
end
|
14
14
|
|
@@ -19,8 +19,7 @@ module PubSubModelSync
|
|
19
19
|
return if model.ps_skip_sync?(action)
|
20
20
|
|
21
21
|
publisher ||= model.class.ps_publisher(action)
|
22
|
-
|
23
|
-
payload = PubSubModelSync::Payload.new(payload_info[:data], payload_info[:attrs])
|
22
|
+
payload = publisher.payload(model, action)
|
24
23
|
res_before = model.ps_before_sync(action, payload.data)
|
25
24
|
return if res_before == :cancel
|
26
25
|
|
@@ -29,8 +28,12 @@ module PubSubModelSync
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def publish(payload)
|
32
|
-
|
33
|
-
|
31
|
+
if config.on_before_publish.call(payload) == :cancel
|
32
|
+
log("Publish message cancelled: #{payload}") if config.debug
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
log("Publishing message: #{[payload]}")
|
34
37
|
connector.publish(payload)
|
35
38
|
config.on_after_publish.call(payload)
|
36
39
|
rescue => e
|
@@ -41,7 +44,7 @@ module PubSubModelSync
|
|
41
44
|
|
42
45
|
def notify_error(exception, payload)
|
43
46
|
info = [payload, exception.message, exception.backtrace]
|
44
|
-
res = config.
|
47
|
+
res = config.on_error_publish.call(exception, payload)
|
45
48
|
log("Error publishing: #{info}", :error) if res != :skip_log
|
46
49
|
end
|
47
50
|
end
|
@@ -11,13 +11,12 @@ module PubSubModelSync
|
|
11
11
|
false
|
12
12
|
end
|
13
13
|
|
14
|
-
# TODO: make it using respond_to?(:ps_skip_sync?)
|
15
14
|
# before preparing data to sync
|
16
15
|
def ps_skip_sync?(_action)
|
17
16
|
false
|
18
17
|
end
|
19
18
|
|
20
|
-
# before delivering data
|
19
|
+
# before delivering data (return :cancel to cancel sync)
|
21
20
|
def ps_before_sync(_action, _data); end
|
22
21
|
|
23
22
|
# after delivering data
|
@@ -64,7 +63,7 @@ module PubSubModelSync
|
|
64
63
|
|
65
64
|
def ps_register_callback(action, publisher)
|
66
65
|
after_commit(on: action) do |model|
|
67
|
-
disabled = PubSubModelSync::Config.
|
66
|
+
disabled = PubSubModelSync::Config.disabled_callback_publisher.call(model, action)
|
68
67
|
if !disabled && !model.ps_skip_callback?(action)
|
69
68
|
klass = PubSubModelSync::MessagePublisher
|
70
69
|
klass.publish_model(model, action.to_sym, publisher)
|
@@ -39,8 +39,8 @@ module PubSubModelSync
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def subscribe_to_topic
|
42
|
-
topic.subscription(config.
|
43
|
-
topic.subscribe(config.
|
42
|
+
topic.subscription(config.subscription_key) ||
|
43
|
+
topic.subscribe(config.subscription_key)
|
44
44
|
end
|
45
45
|
|
46
46
|
def process_message(received_message)
|
@@ -10,11 +10,11 @@ module PubSubModelSync
|
|
10
10
|
cattr_accessor :producer
|
11
11
|
attr_accessor :config, :service, :consumer
|
12
12
|
|
13
|
-
CONSUMER_GROUP = 'service_model_sync'
|
14
|
-
|
15
13
|
def initialize
|
16
14
|
@config = PubSubModelSync::Config
|
17
|
-
|
15
|
+
settings = config.kafka_connection
|
16
|
+
settings[1][:client_id] ||= config.subscription_key
|
17
|
+
@service = Kafka.new(*settings)
|
18
18
|
end
|
19
19
|
|
20
20
|
def listen_messages
|
@@ -44,7 +44,7 @@ module PubSubModelSync
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def start_consumer
|
47
|
-
@consumer = service.consumer(group_id:
|
47
|
+
@consumer = service.consumer(group_id: config.subscription_key)
|
48
48
|
consumer.subscribe(config.topic_name)
|
49
49
|
end
|
50
50
|
|
@@ -40,6 +40,7 @@ module PubSubModelSync
|
|
40
40
|
|
41
41
|
def stop
|
42
42
|
log('Listener stopping...')
|
43
|
+
channel&.close
|
43
44
|
service.close
|
44
45
|
end
|
45
46
|
|
@@ -48,10 +49,15 @@ module PubSubModelSync
|
|
48
49
|
def message_settings
|
49
50
|
{
|
50
51
|
routing_key: queue.name,
|
51
|
-
type: SERVICE_KEY
|
52
|
+
type: SERVICE_KEY,
|
53
|
+
persistent: true
|
52
54
|
}
|
53
55
|
end
|
54
56
|
|
57
|
+
def queue_settings
|
58
|
+
{ durable: true, auto_delete: false }
|
59
|
+
end
|
60
|
+
|
55
61
|
def subscribe_settings
|
56
62
|
{ manual_ack: false }
|
57
63
|
end
|
@@ -65,8 +71,7 @@ module PubSubModelSync
|
|
65
71
|
def subscribe_to_queue
|
66
72
|
service.start
|
67
73
|
@channel = service.create_channel
|
68
|
-
|
69
|
-
@queue = channel.queue(config.queue_name, queue_settings)
|
74
|
+
@queue = channel.queue(config.subscription_key, queue_settings)
|
70
75
|
subscribe_to_exchange
|
71
76
|
end
|
72
77
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Subscriber
|
5
5
|
attr_accessor :klass, :action, :attrs, :settings
|
6
|
+
attr_reader :payload
|
6
7
|
|
7
8
|
# @param settings: (Hash) { id: :id, direct_mode: false,
|
8
9
|
# from_klass: klass, from_action: action }
|
@@ -15,48 +16,51 @@ module PubSubModelSync
|
|
15
16
|
@settings = def_settings.merge(settings)
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
+
def process!(payload)
|
20
|
+
@payload = payload
|
19
21
|
if settings[:direct_mode]
|
20
|
-
run_class_message
|
22
|
+
run_class_message
|
21
23
|
else
|
22
|
-
run_model_message
|
24
|
+
run_model_message
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
28
|
private
|
27
29
|
|
28
|
-
def run_class_message
|
30
|
+
def run_class_message
|
29
31
|
model_class = klass.constantize
|
30
|
-
model_class.send(action,
|
32
|
+
model_class.send(action, payload.data)
|
31
33
|
end
|
32
34
|
|
33
35
|
# support for: create, update, destroy
|
34
|
-
def run_model_message
|
35
|
-
model = find_model
|
36
|
+
def run_model_message
|
37
|
+
model = find_model
|
38
|
+
return if model.ps_before_save_sync(payload) == :cancel
|
39
|
+
|
36
40
|
if action == :destroy
|
37
41
|
model.destroy!
|
38
42
|
else
|
39
|
-
populate_model(model
|
40
|
-
return if action == :update && !model.ps_subscriber_changed?(
|
43
|
+
populate_model(model)
|
44
|
+
return if action == :update && !model.ps_subscriber_changed?(payload.data)
|
41
45
|
|
42
46
|
model.save!
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
|
-
def find_model
|
50
|
+
def find_model
|
47
51
|
model_class = klass.constantize
|
48
|
-
return model_class.ps_find_model(
|
52
|
+
return model_class.ps_find_model(payload.data) if model_class.respond_to?(:ps_find_model)
|
49
53
|
|
50
|
-
model_class.where(model_identifiers
|
54
|
+
model_class.where(model_identifiers).first_or_initialize
|
51
55
|
end
|
52
56
|
|
53
|
-
def model_identifiers
|
57
|
+
def model_identifiers
|
54
58
|
identifiers = Array(settings[:id])
|
55
|
-
identifiers.map { |key| [key,
|
59
|
+
identifiers.map { |key| [key, payload.data[key.to_sym]] }.to_h
|
56
60
|
end
|
57
61
|
|
58
|
-
def populate_model(model
|
59
|
-
values =
|
62
|
+
def populate_model(model)
|
63
|
+
values = payload.data.slice(*attrs)
|
60
64
|
values.each do |attr, value|
|
61
65
|
model.send("#{attr}=", value)
|
62
66
|
end
|
@@ -12,6 +12,10 @@ module PubSubModelSync
|
|
12
12
|
changed?
|
13
13
|
end
|
14
14
|
|
15
|
+
# permit to apply custom actions before applying sync
|
16
|
+
# @return (nil|:cancel): nil to continue sync OR :cancel to skip sync
|
17
|
+
def ps_before_save_sync(_payload); end
|
18
|
+
|
15
19
|
module ClassMethods
|
16
20
|
def ps_subscribe(attrs, actions: nil, from_klass: name, id: :id)
|
17
21
|
settings = { id: id, from_klass: from_klass }
|
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.5.
|
4
|
+
version: 0.5.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-12-
|
11
|
+
date: 2020-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|