pub_sub_model_sync 1.0.1 → 1.2.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: 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