pub_sub_model_sync 0.5.5 → 0.5.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a19e13bd4fa78cde78749ffa47096832c31abbc3fdc10e3938988c2c4a73d44
4
- data.tar.gz: a6abb31c4d900dbb1f7307833e88056ac6d1621bbed46b4136f289602b5aab5c
3
+ metadata.gz: f0a2485c0e567e6c012020de909833a5597f5280680b633ea8e9427c51776ddd
4
+ data.tar.gz: ba82cc15b42bd39928fc8eb186c08b811bd1f2de8ca9cae1afc8df28653e284b
5
5
  SHA512:
6
- metadata.gz: 128c4edf32925745e271933c0416c44740c1b553c61f04efb75111787404a419b70185a55527db87d28f4c0d03b534d01ef8e01fa46191b539c1766c315de685
7
- data.tar.gz: 4e73d7f16aead95368d4e6830530259e1d664fb0a23b4fc0f5f49e13aead8b5fbc1778aac8c2ea5a0e7c4f996a10f925d3af51c0730bf962ac6d712a11f7bb84
6
+ metadata.gz: 7751c9ffd368b27cd0e6dcbfd5a79900a2e2bb003eb3be7a210be4182fef4bef950a2d793f9edb4280e82fa3a4c602f67842289df286d9c32a69895f6f488613
7
+ data.tar.gz: 0f4cfca2f370bd4082537619c9654cf21739e07efaa15ee4b7cedbf3892c3141ce722076edcaae9bda6e65f08028a6819aca8dccc7f8a9ed99d3d0703ee9242b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ # 0.5.8.1 (February 05, 2021)
4
+ - fix: keep message ordering with google pubsub
5
+
6
+ # 0.5.8 (January 29, 2021)
7
+ - fix: keep message ordering with google pubsub
8
+
9
+ # 0.5.7.1 (January 26, 2021)
10
+ - fix: does not call :on_error_processing when processing a message
11
+
12
+ # 0.5.7 (January 13, 2021)
13
+ - feat: add method to preload sync listeners
14
+
15
+ # 0.5.6 (January 12, 2021)
16
+ - feat: add payload validation
17
+ - feat: add method to rebuild payload
18
+
3
19
  # 0.5.5 (January 11, 2021)
4
20
  - feat: google-pub/sub receive messages in the same order they were delivered
5
21
 
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'rubocop', '~> 1.6.0', require: false
4
4
  gem 'bunny' # rabbit-mq
5
- gem 'google-cloud-pubsub' # google pub/sub
5
+ gem 'google-cloud-pubsub', '> 2.0' # google pub/sub
6
6
  gem 'ruby-kafka' # kafka pub/sub
7
7
 
8
8
  group :test do
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (0.5.5)
4
+ pub_sub_model_sync (0.5.8.1)
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 (0.17.3)
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.3.2)
92
+ google-cloud-core (1.5.0)
86
93
  google-cloud-env (~> 1.0)
87
- google-cloud-env (1.2.1)
88
- faraday (~> 0.11)
89
- google-cloud-pubsub (1.0.2)
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.2)
92
- google-gax (~> 1.7)
93
- googleapis-common-protos (>= 1.3.9, < 2.0)
94
- grpc-google-iam-v1 (~> 0.6.9)
95
- google-gax (1.7.1)
96
- google-protobuf (~> 3.2)
97
- googleapis-common-protos (>= 1.3.5, < 2.0)
98
- googleauth (>= 0.6.2, < 0.10.0)
99
- grpc (>= 1.7.2, < 2.0)
100
- rly (~> 0.2.3)
101
- google-protobuf (3.11.4)
102
- googleapis-common-protos (1.3.9)
103
- google-protobuf (~> 3.0)
104
- googleapis-common-protos-types (~> 1.0)
105
- grpc (~> 1.0)
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.7)
115
- grpc (1.27.0)
116
- google-protobuf (~> 3.11)
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.9)
119
- googleapis-common-protos (>= 1.3.1, < 2.0)
120
- grpc (~> 1.0)
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.1)
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.14.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.0.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.3)
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
- signet (0.11.0)
213
+ ruby2_keywords (0.0.2)
214
+ signet (0.14.0)
209
215
  addressable (~> 2.3)
210
- faraday (~> 0.9)
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 outside ```rake pub_sub_model_sync:start```
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
- Rails.application.try(:eager_load!)
65
+ PubSubModelSync::Runner.preload_listeners
66
66
  # PubSubModelSync::Config.subscribers ==> [#<PubSubModelSync::Subscriber:0x000.. @klass="Article", @action=:create..., ....]
67
67
  ```
68
68
 
@@ -308,10 +308,13 @@ config.debug = true
308
308
 
309
309
  ## TODO
310
310
  - Add alias attributes when subscribing (similar to publisher)
311
- - Add flag ```model.ps_processing``` to indicate that the current transaction is being processed by pub/sub
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
313
  - On delete, payload must only be composed by ids
314
- - Change notifications into messages
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 })
316
+ - Add DB table to use as a shield to skip publishing similar notifications or publish partial notifications (similar idea when processing notif)
317
+ - add callback: on_message_received(payload)
315
318
 
316
319
  ## Q&A
317
320
  - Error "could not obtain a connection from the pool within 5.000 seconds"
@@ -319,7 +322,28 @@ config.debug = true
319
322
  To fix the problem, edit config/database.yml and increase the quantity of ```pool: 10```
320
323
  - Google pubsub: How to process notifications parallely and not sequentially (default 1 thread)?
321
324
  ```ruby PubSubModelSync::ServiceGoogle::LISTEN_SETTINGS = { threads: { callback: qty_threads } } ```
322
- Note: by this way some notifications can be processed before others thus missing relationship errors can appear
325
+ Note: by this way some notifications can be processed before others thus missing relationship errors can appear
326
+ - How to retry failed syncs with sidekiq?
327
+ ```ruby
328
+ # lib/initializers/pub_sub_config.rb
329
+
330
+ class PubSubRecovery
331
+ include Sidekiq::Worker
332
+ sidekiq_options queue: :pubsub, retry: 2, backtrace: true
333
+
334
+ def perform(payload_data, action)
335
+ payload = PubSubModelSync::Payload.from_payload_data(payload_data)
336
+ payload.send(action)
337
+ end
338
+ end
339
+
340
+ PubSubModelSync::Config.on_error_publish = lambda do |_e, data|
341
+ PubSubRecovery.perform_async(data[:payload].to_h, :publish!)
342
+ end
343
+ PubSubModelSync::Config.on_error_processing = lambda do |_e, data|
344
+ PubSubRecovery.perform_async(data[:payload].to_h, :process!)
345
+ end
346
+ ```
323
347
 
324
348
  ## Contributing
325
349
 
@@ -34,6 +34,14 @@ module PubSubModelSync
34
34
  def publish(*_args)
35
35
  true
36
36
  end
37
+
38
+ def publish_async(*_args)
39
+ yield(OpenStruct.new(succeeded?: true)) if block_given?
40
+ end
41
+
42
+ def enable_message_ordering!
43
+ true
44
+ end
37
45
  end
38
46
 
39
47
  def topic(*_args)
@@ -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
- preload_framework!
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
@@ -29,7 +29,7 @@ module PubSubModelSync
29
29
  if same_app_message?(payload)
30
30
  log("Skip message from same origin: #{[payload]}") if config.debug
31
31
  else
32
- payload.process!
32
+ payload.process
33
33
  end
34
34
  rescue => e
35
35
  error = [payload, e.message, e.backtrace]
@@ -7,7 +7,8 @@ end
7
7
 
8
8
  module PubSubModelSync
9
9
  class ServiceGoogle < ServiceBase
10
- LISTEN_SETTINGS = { threads: { callback: 1 } }.freeze
10
+ LISTEN_SETTINGS = { threads: { callback: 1 }, message_ordering: true, streams: 1 }.freeze
11
+ TOPIC_SETTINGS = { async: { threads: { publish: 1, callback: 1 } } }.freeze
11
12
  SUBSCRIPTION_SETTINGS = { message_ordering: true }.freeze
12
13
  attr_accessor :service, :topic, :subscription, :subscriber
13
14
 
@@ -15,7 +16,8 @@ module PubSubModelSync
15
16
  @service = Google::Cloud::Pubsub.new(project: config.project,
16
17
  credentials: config.credentials)
17
18
  @topic = service.topic(config.topic_name) ||
18
- service.create_topic(config.topic_name)
19
+ service.create_topic(config.topic_name, TOPIC_SETTINGS)
20
+ topic.enable_message_ordering!
19
21
  end
20
22
 
21
23
  def listen_messages
@@ -30,7 +32,9 @@ module PubSubModelSync
30
32
  end
31
33
 
32
34
  def publish(payload)
33
- topic.publish(payload.to_json, { SERVICE_KEY => true }.merge(PUBLISH_SETTINGS))
35
+ topic.publish_async(payload.to_json, message_headers) do |res|
36
+ raise 'Failed to publish the message.' unless res.succeeded?
37
+ end
34
38
  end
35
39
 
36
40
  def stop
@@ -40,6 +44,10 @@ module PubSubModelSync
40
44
 
41
45
  private
42
46
 
47
+ def message_headers
48
+ { SERVICE_KEY => true, ordering_key: SERVICE_KEY }.merge(PUBLISH_SETTINGS)
49
+ end
50
+
43
51
  def subscribe_to_topic
44
52
  topic.subscription(config.subscription_key) ||
45
53
  topic.subscribe(config.subscription_key, SUBSCRIPTION_SETTINGS)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.5.5'
4
+ VERSION = '0.5.8.1'
5
5
  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.5.5
4
+ version: 0.5.8.1
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 00:00:00.000000000 Z
11
+ date: 2021-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails