pub_sub_model_sync 1.0.1 → 1.2.1

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: 6e73c2af5a62d4b0ae32ea40f3c3c369a049edfd47360a50386eeb5e4ff97cd1
4
- data.tar.gz: 5d1892ee41ada21c9bf70bcc3601e435edfdf55d7b51b443369bf3307da60436
3
+ metadata.gz: 86dd60cd72d095630d44b3d1d6bb0004f0d7ce35326f5613127815047376530a
4
+ data.tar.gz: ef7c8edd08a315adcbad4a4314a467e1564a8dcc2233764ef53d09985a41eaf3
5
5
  SHA512:
6
- metadata.gz: 962defddfdc780ce185d01e9069485207bdf66be9b3554a1003b5ec71211c467223c288fbb6c6a59ae819a9ba37ff118a470942bdef9e69c68e94f34ff899c9a
7
- data.tar.gz: c6e771a40a96dcdf00161aa49fbe7c1b5f283709d5b42b87421c6655e4cb9972e9f7d3bb7d203d47c9757dc681f8c755c7026447f6582ffa1f43a553ba90197a
6
+ metadata.gz: 690ca42fe8d463cabdaa4e0e557ee1fa19ffa0e1c9a4ad19ee1a1195a7a78faab458ebcd0b0581c39b917ac73c9dc01f408bf1c9ae7f25445b7d20afba2592fe
7
+ data.tar.gz: 72d3e0a10a8879f670e65a6f50dbc71e0351893ac1b57dc6fe1796373f7a38d55ecc7a6508c86eba7256dab03c903a42b59f9a0b7a881f6c4c28086b59584049
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Change Log
2
2
 
3
+ # 1.2.1 (October 28, 2021)
4
+ chore: improve logs
5
+
6
+ # 1.2.0 (October 28, 2021)
7
+ - feat: rename Payload `:key` into `:internal_key` to avoid confusions while debugging
8
+
9
+ # 1.1.1 (October 25, 2021)
10
+ - feat: do not exclude `ordering_key topic_name` when delivering a notification (required when debugging)
11
+ - doc: improve docs
12
+
13
+ # 1.1.0 (October 25, 2021)
14
+ - feat: change `transactions_max_buffer` default value to 1 to deliver notifications once they were called
15
+ - feat: use `after_commit` instead of `before_commit` callback and remove the horrible AR patch for rails 4
16
+
3
17
  # 1.0.1 (August 20, 2021)
4
18
  - refactor: improve service exit when running in k8s
5
19
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (1.0.1)
4
+ pub_sub_model_sync (1.2.1)
5
5
  rails
6
6
 
7
7
  GEM
@@ -116,7 +116,6 @@ GEM
116
116
  google-cloud-errors (~> 1.0)
117
117
  grpc-google-iam-v1 (>= 0.6.10, < 2.0)
118
118
  google-protobuf (3.17.0)
119
- google-protobuf (3.17.0-x86_64-linux)
120
119
  googleapis-common-protos (1.3.11)
121
120
  google-protobuf (~> 3.14)
122
121
  googleapis-common-protos-types (>= 1.0.6, < 2.0)
@@ -133,9 +132,6 @@ GEM
133
132
  grpc (1.37.1)
134
133
  google-protobuf (~> 3.15)
135
134
  googleapis-common-protos-types (~> 1.0)
136
- grpc (1.37.1-x86_64-linux)
137
- google-protobuf (~> 3.15)
138
- googleapis-common-protos-types (~> 1.0)
139
135
  grpc-google-iam-v1 (0.6.11)
140
136
  google-protobuf (~> 3.14)
141
137
  googleapis-common-protos (>= 1.3.11, < 2.0)
@@ -264,4 +260,4 @@ DEPENDENCIES
264
260
  sqlite3
265
261
 
266
262
  BUNDLED WITH
267
- 2.2.17
263
+ 2.2.29
data/README.md CHANGED
@@ -245,7 +245,7 @@ PubSubModelSync::Payload.new({ ids: [my_user.id] }, { klass: 'User', action: :ba
245
245
  - `method_name` (Symbol, optional) method to be called to process action callback, sample: `def my_method(action) ... end`
246
246
  - `block` (Proc, optional) Block to be called to process action callback, sample: `{ |action| ... }`
247
247
 
248
- **Note1**: Due to rails callback ordering, this method uses `before_commit` callback when creating or updating models to ensure expected notifications order (More details [**here**](#transactions)).
248
+ **Note1**: Due to rails callback ordering, this method uses `after_commit on: action {...}` callback when creating or updating models to ensure expected notifications order (More details [**here**](#transactions)).
249
249
  **Note2**: Due to rails callback ordering, this method uses `after_destroy` callback when destroying models to ensure the expected notifications order.
250
250
 
251
251
  - `ps_publish(action, data: {}, mapping: [], headers: {}, as_klass: nil)` Delivers an instance notification via pubsub
@@ -311,11 +311,14 @@ Any notification before delivering is transformed as a Payload for a better port
311
311
  - `klass`: (String) Notification class name
312
312
  - `mode`: (Symbol: `:model`|`:class`) Kind of notification
313
313
  * `headers`: (Hash) Notification settings that defines how the notification will be processed or delivered.
314
- - `key`: (String, optional) identifier of the payload, default: `<klass_name>/<action>` when class message, `<model.class.name>/<action>/<model.id>` when model message (Useful for caching techniques).
315
- - `ordering_key`: (String, optional): messages with the same value are processed in the same order they were delivered, default: `klass_name` when class message, `<model.class.name>/<model.id>` when instance message.
316
- Note: Final `ordering_key` is calculated by this way: `payload.headers[:forced_ordering_key] || current_transaction&.key || payload.headers[:ordering_key]`
317
- - `topic_name`: (String|Array<String>, optional): Specific topic name (can be seen as a channel) to be used when delivering the message (default first topic from config).
318
- - `forced_ordering_key`: (String, optional): Will force to use this value as the `ordering_key`, even withing transactions. Default `nil`.
314
+ - `ordering_key`: (String, optional): notifications with the same `ordering_key` are processed in the same order they were delivered, default: `<model.class.name>/<model.id>` when instance notification and `klass_name` when class notification.
315
+ Note: Final `ordering_key` is calculated as: `payload.headers[:forced_ordering_key] || current_transaction&.key || payload.headers[:ordering_key]`
316
+ - `internal_key`: (String, optional) Internal identifier of the payload, default: `<model.class.name>/<action>/<model.id>` when model notification and `<klass_name>/<action>` when class notification (Useful for caching techniques).
317
+ - `topic_name`: (String|Array<String>, optional): Specific topic name where to deliver the notification (default `PubSubModelSync::Config.topic_name`).
318
+ - `forced_ordering_key`: (String, optional): Overrides `ordering_key` with the provided value even withing transactions. Default `nil`.
319
+ - `app_key`: (Auto calculated): Name of the application who delivered the notification.
320
+ - `uuid`: (Auto calculated): Unique notification identifier (Very useful when debugging).
321
+ Note: To reduce Payload size, some header info are not delivered (Enable debug mode to deliver all payload info).
319
322
 
320
323
  - Actions
321
324
  ```ruby
@@ -340,21 +343,21 @@ Any notification before delivering is transformed as a Payload for a better port
340
343
  ps_after_action([:create, :update, :destroy]) { |action| ps_publish(action, mapping: %i[id user_id title]) }
341
344
  end
342
345
  ```
343
- - When created (all notifications use the same ordering key to be processed in the same order)
346
+ - When created (all notifications use the same ordering_key to be processed in the same order)
344
347
  ```ruby
345
348
  user = User.create!(name: 'test', posts_attributes: [{ title: 'Post 1' }, { title: 'Post 2' }])
346
349
  # notification #1 => <Payload data: {id: 1, name: 'sample'}, info: { klass: 'User', action: :create, mode: :model }, headers: { ordering_key = `User/1` }>
347
350
  # notification #2 => <Payload data: {id: 1, title: 'Post 1', user_id: 1}, info: { klass: 'Post', action: :create, mode: :model }, headers: { ordering_key = `User/1` }>
348
351
  # notification #3 => <Payload data: {id: 2, title: 'Post 2', user_id: 1}, info: { klass: 'Post', action: :create, mode: :model }, headers: { ordering_key = `User/1` }>
349
352
  ```
350
- - When updated (all notifications use the same ordering key to be processed in the same order)
353
+ - When updated (all notifications use the same ordering_key to be processed in the same order)
351
354
  ```ruby
352
355
  user.update!(name: 'changed', posts_attributes: [{ id: 1, title: 'Post 1C' }, { id: 2, title: 'Post 2C' }])
353
356
  # notification #1 => <Payload data: {id: 1, name: 'changed'}, info: { klass: 'User', action: :update, mode: :model }, headers: { ordering_key = `User/1` }>
354
357
  # notification #2 => <Payload data: {id: 1, title: 'Post 1C', user_id: 1}, info: { klass: 'Post', action: :update, mode: :model }, headers: { ordering_key = `User/1` }>
355
358
  # notification #3 => <Payload data: {id: 2, title: 'Post 2C', user_id: 1}, info: { klass: 'Post', action: :update, mode: :model }, headers: { ordering_key = `User/1` }>
356
359
  ```
357
- - When destroyed (all notifications use the same ordering key to be processed in the same order)
360
+ - When destroyed (all notifications use the same ordering_key to be processed in the same order)
358
361
  **Note**: The notifications order were reordered in order to avoid inconsistency in other apps
359
362
  ```ruby
360
363
  user.destroy!
@@ -368,10 +371,8 @@ Any notification before delivering is transformed as a Payload for a better port
368
371
 
369
372
  - Manual transactions
370
373
  `PubSubModelSync::MessagePublisher::transaction(key, max_buffer: , &block)`
371
- - `key` (String|nil) Key used as the ordering key for all inner notifications (When nil, will use `ordering_key` of the first notification)
372
- - `max_buffer:` (Boolean, default: `PubSubModelSync::Config.transactions_max_buffer`)
373
- If true: will save all notifications and deliver all them when transaction has successfully finished. If transaction has failed, then all saved notifications will be discarded (not delivered).
374
- If false: will deliver all notifications immediately (no way to rollback notifications if transaction has failed)
374
+ - `key` (String|nil) Key used as the ordering_key for all inner notifications (When nil, will use `ordering_key` of the first notification)
375
+ - `max_buffer:` (Integer, default: `PubSubModelSync::Config.transactions_max_buffer`) Transaction buffer size (more details in #transactions_max_buffer).
375
376
  Sample:
376
377
  ```ruby
377
378
  PubSubModelSync::MessagePublisher::transaction('my-custom-key') do
@@ -530,7 +531,7 @@ config.debug = true
530
531
  Topic name(s) to be used to listen all notifications from when listening. Additionally first topic name is used as the default topic name when publishing a notification.
531
532
  - `.subscription_name = "my-app-1"`: (String, default Rails.application.name)
532
533
  Subscriber's identifier which helps to:
533
- * skip self messages
534
+ * skip self notifications
534
535
  * continue the sync from the last synced notification when service was restarted.
535
536
  - `.default_topic_name = "my_topic"`: (String|Array<String>, optional(default first topic from `topic_name`))
536
537
  Topic name used as the default topic if not defined in the payload when publishing a notification
@@ -539,24 +540,26 @@ config.debug = true
539
540
  - ```.logger = Rails.logger```
540
541
  (Logger) => define custom logger
541
542
  - ```.on_before_processing = ->(payload, {subscriber:}) { puts payload }```
542
- (Proc) => called before processing received message (:cancel can be returned to skip processing)
543
+ (Proc) => called before processing a received notification (:cancel can be returned to skip processing)
543
544
  - ```.on_success_processing = ->(payload, {subscriber:}) { puts payload }```
544
- (Proc) => called when a message was successfully processed
545
+ (Proc) => called when a notification was successfully processed
545
546
  - ```.on_error_processing = ->(exception, {payload:, subscriber:}) { payload.delay(...).process! }```
546
- (Proc) => called when a message failed when processing (delayed_job or similar can be used for retrying)
547
+ (Proc) => called when a notification has failed when processing (delayed_job or similar can be used for retrying)
547
548
  - ```.on_before_publish = ->(payload) { puts payload }```
548
- (Proc) => called before publishing a message (:cancel can be returned to skip publishing)
549
+ (Proc) => called before publishing a notification (:cancel can be returned to skip publishing)
549
550
  - ```.on_after_publish = ->(payload) { puts payload }```
550
- (Proc) => called after publishing a message
551
+ (Proc) => called after publishing a notification
551
552
  - ```.on_error_publish = ->(exception, {payload:}) { payload.delay(...).publish! }```
552
- (Proc) => called when failed publishing a message (delayed_job or similar can be used for retrying)
553
- - ```.transactions_max_buffer = 100``` (Integer) Once this quantity of notifications is reached, then all notifications will immediately be delivered.
554
- Note: There is no way to rollback delivered notifications if current transaction fails
555
- - ```.enable_rails4_before_commit = true``` (true*|false) When false will disable rails 4 hack compatibility and then CRUD notifications will be prepared using `after_commit` callback instead of `before_commit` (used in `ps_after_action(...)`) which will not rollback sql transactions if failed when publishing pubsub notification.
553
+ (Proc) => called when failed publishing a notification (delayed_job or similar can be used for retrying)
554
+ - ```.transactions_max_buffer = 1``` (Integer, default 1) Controls the maximum quantity of notifications to be enqueued to the transaction-buffer before delivering them and thus adds the ability to rollback notifications if the transaction fails.
555
+ Once this quantity of notifications is reached, then all notifications of the current transaction will immediately be delivered (can be customized per transaction).
556
+ Note: There is no way to rollback delivered notifications if current transaction fails later.
557
+ Note2: Only notifications from the buffer can be rollbacked if the current transaction has failed.
556
558
 
557
559
  ## **TODO**
560
+ - add the ability to raise SKIP_ACKNOWLEDGE to auto retry by PubSub
558
561
  - Auto publish update only if payload has changed (see ways to compare previous payload vs new payload)
559
- - Improve transactions to exclude similar messages by klass and action. Sample:
562
+ - Improve transactions to exclude similar notifications by klass and action. Sample:
560
563
  ```PubSubModelSync::MessagePublisher.transaction(key, { 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 })```
561
564
  - Add DB table to use as a shield to prevent publishing similar notifications and publish partial notifications (similar idea when processing notif)
562
565
  - Last notification is not being delivered immediately in google pubsub (maybe force with timeout 10secs and service.deliver_messages)
@@ -564,6 +567,9 @@ config.debug = true
564
567
  - Services support to deliver multiple payloads from transactions
565
568
  - Fix deprecation warnings: pub_sub_model_sync/service_google.rb:39: warning: Splitting the last argument into positional and keyword parameters is deprecated
566
569
  - Add if/unless to ps_after_action
570
+ - Add subscription liveness checker using thread without db connection to check periodically pending notifications from google pubsub
571
+ - Unify .stop() and 'Listener stopped'
572
+ - TODO: Publish new version 1.2.1 (improve logs)
567
573
 
568
574
  ## **Q&A**
569
575
  - I'm getting error "could not obtain a connection from the pool within 5.000 seconds"... what does this mean?
@@ -8,8 +8,7 @@ module PubSubModelSync
8
8
  # customizable callbacks
9
9
  cattr_accessor(:debug) { false }
10
10
  cattr_accessor :logger # LoggerInst
11
- cattr_accessor(:transactions_max_buffer) { 100 }
12
- cattr_accessor(:enable_rails4_before_commit) { Rails::VERSION::MAJOR == 4 }
11
+ cattr_accessor(:transactions_max_buffer) { 1 }
13
12
 
14
13
  cattr_accessor(:on_before_processing) { ->(_payload, _info) {} } # return :cancel to skip
15
14
  cattr_accessor(:on_success_processing) { ->(_payload, _info) {} }
@@ -31,7 +31,7 @@ module PubSubModelSync
31
31
  end
32
32
 
33
33
  # Starts a new transaction
34
- # @param key (String|Nil)
34
+ # @param key (String, Nil)
35
35
  # @return (Transaction)
36
36
  def init_transaction(key, settings = {})
37
37
  new_transaction = PubSubModelSync::Transaction.new(key, settings)
@@ -86,7 +86,7 @@ module PubSubModelSync
86
86
 
87
87
  def build_headers
88
88
  headers[:app_key] ||= PubSubModelSync::Config.subscription_key
89
- headers[:key] ||= [klass, action].join('/')
89
+ headers[:internal_key] ||= [klass, action].join('/')
90
90
  headers[:ordering_key] ||= klass
91
91
  headers[:uuid] ||= SecureRandom.uuid
92
92
  end
@@ -43,8 +43,8 @@ module PubSubModelSync
43
43
 
44
44
  def headers_data
45
45
  klass_name = model.class.name
46
- key = [klass_name, action, model.id || SecureRandom.uuid].join('/')
47
- def_data = { ordering_key: self.class.ordering_key_for(model), key: key }
46
+ internal_key = [klass_name, action, model.id || SecureRandom.uuid].join('/')
47
+ def_data = { ordering_key: self.class.ordering_key_for(model), internal_key: internal_key }
48
48
  def_data.merge(compute_value(headers))
49
49
  end
50
50
 
@@ -67,7 +67,7 @@ module PubSubModelSync
67
67
  if action == :destroy
68
68
  after_destroy { instance_exec(action, &callback) }
69
69
  else
70
- ps_define_commit_action(action, callback)
70
+ send(:after_commit, on: action) { instance_exec(action, &callback) }
71
71
  end
72
72
  end
73
73
  end
@@ -80,15 +80,6 @@ module PubSubModelSync
80
80
 
81
81
  private
82
82
 
83
- def ps_define_commit_action(action, callback)
84
- if PubSubModelSync::Config.enable_rails4_before_commit # rails 4 compatibility
85
- define_method("ps_before_#{action}_commit") { instance_exec(action, &callback) }
86
- else
87
- commit_name = respond_to?(:before_commit) ? :before_commit : :after_commit
88
- send(commit_name, on: action) { instance_exec(action, &callback) }
89
- end
90
- end
91
-
92
83
  # Initialize calls to start and end pub_sub transactions and deliver all them in the same order
93
84
  def ps_init_transaction_callbacks
94
85
  start_transaction = lambda do
@@ -13,7 +13,6 @@ module PubSubModelSync
13
13
  end
14
14
 
15
15
  configure do
16
- require 'pub_sub_model_sync/initializers/before_commit' if PubSubModelSync::Config.enable_rails4_before_commit
17
16
  end
18
17
  end
19
18
  end
@@ -24,7 +24,7 @@ module PubSubModelSync
24
24
  # @return (String): Json Format
25
25
  def encode_payload(payload)
26
26
  data = payload.to_h
27
- not_important_keys = %i[ordering_key topic_name forced_ordering_key]
27
+ not_important_keys = %i[forced_ordering_key]
28
28
  reduce_payload_size = !config.debug
29
29
  data[:headers].except!(*not_important_keys) if reduce_payload_size
30
30
  data.to_json
@@ -77,7 +77,7 @@ module PubSubModelSync
77
77
  }.merge(PUBLISH_SETTINGS)
78
78
  end
79
79
 
80
- # @return [Subscriber]
80
+ # @return [Array<Subscriber>]
81
81
  def subscribe_to_topics
82
82
  topics.map do |key, topic|
83
83
  subs_name = "#{config.subscription_key}_#{key}"
@@ -5,7 +5,7 @@ module PubSubModelSync
5
5
  PUBLISHER_KLASS = PubSubModelSync::MessagePublisher
6
6
  attr_accessor :key, :payloads, :max_buffer, :root, :children, :finished
7
7
 
8
- # @param key (String,Null) Transaction key, if empty will use the ordering_key from first payload
8
+ # @param key (String,Nil) Transaction key, if empty will use the ordering_key from first payload
9
9
  # @param max_buffer (Integer) Once this quantity of notifications is reached, then all notifications
10
10
  # will immediately be delivered.
11
11
  # Note: There is no way to rollback delivered notifications if current transaction fails
@@ -19,10 +19,11 @@ module PubSubModelSync
19
19
  # @param payload (Payload)
20
20
  def add_payload(payload)
21
21
  payloads << payload
22
- log("Payload added to current transaction: #{payload.inspect}") if config.debug
22
+ print_log = config.debug && max_buffer > 1
23
+ log("Payload added to current transaction: #{payload.inspect}") if print_log
23
24
  return unless payloads.count >= max_buffer
24
25
 
25
- log("Payloads buffer was filled, delivering current payloads: #{payloads.count}")
26
+ log("Payloads buffer was filled, delivering current payloads: #{payloads.count}") if print_log
26
27
  deliver_payloads
27
28
  end
28
29
 
@@ -60,14 +61,14 @@ module PubSubModelSync
60
61
  private
61
62
 
62
63
  def deliver_payloads
63
- payloads.each do |payload|
64
- begin # rubocop:disable Style/RedundantBegin (ruby 2.4 support)
65
- PUBLISHER_KLASS.connector_publish(payload)
66
- rescue => e
67
- PUBLISHER_KLASS.send(:notify_error, e, payload)
68
- end
69
- end
64
+ payloads.each(&method(:deliver_payload))
70
65
  self.payloads = []
71
66
  end
67
+
68
+ def deliver_payload(payload)
69
+ PUBLISHER_KLASS.connector_publish(payload)
70
+ rescue => e
71
+ PUBLISHER_KLASS.send(:notify_error, e, payload)
72
+ end
72
73
  end
73
74
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '1.0.1'
4
+ VERSION = '1.2.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: 1.0.1
4
+ version: 1.2.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-08-20 00:00:00.000000000 Z
11
+ date: 2021-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -111,7 +111,6 @@ files:
111
111
  - lib/pub_sub_model_sync/base.rb
112
112
  - lib/pub_sub_model_sync/config.rb
113
113
  - lib/pub_sub_model_sync/connector.rb
114
- - lib/pub_sub_model_sync/initializers/before_commit.rb
115
114
  - lib/pub_sub_model_sync/message_processor.rb
116
115
  - lib/pub_sub_model_sync/message_publisher.rb
117
116
  - lib/pub_sub_model_sync/mock_google_service.rb
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Rails 4 backward compatibility (Add "simple" ps_before_*_commit callbacks)
4
- ActiveRecord::ConnectionAdapters::RealTransaction.class_eval do
5
- alias_method :commit_without_before_commit, :commit
6
-
7
- def commit
8
- call_before_commit_records if PubSubModelSync::Config.enable_rails4_before_commit
9
- commit_without_before_commit
10
- end
11
-
12
- private
13
-
14
- def call_before_commit_records
15
- ite = records.uniq
16
- ite.each do |record|
17
- action = record.previous_changes.include?(:id) ? :create : :update
18
- action = :destroy if record.destroyed?
19
- callback_name = "ps_before_#{action}_commit".to_sym
20
- record.send(callback_name) if record.respond_to?(callback_name)
21
- end
22
- end
23
- end