pub_sub_model_sync 1.1.1 → 1.3.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: 84f478587ccd28d92b653744e72d338cf53818e5286544ff2c6e1e982a5a12a7
4
- data.tar.gz: 32266d8a3ba69cad2e716c4861914b62e87ee96cf226ecf8f67dab3f909d9856
3
+ metadata.gz: 2df220f5f010c22b60791382718383661ae2d4200bb63d4cf3e03a2b096cc740
4
+ data.tar.gz: 84244d1c98800f15d54e3fbfdd3897fa05aea6f7285a8da5f1fcfb67d61513be
5
5
  SHA512:
6
- metadata.gz: 02a727027a13d0b55f330395c95f9077568efb74fae3927c941211a4d849910fd6c071dae5fdd9386b3bb34c5257389cd74e4fcf5979e29f665a9b8e9e93aa95
7
- data.tar.gz: 896f7430fbc83640ef3141a9e24413de69034ebc2d9e6a66987d734e25e9c41e52f03aa23faa494d9f05d0e4628acd058082e729a1de00c513fb0fb1c2cca70e
6
+ metadata.gz: eb2df15c0f909204e165b6d4e69d97434fcd50bc3ce3169086840753879b5ca0bf4de3942da0354e2528a203563084258ab3c6b003969336ae12311105e3a316
7
+ data.tar.gz: d3f4660cc54518d11bb66880a9c99100eb9e757cc9c1087b8a4a59cff976178d3f9d5fe97beddac55395b392083a381df9828a8dace92087f129a7130265759f
@@ -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,7 +1,13 @@
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
+
3
9
  # 1.1.1 (October 25, 2021)
4
- - feat: include `ordering_key topic_name` when delivering a notification for debugging purposes
10
+ - feat: do not exclude `ordering_key topic_name` when delivering a notification (required when debugging)
5
11
  - doc: improve docs
6
12
 
7
13
  # 1.1.0 (October 25, 2021)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (1.1.1)
4
+ pub_sub_model_sync (1.3.1)
5
5
  rails
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -311,14 +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
- - `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
- - `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).
316
- Note: Final `ordering_key` is calculated as: `payload.headers[:forced_ordering_key] || current_transaction&.key || payload.headers[:ordering_key]`
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
317
  - `topic_name`: (String|Array<String>, optional): Specific topic name where to deliver the notification (default `PubSubModelSync::Config.topic_name`).
318
318
  - `forced_ordering_key`: (String, optional): Overrides `ordering_key` with the provided value even withing transactions. Default `nil`.
319
319
  - `app_key`: (Auto calculated): Name of the application who delivered the notification.
320
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).
321
+ Note: To reduce Payload size, some header info are not delivered (Enable debug mode to deliver all payload info).
322
322
 
323
323
  - Actions
324
324
  ```ruby
@@ -343,21 +343,21 @@ Any notification before delivering is transformed as a Payload for a better port
343
343
  ps_after_action([:create, :update, :destroy]) { |action| ps_publish(action, mapping: %i[id user_id title]) }
344
344
  end
345
345
  ```
346
- - 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)
347
347
  ```ruby
348
348
  user = User.create!(name: 'test', posts_attributes: [{ title: 'Post 1' }, { title: 'Post 2' }])
349
349
  # notification #1 => <Payload data: {id: 1, name: 'sample'}, info: { klass: 'User', action: :create, mode: :model }, headers: { ordering_key = `User/1` }>
350
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` }>
351
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` }>
352
352
  ```
353
- - 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)
354
354
  ```ruby
355
355
  user.update!(name: 'changed', posts_attributes: [{ id: 1, title: 'Post 1C' }, { id: 2, title: 'Post 2C' }])
356
356
  # notification #1 => <Payload data: {id: 1, name: 'changed'}, info: { klass: 'User', action: :update, mode: :model }, headers: { ordering_key = `User/1` }>
357
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` }>
358
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` }>
359
359
  ```
360
- - 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)
361
361
  **Note**: The notifications order were reordered in order to avoid inconsistency in other apps
362
362
  ```ruby
363
363
  user.destroy!
@@ -371,7 +371,7 @@ Any notification before delivering is transformed as a Payload for a better port
371
371
 
372
372
  - Manual transactions
373
373
  `PubSubModelSync::MessagePublisher::transaction(key, max_buffer: , &block)`
374
- - `key` (String|nil) Key used as the ordering key for all inner notifications (When nil, will use `ordering_key` of the first notification)
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
375
  - `max_buffer:` (Integer, default: `PubSubModelSync::Config.transactions_max_buffer`) Transaction buffer size (more details in #transactions_max_buffer).
376
376
  Sample:
377
377
  ```ruby
@@ -386,37 +386,15 @@ Any notification before delivering is transformed as a Payload for a better port
386
386
  ## **Testing with RSpec**
387
387
  - Config: (spec/rails_helper.rb)
388
388
  ```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
389
  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
390
+ # disable delivering notifications to pubsub
391
+ allow(PubSubModelSync::MessagePublisher).to receive(:connector_publish)
392
+ # disable all models sync by default (reduces testing time by avoiding to build payload data)
393
+ allow(PubSubModelSync::MessagePublisher).to receive(:publish_model)
415
394
  end
416
395
 
417
396
  # enable all models sync only for tests that includes 'sync: true'
418
397
  config.before(:each, sync: true) do
419
- allow(PubSubModelSync::MessagePublisher).to receive(:publish_data).and_call_original
420
398
  allow(PubSubModelSync::MessagePublisher).to receive(:publish_model).and_call_original
421
399
  end
422
400
 
@@ -426,9 +404,9 @@ Any notification before delivering is transformed as a Payload for a better port
426
404
  # end
427
405
  ```
428
406
  - Examples:
429
- - **Publisher**
407
+ - **Publisher**
408
+ Note: **Do not forget to include 'sync: true'** to enable publishing pubsub notifications
430
409
  ```ruby
431
- # Do not forget to include 'sync: true' to enable publishing pubsub notifications
432
410
  describe 'When publishing sync', truncate: true, sync: true do
433
411
  it 'publishes user notification when created' do
434
412
  expect_publish_notification(:create, klass: 'User')
@@ -557,6 +535,7 @@ config.debug = true
557
535
  Note2: Only notifications from the buffer can be rollbacked if the current transaction has failed.
558
536
 
559
537
  ## **TODO**
538
+ - add the ability to raise SKIP_ACKNOWLEDGE to auto retry by PubSub
560
539
  - Auto publish update only if payload has changed (see ways to compare previous payload vs new payload)
561
540
  - Improve transactions to exclude similar notifications by klass and action. Sample:
562
541
  ```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 })```
@@ -568,6 +547,7 @@ config.debug = true
568
547
  - Add if/unless to ps_after_action
569
548
  - Add subscription liveness checker using thread without db connection to check periodically pending notifications from google pubsub
570
549
  - Unify .stop() and 'Listener stopped'
550
+ - TODO: Publish new version 1.2.1 (improve logs)
571
551
 
572
552
  ## **Q&A**
573
553
  - I'm getting error "could not obtain a connection from the pool within 5.000 seconds"... what does this mean?
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
@@ -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
@@ -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
 
@@ -77,12 +77,12 @@ 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}"
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
@@ -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.1.1'
4
+ VERSION = '1.3.1'
5
5
  end
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.1.1
4
+ version: 1.3.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-10-28 00:00:00.000000000 Z
11
+ date: 2022-02-10 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