pub_sub_model_sync 1.2.1 → 1.4.0

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: 86dd60cd72d095630d44b3d1d6bb0004f0d7ce35326f5613127815047376530a
4
- data.tar.gz: ef7c8edd08a315adcbad4a4314a467e1564a8dcc2233764ef53d09985a41eaf3
3
+ metadata.gz: 1740843df35081aaeb35e2be5c1c27cf1ff4d219af0bcf9ae4394495ab8bba5e
4
+ data.tar.gz: ed506905c6b29b554f4a1cd100ddf4c8df2bf0a99590eb26901fae8feef862ce
5
5
  SHA512:
6
- metadata.gz: 690ca42fe8d463cabdaa4e0e557ee1fa19ffa0e1c9a4ad19ee1a1195a7a78faab458ebcd0b0581c39b917ac73c9dc01f408bf1c9ae7f25445b7d20afba2592fe
7
- data.tar.gz: 72d3e0a10a8879f670e65a6f50dbc71e0351893ac1b57dc6fe1796373f7a38d55ecc7a6508c86eba7256dab03c903a42b59f9a0b7a881f6c4c28086b59584049
6
+ metadata.gz: bff308ce421ae321d2a9e7b53df194d3678e5f6103eb4f2048d4ed064302c042fb7768f96a3c639190b8d452a0636fded9e72394a5ffe4731b4e24ba0b2f5b53
7
+ data.tar.gz: e60c4c78c5fbde7c40d68adb1ed029d74bb10d1759a27682850aae6015c84f938cbccccaadc976878afa26d97621d360937cdeb5cc028b2393facb12fa0d4831
@@ -12,11 +12,15 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  strategy:
14
14
  matrix:
15
- ruby: [2.4, 2.5, 2.6]
15
+ ruby: [2.5, 2.6]
16
16
  rails: [4, 5, 6]
17
17
  include:
18
18
  - ruby: 2.7
19
19
  rails: 6
20
+ - ruby: '3.0'
21
+ rails: 6
22
+ - ruby: '3.0'
23
+ rails: 7
20
24
  exclude: # rails 6 requires ruby >= 2.5
21
25
  - ruby: 2.4
22
26
  rails: 6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Change Log
2
2
 
3
+ For recent releases list go to: https://github.com/owen2345/pub_sub_model_sync/releases
4
+
3
5
  # 1.2.1 (October 28, 2021)
4
6
  chore: improve logs
5
7
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (1.2.1)
4
+ pub_sub_model_sync (1.4.0)
5
5
  rails
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -313,10 +313,15 @@ Any notification before delivering is transformed as a Payload for a better port
313
313
  * `headers`: (Hash) Notification settings that defines how the notification will be processed or delivered.
314
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
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
316
  - `topic_name`: (String|Array<String>, optional): Specific topic name where to deliver the notification (default `PubSubModelSync::Config.topic_name`).
318
317
  - `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.
318
+ - `cache` (Boolean | Hash, Default false) Cache settings
319
+ - `true`: Skip publishing similar payloads
320
+ - `Hash<required: Array<Symbol>>`: Same as `true` and enables payload optimization to exclude unchanged non important attributes. Sample: `{ required: %i[id email] }`
321
+
322
+ ** Read ONLY **
323
+ - `internal_key`: 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).
324
+ - `app_key`: (Auto calculated): Name of the application who delivered the notification.
320
325
  - `uuid`: (Auto calculated): Unique notification identifier (Very useful when debugging).
321
326
  Note: To reduce Payload size, some header info are not delivered (Enable debug mode to deliver all payload info).
322
327
 
@@ -386,37 +391,15 @@ Note: To reduce Payload size, some header info are not delivered (Enable debug m
386
391
  ## **Testing with RSpec**
387
392
  - Config: (spec/rails_helper.rb)
388
393
  ```ruby
389
-
390
- # when using google service
391
- require 'pub_sub_model_sync/mock_google_service'
392
- config.before(:each) do
393
- google_mock = PubSubModelSync::MockGoogleService.new
394
- allow(Google::Cloud::Pubsub).to receive(:new).and_return(google_mock)
395
- end
396
-
397
- # when using rabbitmq service
398
- require 'pub_sub_model_sync/mock_rabbit_service'
399
- config.before(:each) do
400
- rabbit_mock = PubSubModelSync::MockRabbitService.new
401
- allow(Bunny).to receive(:new).and_return(rabbit_mock)
402
- end
403
-
404
- # when using apache kafka service
405
- require 'pub_sub_model_sync/mock_kafka_service'
406
- config.before(:each) do
407
- kafka_mock = PubSubModelSync::MockKafkaService.new
408
- allow(Kafka).to receive(:new).and_return(kafka_mock)
409
- end
410
-
411
- # disable all models sync by default (reduces testing time)
412
394
  config.before(:each) do
413
- allow(PubSubModelSync::MessagePublisher).to receive(:publish_data) # disable class level notif
414
- allow(PubSubModelSync::MessagePublisher).to receive(:publish_model) # disable instance level notif
395
+ # disable delivering notifications to pubsub
396
+ allow(PubSubModelSync::MessagePublisher).to receive(:connector_publish)
397
+ # disable all models sync by default (reduces testing time by avoiding to build payload data)
398
+ allow(PubSubModelSync::MessagePublisher).to receive(:publish_model)
415
399
  end
416
400
 
417
401
  # enable all models sync only for tests that includes 'sync: true'
418
402
  config.before(:each, sync: true) do
419
- allow(PubSubModelSync::MessagePublisher).to receive(:publish_data).and_call_original
420
403
  allow(PubSubModelSync::MessagePublisher).to receive(:publish_model).and_call_original
421
404
  end
422
405
 
@@ -426,9 +409,9 @@ Note: To reduce Payload size, some header info are not delivered (Enable debug m
426
409
  # end
427
410
  ```
428
411
  - Examples:
429
- - **Publisher**
412
+ - **Publisher**
413
+ Note: **Do not forget to include 'sync: true'** to enable publishing pubsub notifications
430
414
  ```ruby
431
- # Do not forget to include 'sync: true' to enable publishing pubsub notifications
432
415
  describe 'When publishing sync', truncate: true, sync: true do
433
416
  it 'publishes user notification when created' do
434
417
  expect_publish_notification(:create, klass: 'User')
data/Rakefile CHANGED
@@ -4,3 +4,4 @@ require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  task :default => :spec
7
+ # by default skip ACK when failed in three of them
data/gemfiles/Gemfile_4 CHANGED
@@ -5,7 +5,6 @@ gem 'bunny' # rabbit-mq
5
5
  gem 'google-cloud-pubsub' # google pub/sub
6
6
  gem 'ruby-kafka' # kafka pub/sub
7
7
  gem 'rails', '~> 4'
8
- gem 'bundler'
9
8
  gem 'sqlite3', '1.3.13'
10
9
 
11
10
  group :test do
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rubocop'
4
+ gem 'bunny' # rabbit-mq
5
+ gem 'google-cloud-pubsub' # google pub/sub
6
+ gem 'ruby-kafka' # kafka pub/sub
7
+ gem 'rails', '~> 7'
8
+
9
+ group :test do
10
+ gem 'database_cleaner-active_record'
11
+ end
12
+
13
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
14
+ gemspec
@@ -9,6 +9,7 @@ module PubSubModelSync
9
9
  cattr_accessor(:debug) { false }
10
10
  cattr_accessor :logger # LoggerInst
11
11
  cattr_accessor(:transactions_max_buffer) { 1 }
12
+ cattr_accessor(:skip_cache) { false }
12
13
 
13
14
  cattr_accessor(:on_before_processing) { ->(_payload, _info) {} } # return :cancel to skip
14
15
  cattr_accessor(:on_success_processing) { ->(_payload, _info) {} }
@@ -34,7 +34,7 @@ module PubSubModelSync
34
34
  # @param key (String, Nil)
35
35
  # @return (Transaction)
36
36
  def init_transaction(key, settings = {})
37
- new_transaction = PubSubModelSync::Transaction.new(key, settings)
37
+ new_transaction = PubSubModelSync::Transaction.new(key, **settings)
38
38
  if current_transaction
39
39
  current_transaction.add_transaction(new_transaction)
40
40
  else
@@ -98,8 +98,11 @@ module PubSubModelSync
98
98
  private
99
99
 
100
100
  def ensure_publish(payload)
101
- cancelled = config.on_before_publish.call(payload) == :cancel
102
- log("Publish cancelled by config.on_before_publish: #{[payload]}") if config.debug && cancelled
101
+ cache_klass = PubSubModelSync::PayloadCacheOptimizer
102
+ cancelled = payload.cache_settings ? cache_klass.new(payload).call == :already_sent : false
103
+ cancelled ||= config.on_before_publish.call(payload) == :cancel
104
+ log_msg = "Publish cancelled by config.on_before_publish or cache checker: #{[payload]}"
105
+ log(log_msg) if config.debug && cancelled
103
106
  !cancelled
104
107
  end
105
108
 
@@ -19,9 +19,17 @@ module PubSubModelSync
19
19
  # <klass>: when class message
20
20
  # <klass/id>: when model message
21
21
  # topic_name (String|Array<String>): Specific topic name to be used when delivering the
22
- # message (default first topic)
22
+ # message (default Config.topic_name)
23
23
  # forced_ordering_key (String, optional): Will force to use this value as the ordering_key,
24
24
  # even withing transactions. Default nil.
25
+ # cache (Boolean | Hash, Default false) Cache settings
26
+ # true: Skip publishing similar payloads
27
+ # Hash<required: Array<Symbol>>: Same as true and enables payload optimization to exclude
28
+ # unchanged non important attributes. Sample: { required: %i[id email] }
29
+ # --- READ ONLY ----
30
+ # app_key: (string) Subscriber-Key of the application who delivered the notification
31
+ # internal_key: (String) "<klass>/<action>"
32
+ # uuid: Unique notification identifier
25
33
  def initialize(data, info, headers = {})
26
34
  @data = data.deep_symbolize_keys
27
35
  @info = info.deep_symbolize_keys
@@ -75,6 +83,16 @@ module PubSubModelSync
75
83
  klass.publish(self)
76
84
  end
77
85
 
86
+ # @param attr_keys (Array<Symbol>) List of attributes to be excluded from payload
87
+ def exclude_data_attrs(attr_keys)
88
+ @data = data.except(*attr_keys)
89
+ end
90
+
91
+ # Attributes to always be delivered after cache optimization
92
+ def cache_settings
93
+ headers[:cache]
94
+ end
95
+
78
96
  # convert payload data into Payload
79
97
  # @param data [Hash]: payload data (:data, :info, :headers)
80
98
  def self.from_payload_data(data)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubSubModelSync
4
+ class PayloadCacheOptimizer < PubSubModelSync::Base
5
+ # Optimizes payload data to deliver only the required ones and the changed ones and thus avoid
6
+ # delivering unnecessary notifications.
7
+ # Uses Rails.cache to retrieve previous delivered data.
8
+ attr_reader :payload, :required_attrs, :cache_key
9
+
10
+ # @param payload (Payload)
11
+ def initialize(payload)
12
+ @payload = payload
13
+ @cache_key = "pubsub/#{payload.headers[:internal_key]}/#{payload.headers[:topic_name]}"
14
+ end
15
+
16
+ # @return (:already_sent|Payload)
17
+ def call
18
+ backup_data = payload.data.clone
19
+ return payload if cache_disabled?
20
+ return :already_sent if previous_payload_data == payload.data
21
+
22
+ optimize_payload if optimization_enabled?
23
+ Rails.cache.write(cache_key, backup_data, expires_in: 1.week)
24
+ payload
25
+ end
26
+
27
+ private
28
+
29
+ def optimization_enabled?
30
+ previous_payload_data && payload.cache_settings.is_a?(Hash)
31
+ end
32
+
33
+ def cache_disabled?
34
+ res = config.skip_cache || Rails.cache.nil?
35
+ log("Skipping cache, it was disabled: #{[payload]}") if res && debug?
36
+ res
37
+ end
38
+
39
+ def previous_payload_data
40
+ @previous_payload_data ||= Rails.cache.read(cache_key)
41
+ end
42
+
43
+ def optimize_payload # rubocop:disable Metrics/AbcSize
44
+ changed_keys = Hash[(payload.data.to_a - previous_payload_data.to_a)].keys
45
+ invalid_keys = payload.data.keys - (changed_keys + payload.cache_settings[:required])
46
+ log("Excluding non changed attributes: #{invalid_keys} from: #{payload.inspect}") if debug?
47
+ payload.exclude_data_attrs(invalid_keys)
48
+ end
49
+ end
50
+ 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[forced_ordering_key]
27
+ not_important_keys = %i[forced_ordering_key cache]
28
28
  reduce_payload_size = !config.debug
29
29
  data[:headers].except!(*not_important_keys) if reduce_payload_size
30
30
  data.to_json
@@ -81,8 +81,8 @@ module PubSubModelSync
81
81
  def subscribe_to_topics
82
82
  topics.map do |key, topic|
83
83
  subs_name = "#{config.subscription_key}_#{key}"
84
- subscription = topic.subscription(subs_name) || topic.subscribe(subs_name, SUBSCRIPTION_SETTINGS)
85
- subscriber = subscription.listen(LISTEN_SETTINGS, &method(:process_message))
84
+ subscription = topic.subscription(subs_name) || topic.subscribe(subs_name, **SUBSCRIPTION_SETTINGS)
85
+ subscriber = subscription.listen(**LISTEN_SETTINGS, &method(:process_message))
86
86
  subscriber.start
87
87
  log("Subscribed to topic: #{topic.name} as: #{subs_name}")
88
88
  subscriber
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '1.2.1'
4
+ VERSION = '1.4.0'
5
5
  end
@@ -16,6 +16,7 @@ require 'pub_sub_model_sync/message_processor'
16
16
  require 'pub_sub_model_sync/run_subscriber'
17
17
 
18
18
  require 'pub_sub_model_sync/payload_builder'
19
+ require 'pub_sub_model_sync/payload_cache_optimizer'
19
20
  require 'pub_sub_model_sync/subscriber'
20
21
 
21
22
  require 'pub_sub_model_sync/service_base'
data/samples/README.md CHANGED
@@ -2,13 +2,27 @@
2
2
  This is a sample to sync information between rails applications using RabbitMQ
3
3
 
4
4
  ## Installation
5
- * Create manually the required network to share rabbitMQ (just if not exist):
5
+ 1. Create manually the required network to share rabbitMQ accross Rails applications (just if not exist):
6
6
  ```docker network create shared_app_services```
7
7
 
8
- * Start RabbitMQ server
8
+ 2. Start RabbitMQ server
9
9
  ```cd samples/app1 && docker-compose up pubsub```
10
10
 
11
- * In another tab access to App1 to publish notifications (Wait for step 2)
11
+ 3. In another tab access to App2 to listen notifications (Wait for step 2)
12
+ - Access to the folder
13
+ `cd samples/app2`
14
+
15
+ - Build docker and start listener (Received notifications will be printed here)
16
+ ```docker-compose run listener```
17
+
18
+ - Optional: Open another tab to access application to ensure synced data
19
+ ```docker-compose run listener bash -c "rails c```
20
+ ```ruby
21
+ user = User.last.inspect
22
+ user.posts.inspect
23
+ ```
24
+
25
+ 4. In another tab access to App1 to publish notifications (Wait for step 2)
12
26
  - Access to the application
13
27
  `cd samples/app1`
14
28
 
@@ -33,18 +47,4 @@ This is a sample to sync information between rails applications using RabbitMQ
33
47
  ```ruby
34
48
  user.destroy!
35
49
  ```
36
-
37
- * In another tab access to App2 to listen notifications (Wait for step 2)
38
- - Access to the folder
39
- `cd samples/app2`
40
-
41
- - Build docker and start listener (Received notifications will be printed here)
42
- ```docker-compose run listener```
43
50
 
44
- - Optional: Open another tab to access application to ensure synced data
45
- ```docker-compose run listener bash -c "rails c```
46
- ```ruby
47
- user = User.last.inspect
48
- user.posts.inspect
49
- ```
50
-
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.2.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-28 00:00:00.000000000 Z
11
+ date: 2022-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -107,6 +107,7 @@ files:
107
107
  - gemfiles/Gemfile_4
108
108
  - gemfiles/Gemfile_5
109
109
  - gemfiles/Gemfile_6
110
+ - gemfiles/Gemfile_7
110
111
  - lib/pub_sub_model_sync.rb
111
112
  - lib/pub_sub_model_sync/base.rb
112
113
  - lib/pub_sub_model_sync/config.rb
@@ -118,6 +119,7 @@ files:
118
119
  - lib/pub_sub_model_sync/mock_rabbit_service.rb
119
120
  - lib/pub_sub_model_sync/payload.rb
120
121
  - lib/pub_sub_model_sync/payload_builder.rb
122
+ - lib/pub_sub_model_sync/payload_cache_optimizer.rb
121
123
  - lib/pub_sub_model_sync/publisher_concern.rb
122
124
  - lib/pub_sub_model_sync/railtie.rb
123
125
  - lib/pub_sub_model_sync/run_subscriber.rb