pub_sub_model_sync 0.5.4.1 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +45 -39
- data/README.md +29 -6
- data/lib/pub_sub_model_sync/mock_google_service.rb +8 -0
- data/lib/pub_sub_model_sync/payload.rb +22 -0
- data/lib/pub_sub_model_sync/runner.rb +7 -6
- data/lib/pub_sub_model_sync/service_base.rb +1 -1
- data/lib/pub_sub_model_sync/service_google.rb +12 -3
- 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: 14095a129de1a44dd2f51051e3ee768217eae9ddbaa69b2e9bb3979d857dde5a
|
4
|
+
data.tar.gz: eeff964335f21b2bc5db23048cf0a09cb8e4d385333f72cf431d9201e107737b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f85d85ac9f5de88af18848c8511e113a2359c5135b4dba8904e4bbc2b1f72b7be526c9fcdd221761202e9ef8d4630d84e08e9a5d8aa0c2753f98046a514fb70
|
7
|
+
data.tar.gz: 4788d133e2eaf6b302d34898d5f0d7002556edb357c907f280df137d686c71d7280503a763180f7fb61dece830c41f1263a7d569a868fad0082d13062e7caccd
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,23 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# 0.5.8 (January 29, 2021)
|
4
|
+
- fix: keep message ordering with google pubsub
|
5
|
+
|
6
|
+
# 0.5.7.1 (January 26, 2021)
|
7
|
+
- fix: does not call :on_error_processing when processing a message
|
8
|
+
|
9
|
+
# 0.5.7 (January 13, 2021)
|
10
|
+
- feat: add method to preload sync listeners
|
11
|
+
|
12
|
+
# 0.5.6 (January 12, 2021)
|
13
|
+
- feat: add payload validation
|
14
|
+
- feat: add method to rebuild payload
|
15
|
+
|
16
|
+
# 0.5.5 (January 11, 2021)
|
17
|
+
- feat: google-pub/sub receive messages in the same order they were delivered
|
18
|
+
|
3
19
|
# 0.5.4.1 (January 8, 2021)
|
4
|
-
- fix:
|
20
|
+
- fix: google-pub/sub receive messages sequentially and not in parallel (default 5 threads).
|
5
21
|
|
6
22
|
# 0.5.4 (January 8, 2021)
|
7
23
|
- fix: exclude identifiers when syncing model
|
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.5.
|
4
|
+
pub_sub_model_sync (0.5.8)
|
5
5
|
rails
|
6
6
|
|
7
7
|
GEM
|
@@ -78,49 +78,55 @@ GEM
|
|
78
78
|
diff-lcs (1.3)
|
79
79
|
digest-crc (0.5.1)
|
80
80
|
erubi (1.10.0)
|
81
|
-
faraday (
|
81
|
+
faraday (1.1.0)
|
82
82
|
multipart-post (>= 1.2, < 3)
|
83
|
+
ruby2_keywords
|
84
|
+
gapic-common (0.3.4)
|
85
|
+
google-protobuf (~> 3.12, >= 3.12.2)
|
86
|
+
googleapis-common-protos (>= 1.3.9, < 2.0)
|
87
|
+
googleapis-common-protos-types (>= 1.0.4, < 2.0)
|
88
|
+
googleauth (~> 0.9)
|
89
|
+
grpc (~> 1.25)
|
83
90
|
globalid (0.4.2)
|
84
91
|
activesupport (>= 4.2.0)
|
85
|
-
google-cloud-core (1.
|
92
|
+
google-cloud-core (1.5.0)
|
86
93
|
google-cloud-env (~> 1.0)
|
87
|
-
|
88
|
-
|
89
|
-
|
94
|
+
google-cloud-errors (~> 1.0)
|
95
|
+
google-cloud-env (1.4.0)
|
96
|
+
faraday (>= 0.17.3, < 2.0)
|
97
|
+
google-cloud-errors (1.0.1)
|
98
|
+
google-cloud-pubsub (2.3.0)
|
90
99
|
concurrent-ruby (~> 1.1)
|
91
|
-
google-cloud-core (~> 1.
|
92
|
-
google-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
google-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
googleapis-common-protos (1.
|
103
|
-
google-protobuf (~> 3.
|
104
|
-
|
105
|
-
|
106
|
-
googleapis-common-protos-types (1.0.4)
|
107
|
-
google-protobuf (~> 3.0)
|
108
|
-
googleauth (0.9.0)
|
109
|
-
faraday (~> 0.12)
|
100
|
+
google-cloud-core (~> 1.5)
|
101
|
+
google-cloud-pubsub-v1 (~> 0.0)
|
102
|
+
google-cloud-pubsub-v1 (0.1.2)
|
103
|
+
gapic-common (~> 0.3)
|
104
|
+
google-cloud-errors (~> 1.0)
|
105
|
+
grpc-google-iam-v1 (>= 0.6.10, < 2.0)
|
106
|
+
google-protobuf (3.14.0-universal-darwin)
|
107
|
+
googleapis-common-protos (1.3.10)
|
108
|
+
google-protobuf (~> 3.11)
|
109
|
+
googleapis-common-protos-types (>= 1.0.5, < 2.0)
|
110
|
+
grpc (~> 1.27)
|
111
|
+
googleapis-common-protos-types (1.0.5)
|
112
|
+
google-protobuf (~> 3.11)
|
113
|
+
googleauth (0.14.0)
|
114
|
+
faraday (>= 0.17.3, < 2.0)
|
110
115
|
jwt (>= 1.4, < 3.0)
|
111
116
|
memoist (~> 0.16)
|
112
117
|
multi_json (~> 1.11)
|
113
118
|
os (>= 0.9, < 2.0)
|
114
|
-
signet (~> 0.
|
115
|
-
grpc (1.
|
116
|
-
google-protobuf (~> 3.
|
119
|
+
signet (~> 0.14)
|
120
|
+
grpc (1.34.0-universal-darwin)
|
121
|
+
google-protobuf (~> 3.13)
|
117
122
|
googleapis-common-protos-types (~> 1.0)
|
118
|
-
grpc-google-iam-v1 (0.6.
|
119
|
-
|
120
|
-
|
123
|
+
grpc-google-iam-v1 (0.6.10)
|
124
|
+
google-protobuf (~> 3.11)
|
125
|
+
googleapis-common-protos (>= 1.3.10, < 2.0)
|
126
|
+
grpc (~> 1.27)
|
121
127
|
i18n (1.8.2)
|
122
128
|
concurrent-ruby (~> 1.0)
|
123
|
-
jwt (2.2.
|
129
|
+
jwt (2.2.2)
|
124
130
|
loofah (2.8.0)
|
125
131
|
crass (~> 1.0.2)
|
126
132
|
nokogiri (>= 1.5.9)
|
@@ -134,16 +140,16 @@ GEM
|
|
134
140
|
mini_mime (1.0.2)
|
135
141
|
mini_portile2 (2.4.0)
|
136
142
|
minitest (5.14.0)
|
137
|
-
multi_json (1.
|
143
|
+
multi_json (1.15.0)
|
138
144
|
multipart-post (2.1.1)
|
139
145
|
nio4r (2.5.4)
|
140
146
|
nokogiri (1.10.10)
|
141
147
|
mini_portile2 (~> 2.4.0)
|
142
|
-
os (1.
|
148
|
+
os (1.1.1)
|
143
149
|
parallel (1.20.1)
|
144
150
|
parser (2.7.2.0)
|
145
151
|
ast (~> 2.4.1)
|
146
|
-
public_suffix (4.0.
|
152
|
+
public_suffix (4.0.6)
|
147
153
|
rack (2.2.3)
|
148
154
|
rack-test (1.1.0)
|
149
155
|
rack (>= 1.0, < 3)
|
@@ -177,7 +183,6 @@ GEM
|
|
177
183
|
rake (13.0.1)
|
178
184
|
regexp_parser (2.0.1)
|
179
185
|
rexml (3.2.4)
|
180
|
-
rly (0.2.3)
|
181
186
|
rspec (3.9.0)
|
182
187
|
rspec-core (~> 3.9.0)
|
183
188
|
rspec-expectations (~> 3.9.0)
|
@@ -205,9 +210,10 @@ GEM
|
|
205
210
|
ruby-kafka (1.0.0)
|
206
211
|
digest-crc
|
207
212
|
ruby-progressbar (1.10.1)
|
208
|
-
|
213
|
+
ruby2_keywords (0.0.2)
|
214
|
+
signet (0.14.0)
|
209
215
|
addressable (~> 2.3)
|
210
|
-
faraday (
|
216
|
+
faraday (>= 0.17.3, < 2.0)
|
211
217
|
jwt (>= 1.5, < 3.0)
|
212
218
|
multi_json (~> 1.10)
|
213
219
|
sprockets (4.0.2)
|
@@ -235,7 +241,7 @@ DEPENDENCIES
|
|
235
241
|
bundler
|
236
242
|
bunny
|
237
243
|
database_cleaner-active_record
|
238
|
-
google-cloud-pubsub
|
244
|
+
google-cloud-pubsub (> 2.0)
|
239
245
|
pub_sub_model_sync!
|
240
246
|
rake
|
241
247
|
rspec
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ 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
|
+
gem 'google-cloud-pubsub', '>= 1.9' # to use google pub/sub service
|
19
19
|
gem 'bunny' # to use rabbit-mq pub/sub service
|
20
20
|
gem 'ruby-kafka' # to use apache kafka pub/sub service
|
21
21
|
```
|
@@ -59,10 +59,10 @@ And then execute: $ bundle install
|
|
59
59
|
rake pub_sub_model_sync:start
|
60
60
|
```
|
61
61
|
Note: Publishers do not need todo this
|
62
|
-
Note2 (Rails 6+): Due to Zeitwerk, you need to load listeners manually when syncing
|
62
|
+
Note2 (Rails 6+): Due to Zeitwerk, you need to load listeners manually when syncing without mentioned task (like rails console)
|
63
63
|
```ruby
|
64
64
|
# PubSubModelSync::Config.subscribers ==> []
|
65
|
-
|
65
|
+
PubSubModelSync::Runner.preload_listeners
|
66
66
|
# PubSubModelSync::Config.subscribers ==> [#<PubSubModelSync::Subscriber:0x000.. @klass="Article", @action=:create..., ....]
|
67
67
|
```
|
68
68
|
|
@@ -308,9 +308,11 @@ config.debug = true
|
|
308
308
|
|
309
309
|
## TODO
|
310
310
|
- Add alias attributes when subscribing (similar to publisher)
|
311
|
-
- Add flag ```model.
|
311
|
+
- Add flag ```model.ps_process_payload``` to retrieve the payload used to process the pub/sub sync
|
312
312
|
- Auto publish update only if payload has changed
|
313
|
-
- On delete, payload must only be composed by ids
|
313
|
+
- On delete, payload must only be composed by ids
|
314
|
+
- Feature to publish multiple message at a time with the ability to exclude similar messages by klass and action (use the last one)
|
315
|
+
PubSubModelSync::MessagePublisher.batch_publish({ same_keys: :use_last_as_first|:use_last|:use_first_as_last|:keep*, same_data: :use_last_as_first*|:use_last|:use_first_as_last|:keep })
|
314
316
|
|
315
317
|
## Q&A
|
316
318
|
- Error "could not obtain a connection from the pool within 5.000 seconds"
|
@@ -318,7 +320,28 @@ config.debug = true
|
|
318
320
|
To fix the problem, edit config/database.yml and increase the quantity of ```pool: 10```
|
319
321
|
- Google pubsub: How to process notifications parallely and not sequentially (default 1 thread)?
|
320
322
|
```ruby PubSubModelSync::ServiceGoogle::LISTEN_SETTINGS = { threads: { callback: qty_threads } } ```
|
321
|
-
Note: by this way some notifications can be processed before others thus missing relationship errors can appear
|
323
|
+
Note: by this way some notifications can be processed before others thus missing relationship errors can appear
|
324
|
+
- How to retry failed syncs with sidekiq?
|
325
|
+
```ruby
|
326
|
+
# lib/initializers/pub_sub_config.rb
|
327
|
+
|
328
|
+
class PubSubRecovery
|
329
|
+
include Sidekiq::Worker
|
330
|
+
sidekiq_options queue: :pubsub, retry: 2, backtrace: true
|
331
|
+
|
332
|
+
def perform(payload_data, action)
|
333
|
+
payload = PubSubModelSync::Payload.from_payload_data(payload_data)
|
334
|
+
payload.send(action)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
PubSubModelSync::Config.on_error_publish = lambda do |_e, data|
|
339
|
+
PubSubRecovery.perform_async(data[:payload].to_h, :publish!)
|
340
|
+
end
|
341
|
+
PubSubModelSync::Config.on_error_processing = lambda do |_e, data|
|
342
|
+
PubSubRecovery.perform_async(data[:payload].to_h, :process!)
|
343
|
+
end
|
344
|
+
```
|
322
345
|
|
323
346
|
## Contributing
|
324
347
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module PubSubModelSync
|
4
4
|
class Payload
|
5
|
+
class MissingInfo < StandardError; end
|
5
6
|
attr_reader :data, :attributes, :headers
|
6
7
|
|
7
8
|
# @param data (Hash: { any value }):
|
@@ -11,8 +12,10 @@ module PubSubModelSync
|
|
11
12
|
@attributes = attributes
|
12
13
|
@headers = headers
|
13
14
|
build_headers
|
15
|
+
validate!
|
14
16
|
end
|
15
17
|
|
18
|
+
# @return Hash: payload data
|
16
19
|
def to_h
|
17
20
|
{ data: data, attributes: attributes, headers: headers }
|
18
21
|
end
|
@@ -25,33 +28,52 @@ module PubSubModelSync
|
|
25
28
|
attributes[:action]
|
26
29
|
end
|
27
30
|
|
31
|
+
# Process payload data
|
32
|
+
# (If error will raise exception and wont call on_error_processing callback)
|
28
33
|
def process!
|
29
34
|
process do |publisher|
|
30
35
|
publisher.raise_error = true
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
39
|
+
# Process payload data
|
40
|
+
# (If error will call on_error_processing callback)
|
34
41
|
def process
|
35
42
|
publisher = PubSubModelSync::MessageProcessor.new(self)
|
36
43
|
yield(publisher) if block_given?
|
37
44
|
publisher.process
|
38
45
|
end
|
39
46
|
|
47
|
+
# Publish payload to pubsub
|
48
|
+
# (If error will raise exception and wont call on_error_publish callback)
|
40
49
|
def publish!
|
41
50
|
klass = PubSubModelSync::MessagePublisher
|
42
51
|
klass.publish(self, raise_error: true)
|
43
52
|
end
|
44
53
|
|
54
|
+
# Publish payload to pubsub
|
55
|
+
# (If error will call on_error_publish callback)
|
45
56
|
def publish
|
46
57
|
klass = PubSubModelSync::MessagePublisher
|
47
58
|
klass.publish(self)
|
48
59
|
end
|
49
60
|
|
61
|
+
# convert payload data into Payload
|
62
|
+
# @param data [Hash]: payload data (:data, :attributes, :headers)
|
63
|
+
def self.from_payload_data(data)
|
64
|
+
data = data.deep_symbolize_keys
|
65
|
+
new(data[:data], data[:attributes], data[:headers])
|
66
|
+
end
|
67
|
+
|
50
68
|
private
|
51
69
|
|
52
70
|
def build_headers
|
53
71
|
headers[:uuid] ||= SecureRandom.uuid
|
54
72
|
headers[:app_key] ||= PubSubModelSync::Config.subscription_key
|
55
73
|
end
|
74
|
+
|
75
|
+
def validate!
|
76
|
+
raise MissingInfo if !attributes[:klass] || !attributes[:action]
|
77
|
+
end
|
56
78
|
end
|
57
79
|
end
|
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module'
|
|
4
4
|
module PubSubModelSync
|
5
5
|
class Runner
|
6
6
|
class ShutDown < StandardError; end
|
7
|
+
delegate :preload_listeners, to: :class
|
7
8
|
attr_accessor :connector
|
8
9
|
|
9
10
|
def initialize
|
@@ -12,12 +13,17 @@ module PubSubModelSync
|
|
12
13
|
|
13
14
|
def run
|
14
15
|
trap_signals!
|
15
|
-
|
16
|
+
preload_listeners
|
16
17
|
start_listeners
|
17
18
|
rescue ShutDown
|
18
19
|
connector.stop
|
19
20
|
end
|
20
21
|
|
22
|
+
def self.preload_listeners
|
23
|
+
Rails.application.try(:eager_load!) if defined?(Rails)
|
24
|
+
Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk::Loader)
|
25
|
+
end
|
26
|
+
|
21
27
|
private
|
22
28
|
|
23
29
|
def start_listeners
|
@@ -31,10 +37,5 @@ module PubSubModelSync
|
|
31
37
|
end
|
32
38
|
%w[INT QUIT TERM].each { |signal| Signal.trap(signal, handler) }
|
33
39
|
end
|
34
|
-
|
35
|
-
def preload_framework!
|
36
|
-
Rails.application.try(:eager_load!) if defined?(Rails)
|
37
|
-
Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk::Loader)
|
38
|
-
end
|
39
40
|
end
|
40
41
|
end
|
@@ -8,13 +8,16 @@ end
|
|
8
8
|
module PubSubModelSync
|
9
9
|
class ServiceGoogle < ServiceBase
|
10
10
|
LISTEN_SETTINGS = { threads: { callback: 1 } }.freeze
|
11
|
+
TOPIC_SETTINGS = { async: { threads: { publish: 1, callback: 1 } } }.freeze
|
12
|
+
SUBSCRIPTION_SETTINGS = { message_ordering: true }.freeze
|
11
13
|
attr_accessor :service, :topic, :subscription, :subscriber
|
12
14
|
|
13
15
|
def initialize
|
14
16
|
@service = Google::Cloud::Pubsub.new(project: config.project,
|
15
17
|
credentials: config.credentials)
|
16
18
|
@topic = service.topic(config.topic_name) ||
|
17
|
-
service.create_topic(config.topic_name)
|
19
|
+
service.create_topic(config.topic_name, TOPIC_SETTINGS)
|
20
|
+
topic.enable_message_ordering!
|
18
21
|
end
|
19
22
|
|
20
23
|
def listen_messages
|
@@ -29,7 +32,9 @@ module PubSubModelSync
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def publish(payload)
|
32
|
-
topic.
|
35
|
+
topic.publish_async(payload.to_json, message_headers) do |res|
|
36
|
+
raise 'Failed to publish the message.' unless res.succeeded?
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
def stop
|
@@ -39,9 +44,13 @@ module PubSubModelSync
|
|
39
44
|
|
40
45
|
private
|
41
46
|
|
47
|
+
def message_headers
|
48
|
+
{ SERVICE_KEY => true, ordering_key: Time.current.to_i }.merge(PUBLISH_SETTINGS)
|
49
|
+
end
|
50
|
+
|
42
51
|
def subscribe_to_topic
|
43
52
|
topic.subscription(config.subscription_key) ||
|
44
|
-
topic.subscribe(config.subscription_key)
|
53
|
+
topic.subscribe(config.subscription_key, SUBSCRIPTION_SETTINGS)
|
45
54
|
end
|
46
55
|
|
47
56
|
def process_message(received_message)
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Owen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|