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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a06777a1f70a6efc94e7024b5b5c1f38d04f7eac8c9721a82fe84ffa042d15bb
4
- data.tar.gz: f558c5116ec027c2b387dd04a16d03657d64f5fbefa244e9f075114cbd34ef21
3
+ metadata.gz: 14095a129de1a44dd2f51051e3ee768217eae9ddbaa69b2e9bb3979d857dde5a
4
+ data.tar.gz: eeff964335f21b2bc5db23048cf0a09cb8e4d385333f72cf431d9201e107737b
5
5
  SHA512:
6
- metadata.gz: 41815c6d3390abcb71c3c4855e74e95ef7ead047e0e9315925eb8c1a56572f3458304d19bcdd38ceb51e203071f26a18071184fcbb766c47e5f617b2e0f530f4
7
- data.tar.gz: 06f17b5f3ecfde95baf7c6465d371798a3257f2bbe86b288af4ab44250c8c8ff62faaa2597d4ddb520badf469a9558b2430e581d5d7c33bdfdd2d9e2ad83ae0a
6
+ metadata.gz: 3f85d85ac9f5de88af18848c8511e113a2359c5135b4dba8904e4bbc2b1f72b7be526c9fcdd221761202e9ef8d4630d84e08e9a5d8aa0c2753f98046a514fb70
7
+ data.tar.gz: 4788d133e2eaf6b302d34898d5f0d7002556edb357c907f280df137d686c71d7280503a763180f7fb61dece830c41f1263a7d569a868fad0082d13062e7caccd
@@ -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: make sequential message processor google-pub/sub
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
@@ -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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (0.5.4.1)
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 (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,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.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
- - 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
 
@@ -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]
@@ -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.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
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.5.4.1'
4
+ VERSION = '0.5.8'
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.4.1
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-08 00:00:00.000000000 Z
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails