pub_sub_model_sync 1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +2 -8
- data/README.md +33 -26
- data/lib/pub_sub_model_sync/config.rb +1 -2
- data/lib/pub_sub_model_sync/message_publisher.rb +7 -7
- data/lib/pub_sub_model_sync/payload.rb +1 -1
- data/lib/pub_sub_model_sync/payload_builder.rb +3 -3
- data/lib/pub_sub_model_sync/publisher_concern.rb +5 -14
- data/lib/pub_sub_model_sync/railtie.rb +0 -1
- data/lib/pub_sub_model_sync/runner.rb +3 -5
- data/lib/pub_sub_model_sync/service_base.rb +1 -1
- data/lib/pub_sub_model_sync/service_google.rb +1 -1
- data/lib/pub_sub_model_sync/service_kafka.rb +2 -2
- data/lib/pub_sub_model_sync/subscriber_concern.rb +5 -5
- data/lib/pub_sub_model_sync/transaction.rb +1 -1
- data/lib/pub_sub_model_sync/version.rb +1 -1
- data/samples/README.md +1 -1
- data/samples/app1/Gemfile +1 -1
- data/samples/app1/Gemfile.lock +3 -3
- data/samples/app1/config/master.key +1 -0
- data/samples/app2/Gemfile +1 -1
- data/samples/app2/Gemfile.lock +3 -3
- data/samples/app2/config/master.key +1 -0
- data/samples/app2/db/development.sqlite3 +0 -0
- metadata +5 -7
- data/lib/pub_sub_model_sync/initializers/before_commit.rb +0 -23
- data/samples/app1/.gitattributes +0 -8
- data/samples/app1/.gitignore +0 -28
- data/samples/app2/.gitattributes +0 -8
- data/samples/app2/.gitignore +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec006984fe2bf9c33e9992facdd25aa9fdaa3494b6bcaf656592593829429017
|
4
|
+
data.tar.gz: 8ca60de929eb7465c95ff819682af73a9094c60294661e5c823394aeb02ed6a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d799e1e27c071e18d5d7e15460a7d481b9991b469599356e23c86669fd8de95046b236d4596a5fbc22e93d278548d2b17255262e4f5c67eece7a4f6898ff03ca
|
7
|
+
data.tar.gz: 89f6e95f040919994a0f0397b61b09ac83567b69472ce41aa722b240dba66e8a66d4e7a60cedb0de5f59d671525c4d7ad38b02255e558728f8e34e71e227e1e1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
# 1.2.0 (October 28, 2021)
|
4
|
+
- feat: rename Payload `:key` into `:internal_key` to avoid confusions while debugging
|
5
|
+
|
6
|
+
# 1.1.1 (October 25, 2021)
|
7
|
+
- feat: include `ordering_key topic_name` when delivering a notification for debugging purposes
|
8
|
+
- doc: improve docs
|
9
|
+
|
10
|
+
# 1.1.0 (October 25, 2021)
|
11
|
+
- feat: change `transactions_max_buffer` default value to 1 to deliver notifications once they were called
|
12
|
+
- feat: use `after_commit` instead of `before_commit` callback and remove the horrible AR patch for rails 4
|
13
|
+
|
14
|
+
# 1.0.1 (August 20, 2021)
|
15
|
+
- refactor: improve service exit when running in k8s
|
16
|
+
|
3
17
|
# 1.0 (June 13, 2021)
|
4
18
|
This version includes many changes that was refactored from previous version, and thus it needs manual changes to migrate into this version.
|
5
19
|
- Refactor: Subscribers param renamed `from_action` into `to_action` and added support for block or lambda
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pub_sub_model_sync (1.0)
|
4
|
+
pub_sub_model_sync (1.2.0)
|
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)
|
@@ -158,8 +154,6 @@ GEM
|
|
158
154
|
nio4r (2.5.7)
|
159
155
|
nokogiri (1.11.3-x86_64-darwin)
|
160
156
|
racc (~> 1.4)
|
161
|
-
nokogiri (1.11.3-x86_64-linux)
|
162
|
-
racc (~> 1.4)
|
163
157
|
os (1.1.1)
|
164
158
|
parallel (1.20.1)
|
165
159
|
parser (3.0.1.1)
|
@@ -266,4 +260,4 @@ DEPENDENCIES
|
|
266
260
|
sqlite3
|
267
261
|
|
268
262
|
BUNDLED WITH
|
269
|
-
2.2.
|
263
|
+
2.2.29
|
data/README.md
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
![Ruby badge](https://img.shields.io/badge/Ruby-2.4+-success.png)
|
4
4
|
![Production badge](https://img.shields.io/badge/Production-ready-success.png)
|
5
5
|
|
6
|
-
This gem permits to sync automatically models and custom data between multiple Rails applications by publishing notifications via pubsub (Google PubSub, RabbitMQ, or Apache Kafka) and automatically processed by all connected applications. Out of the scope, this gem includes transactions to keep Data consistency by processing notifications in the order they were delivered.
|
7
|
-
These notifications use JSON format to easily be decoded by subscribers (Rails applications and even other languages)
|
6
|
+
This gem permits to sync automatically models and custom data between multiple Rails applications by publishing notifications via pubsub (Google PubSub, RabbitMQ, or Apache Kafka) and automatically processed by all connected applications. Out of the scope, this gem includes transactions to keep Data consistency by processing notifications in the order they were delivered.
|
7
|
+
These notifications use JSON format to easily be decoded by subscribers (Rails applications and even other languages, soon for [Cristal-lang](https://crystal-lang.org/))
|
8
8
|
|
9
9
|
- [**PubSubModelSync**](#pubsubmodelsync)
|
10
10
|
- [**Features**](#features)
|
@@ -104,6 +104,7 @@ And then execute: $ bundle install
|
|
104
104
|
![Diagram](/docs/notifications-diagram.png?raw=true)
|
105
105
|
|
106
106
|
## **Examples**
|
107
|
+
See sample apps in [/samples](/samples/)
|
107
108
|
### **Basic Example**
|
108
109
|
```ruby
|
109
110
|
# App 1 (Publisher)
|
@@ -244,7 +245,7 @@ PubSubModelSync::Payload.new({ ids: [my_user.id] }, { klass: 'User', action: :ba
|
|
244
245
|
- `method_name` (Symbol, optional) method to be called to process action callback, sample: `def my_method(action) ... end`
|
245
246
|
- `block` (Proc, optional) Block to be called to process action callback, sample: `{ |action| ... }`
|
246
247
|
|
247
|
-
**Note1**: Due to rails callback ordering, this method uses `
|
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)).
|
248
249
|
**Note2**: Due to rails callback ordering, this method uses `after_destroy` callback when destroying models to ensure the expected notifications order.
|
249
250
|
|
250
251
|
- `ps_publish(action, data: {}, mapping: [], headers: {}, as_klass: nil)` Delivers an instance notification via pubsub
|
@@ -310,11 +311,14 @@ Any notification before delivering is transformed as a Payload for a better port
|
|
310
311
|
- `klass`: (String) Notification class name
|
311
312
|
- `mode`: (Symbol: `:model`|`:class`) Kind of notification
|
312
313
|
* `headers`: (Hash) Notification settings that defines how the notification will be processed or delivered.
|
313
|
-
- `
|
314
|
-
|
315
|
-
|
316
|
-
- `topic_name`: (String|Array<String>, optional): Specific topic name
|
317
|
-
- `forced_ordering_key`: (String, optional):
|
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).
|
318
322
|
|
319
323
|
- Actions
|
320
324
|
```ruby
|
@@ -339,21 +343,21 @@ Any notification before delivering is transformed as a Payload for a better port
|
|
339
343
|
ps_after_action([:create, :update, :destroy]) { |action| ps_publish(action, mapping: %i[id user_id title]) }
|
340
344
|
end
|
341
345
|
```
|
342
|
-
- When created (all notifications use the same
|
346
|
+
- When created (all notifications use the same ordering_key to be processed in the same order)
|
343
347
|
```ruby
|
344
348
|
user = User.create!(name: 'test', posts_attributes: [{ title: 'Post 1' }, { title: 'Post 2' }])
|
345
349
|
# notification #1 => <Payload data: {id: 1, name: 'sample'}, info: { klass: 'User', action: :create, mode: :model }, headers: { ordering_key = `User/1` }>
|
346
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` }>
|
347
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` }>
|
348
352
|
```
|
349
|
-
- When updated (all notifications use the same
|
353
|
+
- When updated (all notifications use the same ordering_key to be processed in the same order)
|
350
354
|
```ruby
|
351
355
|
user.update!(name: 'changed', posts_attributes: [{ id: 1, title: 'Post 1C' }, { id: 2, title: 'Post 2C' }])
|
352
356
|
# notification #1 => <Payload data: {id: 1, name: 'changed'}, info: { klass: 'User', action: :update, mode: :model }, headers: { ordering_key = `User/1` }>
|
353
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` }>
|
354
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` }>
|
355
359
|
```
|
356
|
-
- When destroyed (all notifications use the same
|
360
|
+
- When destroyed (all notifications use the same ordering_key to be processed in the same order)
|
357
361
|
**Note**: The notifications order were reordered in order to avoid inconsistency in other apps
|
358
362
|
```ruby
|
359
363
|
user.destroy!
|
@@ -367,10 +371,8 @@ Any notification before delivering is transformed as a Payload for a better port
|
|
367
371
|
|
368
372
|
- Manual transactions
|
369
373
|
`PubSubModelSync::MessagePublisher::transaction(key, max_buffer: , &block)`
|
370
|
-
- `key` (String|nil) Key used as the
|
371
|
-
- `max_buffer:` (
|
372
|
-
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).
|
373
|
-
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).
|
374
376
|
Sample:
|
375
377
|
```ruby
|
376
378
|
PubSubModelSync::MessagePublisher::transaction('my-custom-key') do
|
@@ -529,7 +531,7 @@ config.debug = true
|
|
529
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.
|
530
532
|
- `.subscription_name = "my-app-1"`: (String, default Rails.application.name)
|
531
533
|
Subscriber's identifier which helps to:
|
532
|
-
* skip self
|
534
|
+
* skip self notifications
|
533
535
|
* continue the sync from the last synced notification when service was restarted.
|
534
536
|
- `.default_topic_name = "my_topic"`: (String|Array<String>, optional(default first topic from `topic_name`))
|
535
537
|
Topic name used as the default topic if not defined in the payload when publishing a notification
|
@@ -538,29 +540,34 @@ config.debug = true
|
|
538
540
|
- ```.logger = Rails.logger```
|
539
541
|
(Logger) => define custom logger
|
540
542
|
- ```.on_before_processing = ->(payload, {subscriber:}) { puts payload }```
|
541
|
-
(Proc) => called before processing received
|
543
|
+
(Proc) => called before processing a received notification (:cancel can be returned to skip processing)
|
542
544
|
- ```.on_success_processing = ->(payload, {subscriber:}) { puts payload }```
|
543
|
-
(Proc) => called when a
|
545
|
+
(Proc) => called when a notification was successfully processed
|
544
546
|
- ```.on_error_processing = ->(exception, {payload:, subscriber:}) { payload.delay(...).process! }```
|
545
|
-
(Proc) => called when a
|
547
|
+
(Proc) => called when a notification has failed when processing (delayed_job or similar can be used for retrying)
|
546
548
|
- ```.on_before_publish = ->(payload) { puts payload }```
|
547
|
-
(Proc) => called before publishing a
|
549
|
+
(Proc) => called before publishing a notification (:cancel can be returned to skip publishing)
|
548
550
|
- ```.on_after_publish = ->(payload) { puts payload }```
|
549
|
-
(Proc) => called after publishing a
|
551
|
+
(Proc) => called after publishing a notification
|
550
552
|
- ```.on_error_publish = ->(exception, {payload:}) { payload.delay(...).publish! }```
|
551
|
-
(Proc) => called when failed publishing a
|
552
|
-
- ```.transactions_max_buffer =
|
553
|
-
|
554
|
-
|
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.
|
555
558
|
|
556
559
|
## **TODO**
|
557
560
|
- Auto publish update only if payload has changed (see ways to compare previous payload vs new payload)
|
558
|
-
- Improve transactions to exclude similar
|
561
|
+
- Improve transactions to exclude similar notifications by klass and action. Sample:
|
559
562
|
```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 })```
|
560
563
|
- Add DB table to use as a shield to prevent publishing similar notifications and publish partial notifications (similar idea when processing notif)
|
561
564
|
- Last notification is not being delivered immediately in google pubsub (maybe force with timeout 10secs and service.deliver_messages)
|
562
565
|
- Update folder structure
|
563
566
|
- Services support to deliver multiple payloads from transactions
|
567
|
+
- Fix deprecation warnings: pub_sub_model_sync/service_google.rb:39: warning: Splitting the last argument into positional and keyword parameters is deprecated
|
568
|
+
- Add if/unless to ps_after_action
|
569
|
+
- Add subscription liveness checker using thread without db connection to check periodically pending notifications from google pubsub
|
570
|
+
- Unify .stop() and 'Listener stopped'
|
564
571
|
|
565
572
|
## **Q&A**
|
566
573
|
- 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) {
|
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
|
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)
|
@@ -54,9 +54,9 @@ module PubSubModelSync
|
|
54
54
|
publish(payload)
|
55
55
|
end
|
56
56
|
|
57
|
-
# @param model (ActiveRecord::Base)
|
58
|
-
# @param action (Symbol
|
59
|
-
# @param settings (Hash
|
57
|
+
# @param model (ActiveRecord::Base,PubSubModelSync::PublisherConcern)
|
58
|
+
# @param action (Symbol,String @see PublishConcern::ps_publish)
|
59
|
+
# @param settings (Hash @see PayloadBuilder.settings)
|
60
60
|
def publish_model(model, action, settings = {})
|
61
61
|
log("Building payload for: #{[model, action].inspect}") if config.debug
|
62
62
|
payload = PubSubModelSync::PayloadBuilder.new(model, action, settings).call
|
@@ -80,7 +80,7 @@ module PubSubModelSync
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def connector_publish(payload)
|
83
|
-
log("Publishing message #{payload
|
83
|
+
log("Publishing message #{[payload]}...") if config.debug
|
84
84
|
connector.publish(payload)
|
85
85
|
log("Published message: #{[payload]}")
|
86
86
|
config.on_after_publish.call(payload)
|
@@ -99,7 +99,7 @@ module PubSubModelSync
|
|
99
99
|
|
100
100
|
def ensure_publish(payload)
|
101
101
|
cancelled = config.on_before_publish.call(payload) == :cancel
|
102
|
-
log("Publish cancelled by config.on_before_publish: #{payload}") if config.debug && cancelled
|
102
|
+
log("Publish cancelled by config.on_before_publish: #{[payload]}") if config.debug && cancelled
|
103
103
|
!cancelled
|
104
104
|
end
|
105
105
|
|
@@ -110,7 +110,7 @@ module PubSubModelSync
|
|
110
110
|
def ensure_model_publish(model, action, payload)
|
111
111
|
res_before = model.ps_before_publish(action, payload)
|
112
112
|
cancelled = res_before == :cancel
|
113
|
-
log("Publish cancelled by model.ps_before_publish: #{payload}") if config.debug && cancelled
|
113
|
+
log("Publish cancelled by model.ps_before_publish: #{[payload]}") if config.debug && cancelled
|
114
114
|
!cancelled
|
115
115
|
end
|
116
116
|
|
@@ -86,7 +86,7 @@ module PubSubModelSync
|
|
86
86
|
|
87
87
|
def build_headers
|
88
88
|
headers[:app_key] ||= PubSubModelSync::Config.subscription_key
|
89
|
-
headers[:
|
89
|
+
headers[:internal_key] ||= [klass, action].join('/')
|
90
90
|
headers[:ordering_key] ||= klass
|
91
91
|
headers[:uuid] ||= SecureRandom.uuid
|
92
92
|
end
|
@@ -4,7 +4,7 @@ module PubSubModelSync
|
|
4
4
|
class PayloadBuilder < PubSubModelSync::Base
|
5
5
|
attr_accessor :model, :action, :data, :mapping, :headers, :as_klass
|
6
6
|
|
7
|
-
# @param model (ActiveRecord::Base)
|
7
|
+
# @param model (ActiveRecord::Base,PubSubModelSync::PublisherConcern)
|
8
8
|
# @param action (@see PublishConcern::ps_publish)
|
9
9
|
# @param settings (@see PublishConcern::ps_publish): { data:, mapping:, headers:, as_klass: }
|
10
10
|
def initialize(model, action, settings = {})
|
@@ -43,8 +43,8 @@ module PubSubModelSync
|
|
43
43
|
|
44
44
|
def headers_data
|
45
45
|
klass_name = model.class.name
|
46
|
-
|
47
|
-
def_data = { ordering_key: self.class.ordering_key_for(model),
|
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
|
|
@@ -18,14 +18,14 @@ module PubSubModelSync
|
|
18
18
|
alias ps_after_sync ps_after_publish # @deprecated
|
19
19
|
|
20
20
|
# Delivers a notification via pubsub
|
21
|
-
# @param action (
|
21
|
+
# @param action (Symbol,String) Sample: create|update|save|destroy|<any_other_key>
|
22
22
|
# @param mapping? (Array<String>) If present will generate data using the mapping and added to the payload.
|
23
23
|
# Sample: ["id", "full_name:name"]
|
24
|
-
# @param data? (Hash
|
24
|
+
# @param data? (Hash,Symbol,Proc)
|
25
25
|
# Hash: Data to be added to the payload
|
26
26
|
# Symbol: Method name to be called to retrieve payload data (must return a hash value, receives :action name)
|
27
27
|
# Proc: Block to be called to retrieve payload data
|
28
|
-
# @param headers? (Hash
|
28
|
+
# @param headers? (Hash,Symbol,Proc): (All available attributes in @Payload.headers)
|
29
29
|
# Hash: Data that will be merged with default header values
|
30
30
|
# Symbol: Method name that will be called to retrieve header values (must return a hash, receives :action name)
|
31
31
|
# Proc: Block to be called to retrieve header values
|
@@ -57,7 +57,7 @@ module PubSubModelSync
|
|
57
57
|
klass.publish_data((as_klass || name).to_s, data, action.to_sym, headers: headers)
|
58
58
|
end
|
59
59
|
|
60
|
-
# @param crud_actions (Symbol
|
60
|
+
# @param crud_actions (Symbol,Array<Symbol>): :create, :update, :destroy
|
61
61
|
# @param method_name (Symbol, optional) method to be called
|
62
62
|
def ps_after_action(crud_actions, method_name = nil, &block)
|
63
63
|
actions = Array(crud_actions).map(&:to_sym)
|
@@ -67,7 +67,7 @@ module PubSubModelSync
|
|
67
67
|
if action == :destroy
|
68
68
|
after_destroy { instance_exec(action, &callback) }
|
69
69
|
else
|
70
|
-
|
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
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'active_support/core_ext/module'
|
4
4
|
module PubSubModelSync
|
5
5
|
class Runner
|
6
|
-
class ShutDown < StandardError; end
|
7
6
|
delegate :preload_listeners, to: :class
|
8
7
|
attr_accessor :connector
|
9
8
|
|
@@ -12,11 +11,10 @@ module PubSubModelSync
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def run
|
14
|
+
at_exit { connector.stop }
|
15
15
|
trap_signals!
|
16
16
|
preload_listeners
|
17
17
|
start_listeners
|
18
|
-
rescue ShutDown
|
19
|
-
connector.stop
|
20
18
|
end
|
21
19
|
|
22
20
|
def self.preload_listeners
|
@@ -32,8 +30,8 @@ module PubSubModelSync
|
|
32
30
|
|
33
31
|
def trap_signals!
|
34
32
|
handler = proc do |signal|
|
35
|
-
puts "received #{Signal.signame(signal)}"
|
36
|
-
|
33
|
+
puts "PS_MSYNC ==> received #{Signal.signame(signal)}"
|
34
|
+
exit
|
37
35
|
end
|
38
36
|
%w[INT QUIT TERM].each { |signal| Signal.trap(signal, handler) }
|
39
37
|
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[
|
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
|
@@ -76,8 +76,8 @@ module PubSubModelSync
|
|
76
76
|
end
|
77
77
|
|
78
78
|
# Check topic existence, create if missing topic
|
79
|
-
# @param names (Array<String
|
80
|
-
# @return (Array
|
79
|
+
# @param names (Array<String>,String)
|
80
|
+
# @return (Array,String) return @param names
|
81
81
|
def ensure_topics(names)
|
82
82
|
missing_topics = Array(names) - (@known_topics || service.topics)
|
83
83
|
missing_topics.each do |name|
|
@@ -9,16 +9,16 @@ module PubSubModelSync
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
# @param actions (Symbol
|
12
|
+
# @param actions (Symbol,Array<Symbol>) Notification.action name: save|create|update|destroy|<any_other_action>
|
13
13
|
# @param mapping (Array<String,Symbol>) Attributes mapping with aliasing support, sample: ["id", "full_name:name"]
|
14
14
|
# @param settings (Hash<:from_klass, :to_action, :id, :if, :unless>)
|
15
15
|
# from_klass (String) Notification.class name
|
16
|
-
# to_action (Symbol
|
16
|
+
# to_action (Symbol,Proc):
|
17
17
|
# Symbol: Method to process the notification
|
18
18
|
# Proc: Block to process the notification
|
19
|
-
# id (Symbol
|
20
|
-
# if (Symbol
|
21
|
-
# unless (Symbol
|
19
|
+
# id (Symbol,Array<Symbol,String>) attribute(s) DB primary identifier(s). Supports for mapping format.
|
20
|
+
# if (Symbol,Proc,Array<Symbol>) Method or block called as the conformation before calling the callback
|
21
|
+
# unless (Symbol,Proc,Array<Symbol>) Method or block called as the negation before calling the callback
|
22
22
|
def ps_subscribe(actions, mapping = [], settings = {}, &block)
|
23
23
|
settings[:to_action] ||= block if block
|
24
24
|
Array(actions).map do |action|
|
@@ -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
|
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
|
data/samples/README.md
CHANGED
@@ -6,7 +6,7 @@ This is a sample to sync information between rails applications using RabbitMQ
|
|
6
6
|
```docker network create shared_app_services```
|
7
7
|
|
8
8
|
* Start RabbitMQ server
|
9
|
-
```docker-compose up pubsub```
|
9
|
+
```cd samples/app1 && docker-compose up pubsub```
|
10
10
|
|
11
11
|
* In another tab access to App1 to publish notifications (Wait for step 2)
|
12
12
|
- Access to the application
|
data/samples/app1/Gemfile
CHANGED
@@ -18,7 +18,7 @@ gem 'bootsnap', '>= 1.4.4', require: false
|
|
18
18
|
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
|
19
19
|
# gem 'rack-cors'
|
20
20
|
|
21
|
-
gem 'pub_sub_model_sync'
|
21
|
+
gem 'pub_sub_model_sync', '>= 1.0'
|
22
22
|
gem 'bunny' # to use rabbit-mq pub/sub service
|
23
23
|
gem 'annotate'
|
24
24
|
|
data/samples/app1/Gemfile.lock
CHANGED
@@ -96,7 +96,7 @@ GEM
|
|
96
96
|
nokogiri (1.11.3)
|
97
97
|
mini_portile2 (~> 2.5.0)
|
98
98
|
racc (~> 1.4)
|
99
|
-
pub_sub_model_sync (1.0
|
99
|
+
pub_sub_model_sync (1.0)
|
100
100
|
rails
|
101
101
|
puma (5.3.1)
|
102
102
|
nio4r (~> 2.0)
|
@@ -160,7 +160,7 @@ DEPENDENCIES
|
|
160
160
|
bunny
|
161
161
|
byebug
|
162
162
|
listen (~> 3.3)
|
163
|
-
pub_sub_model_sync (
|
163
|
+
pub_sub_model_sync (>= 1.0)
|
164
164
|
puma (~> 5.0)
|
165
165
|
rails (~> 6.1.3, >= 6.1.3.2)
|
166
166
|
spring
|
@@ -168,4 +168,4 @@ DEPENDENCIES
|
|
168
168
|
tzinfo-data
|
169
169
|
|
170
170
|
BUNDLED WITH
|
171
|
-
2.
|
171
|
+
2.2.17
|
@@ -0,0 +1 @@
|
|
1
|
+
f76db08751757e296d844ca18dcdb0a1
|
data/samples/app2/Gemfile
CHANGED
@@ -18,7 +18,7 @@ gem 'bootsnap', '>= 1.4.4', require: false
|
|
18
18
|
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
|
19
19
|
# gem 'rack-cors'
|
20
20
|
|
21
|
-
gem 'pub_sub_model_sync'
|
21
|
+
gem 'pub_sub_model_sync', '>= 1.0'
|
22
22
|
gem 'bunny' # to use rabbit-mq pub/sub service
|
23
23
|
gem 'annotate'
|
24
24
|
|
data/samples/app2/Gemfile.lock
CHANGED
@@ -96,7 +96,7 @@ GEM
|
|
96
96
|
nokogiri (1.11.3)
|
97
97
|
mini_portile2 (~> 2.5.0)
|
98
98
|
racc (~> 1.4)
|
99
|
-
pub_sub_model_sync (1.0
|
99
|
+
pub_sub_model_sync (1.0)
|
100
100
|
rails
|
101
101
|
puma (5.3.1)
|
102
102
|
nio4r (~> 2.0)
|
@@ -160,7 +160,7 @@ DEPENDENCIES
|
|
160
160
|
bunny
|
161
161
|
byebug
|
162
162
|
listen (~> 3.3)
|
163
|
-
pub_sub_model_sync (
|
163
|
+
pub_sub_model_sync (>= 1.0)
|
164
164
|
puma (~> 5.0)
|
165
165
|
rails (~> 6.1.3, >= 6.1.3.2)
|
166
166
|
spring
|
@@ -168,4 +168,4 @@ DEPENDENCIES
|
|
168
168
|
tzinfo-data
|
169
169
|
|
170
170
|
BUNDLED WITH
|
171
|
-
2.
|
171
|
+
2.2.17
|
@@ -0,0 +1 @@
|
|
1
|
+
ca702da1b8c8f8405ed03739ca90b0e5
|
Binary file
|
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:
|
4
|
+
version: 1.2.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-
|
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
|
@@ -134,8 +133,6 @@ files:
|
|
134
133
|
- lib/pub_sub_model_sync/version.rb
|
135
134
|
- pub_sub_model_sync.gemspec
|
136
135
|
- samples/README.md
|
137
|
-
- samples/app1/.gitattributes
|
138
|
-
- samples/app1/.gitignore
|
139
136
|
- samples/app1/Dockerfile
|
140
137
|
- samples/app1/Gemfile
|
141
138
|
- samples/app1/Gemfile.lock
|
@@ -168,6 +165,7 @@ files:
|
|
168
165
|
- samples/app1/config/initializers/pubsub.rb
|
169
166
|
- samples/app1/config/initializers/wrap_parameters.rb
|
170
167
|
- samples/app1/config/locales/en.yml
|
168
|
+
- samples/app1/config/master.key
|
171
169
|
- samples/app1/config/puma.rb
|
172
170
|
- samples/app1/config/routes.rb
|
173
171
|
- samples/app1/config/spring.rb
|
@@ -177,8 +175,6 @@ files:
|
|
177
175
|
- samples/app1/db/seeds.rb
|
178
176
|
- samples/app1/docker-compose.yml
|
179
177
|
- samples/app1/log/.keep
|
180
|
-
- samples/app2/.gitattributes
|
181
|
-
- samples/app2/.gitignore
|
182
178
|
- samples/app2/Dockerfile
|
183
179
|
- samples/app2/Gemfile
|
184
180
|
- samples/app2/Gemfile.lock
|
@@ -211,9 +207,11 @@ files:
|
|
211
207
|
- samples/app2/config/initializers/pubsub.rb
|
212
208
|
- samples/app2/config/initializers/wrap_parameters.rb
|
213
209
|
- samples/app2/config/locales/en.yml
|
210
|
+
- samples/app2/config/master.key
|
214
211
|
- samples/app2/config/puma.rb
|
215
212
|
- samples/app2/config/routes.rb
|
216
213
|
- samples/app2/config/spring.rb
|
214
|
+
- samples/app2/db/development.sqlite3
|
217
215
|
- samples/app2/db/migrate/20210513080956_create_customers.rb
|
218
216
|
- samples/app2/db/migrate/20210513135203_create_posts.rb
|
219
217
|
- samples/app2/db/schema.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
|
data/samples/app1/.gitattributes
DELETED
data/samples/app1/.gitignore
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
-
#
|
3
|
-
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
-
# or operating system, you probably want to add a global ignore instead:
|
5
|
-
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
-
|
7
|
-
# Ignore bundler config.
|
8
|
-
/.bundle
|
9
|
-
|
10
|
-
# Ignore the default SQLite database.
|
11
|
-
/db/*.sqlite3
|
12
|
-
/db/*.sqlite3-*
|
13
|
-
|
14
|
-
# Ignore all logfiles and tempfiles.
|
15
|
-
/log/*
|
16
|
-
/tmp/*
|
17
|
-
!/log/.keep
|
18
|
-
!/tmp/.keep
|
19
|
-
|
20
|
-
# Ignore pidfiles, but keep the directory.
|
21
|
-
/tmp/pids/*
|
22
|
-
!/tmp/pids/
|
23
|
-
!/tmp/pids/.keep
|
24
|
-
|
25
|
-
.byebug_history
|
26
|
-
|
27
|
-
# Ignore master key for decrypting credentials and more.
|
28
|
-
/config/master.key
|
data/samples/app2/.gitattributes
DELETED
data/samples/app2/.gitignore
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
-
#
|
3
|
-
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
-
# or operating system, you probably want to add a global ignore instead:
|
5
|
-
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
-
|
7
|
-
# Ignore bundler config.
|
8
|
-
/.bundle
|
9
|
-
|
10
|
-
# Ignore the default SQLite database.
|
11
|
-
/db/*.sqlite3
|
12
|
-
/db/*.sqlite3-*
|
13
|
-
|
14
|
-
# Ignore all logfiles and tempfiles.
|
15
|
-
/log/*
|
16
|
-
/tmp/*
|
17
|
-
!/log/.keep
|
18
|
-
!/tmp/.keep
|
19
|
-
|
20
|
-
# Ignore pidfiles, but keep the directory.
|
21
|
-
/tmp/pids/*
|
22
|
-
!/tmp/pids/
|
23
|
-
!/tmp/pids/.keep
|
24
|
-
|
25
|
-
.byebug_history
|
26
|
-
|
27
|
-
# Ignore master key for decrypting credentials and more.
|
28
|
-
/config/master.key
|