sync_client 0.0.14 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +91 -22
- data/app/models/sync_client/pub_message.rb +1 -1
- data/app/models/sync_client/service_resource/base.rb +4 -0
- data/app/models/sync_client/sub_message.rb +2 -2
- data/lib/generators/templates/sync_client.rb +3 -3
- data/lib/sync_client/configurator.rb +1 -0
- data/lib/sync_client/publisher/active_record_publisher.rb +20 -0
- data/lib/sync_client/publisher/base_publisher.rb +39 -0
- data/lib/sync_client/publisher/mongoid_publisher.rb +28 -0
- data/lib/sync_client/publisher/poro_publisher.rb +33 -0
- data/lib/sync_client/publisher.rb +9 -41
- data/lib/sync_client/queue_publisher.rb +2 -2
- data/lib/sync_client/sync_queue.rb +2 -2
- data/lib/sync_client/task_queue/sidekiq.rb +7 -0
- data/lib/sync_client/version.rb +1 -1
- data/test/dummy/app/models/stat.rb +11 -0
- data/test/dummy/app/services/game_publisher.rb +6 -0
- data/test/dummy/config/initializers/sync_client.rb +3 -3
- data/test/dummy/log/test.log +8048 -0
- data/test/lib/publisher/active_record_publisher_test.rb +82 -0
- data/test/lib/publisher/mongoid_publisher_test.rb +84 -0
- data/test/lib/publisher/poro_publisher_test.rb +84 -0
- data/test/lib/publisher_test.rb +14 -3
- data/test/lib/queue_publisher_test.rb +9 -0
- data/test/lib/sync_queue_test.rb +7 -0
- data/test/test_helper.rb +5 -3
- metadata +99 -84
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NWFkZjA0MTY4NTkyNDdjMWJiYTQ0ZThjNjg5NzVmNzI5MDYyZmIxZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YWNjM2EzNzJhY2M3NDE4MmI5NDNkODljMzE2NzRmMjQ2MjQ3ODMxYw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjEzYjc0NjY1Yjc2M2VkMzkwNzUwYWNjNmI2MmNlNDZiNDJlMWQ3NmI2MDFh
|
10
|
+
NzQwMTJkZTUxN2ViMGE5NzQ3YzU2YmNkNmZkZWUzZDgwMDdjNzVlMjE0OGNm
|
11
|
+
ZjA4YzQ4MDJmZmY3MGUzMGNkNDQzYWM5MTY2YmU4Yjk0NmUwM2Q=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjU3ZmNkMDc3MGE2YzAwZDNmZmQ0OTVjOTk5NjQ1MjBiYTRkZDk0MzcyNjM5
|
14
|
+
NDlkZDVmNWQ3NGQ5MjJlZmQyNTQyOGNlMDg3NDU1NWRkODFjYzE5MGQwNGM5
|
15
|
+
OThhNDhmYTliNDNiMmZmODE2YzAxMzU5ODYwN2Q5ZDc3NGNhYzg=
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# SyncClient
|
2
|
+
[![Build Status][build_status_image]][build_status]
|
3
|
+
[![Coverage Status][coverage_status_image]][coverage_status]
|
4
|
+
|
5
|
+
This gem simplifies syncing data between services by using delayed job processing and a message queue for guaranteed delivery and eventual consistency. SyncClient defines messages based on a resource and action system to simplify message publishing and handling. It supports inline processing and delayed job processeding by Resque, DelayedJob, and Sidekiq. See [Queuel](https://rubygems.org/gems/sync_client) for a list of supported message queues.
|
2
6
|
|
3
|
-
This gem simplifies syncing data between services by using a resque queue and a message queue for guaranteed delivery and eventual consistency of data.
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
@@ -24,12 +27,35 @@ $ rails g sync_client:install
|
|
24
27
|
|
25
28
|
Edit configuation in `config/initializers/sync_client.rb`
|
26
29
|
|
30
|
+
## Configuration
|
31
|
+
|
32
|
+
SyncClient requires that message queue creditials, a background task queue, and message handler definitions to be defined in the configuration file. Note that any message that does not match a defined handler is simply dropped from the queue.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
SyncClient.config do |config|
|
36
|
+
|
37
|
+
config.queuel do |c|
|
38
|
+
# c.default_queue 'VenueService'
|
39
|
+
# c.credentials token: [token], project_id: 'project_id'
|
40
|
+
# c.engine :iron_mq
|
41
|
+
end
|
42
|
+
# config.background_task_queue SyncClient::Resque #or SyncClient::DelayedJob or write your own.
|
43
|
+
# config.queue_suffix 'suffix'
|
44
|
+
# config.logger Logger.new
|
45
|
+
# config.add_message_handler object_name, handler_class, actions
|
46
|
+
# config.add_message_handler 'Service::Game', 'Service::Game', [:update, :create, :destroy]
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
|
27
51
|
## Usage
|
28
52
|
|
29
|
-
|
53
|
+
### Publisher
|
54
|
+
|
55
|
+
SyncClient provides a simple interface to define what attributes to publish to a service for associated actions where the message class is the class of the resource. For models that inherit from `ActiveRecord::Base` or include `Mongoid::Document`, add the `SyncClient::Publisher` module and configure like the following:
|
30
56
|
|
31
57
|
```ruby
|
32
|
-
class Team
|
58
|
+
class Team < ActiveRecord::Base
|
33
59
|
include SyncClient::Publisher
|
34
60
|
publish_changes_of :name, :color, to: :queue, for: [:update, :destroy], if: lambda{|team| !team.name.nil?}
|
35
61
|
# options:
|
@@ -39,8 +65,34 @@ class Team
|
|
39
65
|
end
|
40
66
|
```
|
41
67
|
|
68
|
+
If you are just working with a "Plain Old Ruby Object", you can do something
|
69
|
+
like this:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class TeamSchedulerService
|
73
|
+
include SyncClient::Publisher
|
74
|
+
publish_to :queue
|
75
|
+
# options:
|
76
|
+
# for: callbacks for publishing (default: :sync)
|
77
|
+
# if/unless: condition for publishing
|
78
|
+
|
79
|
+
def do_some_stuffs
|
80
|
+
# CODEZ
|
81
|
+
sync
|
82
|
+
# return value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
And then in a seperate method, you are able to call the `sync` method and fire
|
88
|
+
the queue syncing yourself.
|
89
|
+
|
90
|
+
The `sync` method is mapped to the `:sync` action in the ServiceResource by
|
91
|
+
default, but `:create`, `:update` and `:destroy` are still available for you to
|
92
|
+
use as well.
|
42
93
|
|
43
|
-
|
94
|
+
|
95
|
+
### Poller
|
44
96
|
|
45
97
|
Run the message queue poller:
|
46
98
|
|
@@ -51,30 +103,47 @@ $ bundle exec script/sync_client restart
|
|
51
103
|
$ bundle exec script/sync_client stop
|
52
104
|
```
|
53
105
|
|
54
|
-
Define handlers for the all messages such as the following:
|
55
|
-
|
56
|
-
```ruby
|
57
|
-
class Game < SyncClient::ServiceResource::Base
|
58
|
-
attr_accessor :id
|
59
|
-
attr_accessor :starts_at
|
60
|
-
attr_accessor :ends_at
|
61
106
|
|
62
|
-
|
63
|
-
Game.create(:game_id => self.id, :starts_at => self.starts_at, :ends_at => self.ends_at)
|
64
|
-
end
|
107
|
+
### Service Resource
|
65
108
|
|
66
|
-
|
67
|
-
game = Game.find(self.id).first
|
68
|
-
game.update_attributes(:starts_at => self.starts_at, :ends_at => self.ends_at)
|
69
|
-
end
|
109
|
+
Message handlers for the actions are then defined matching the handler class set in the configuration file where each method corresponds to the message action as follows:
|
70
110
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
111
|
+
```ruby
|
112
|
+
module Service
|
113
|
+
class Game < SyncClient::ServiceResource::Base
|
114
|
+
|
115
|
+
# Determine what attributes are to be accessible using attr_accessors
|
116
|
+
attr_accessor :id
|
117
|
+
attr_accessor :starts_at
|
118
|
+
attr_accessor :ends_at
|
119
|
+
|
120
|
+
def create
|
121
|
+
Game.create(:game_id => self.id, :starts_at => self.starts_at, :ends_at => self.ends_at)
|
122
|
+
end
|
123
|
+
|
124
|
+
def update
|
125
|
+
game = Game.find(self.id).first
|
126
|
+
game.update_attributes(:starts_at => self.starts_at, :ends_at => self. ends_at)
|
127
|
+
end
|
128
|
+
|
129
|
+
def destroy
|
130
|
+
game = Game.find(self.id)
|
131
|
+
game.destroy
|
75
132
|
end
|
76
133
|
end
|
77
134
|
end
|
78
135
|
```
|
79
136
|
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
1. Fork it ( https://github.com/[my-github-username]/sync_client/fork )
|
140
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
141
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
142
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
143
|
+
5. Create a new Pull Request
|
144
|
+
|
145
|
+
[build_status]: https://travis-ci.org/sportngin/sync_client
|
146
|
+
[build_status_image]: https://travis-ci.org/sportngin/sync_client.svg?branch=master
|
147
|
+
[coverage_status]: https://coveralls.io/r/sportngin/sync_client
|
148
|
+
[coverage_status_image]: https://img.shields.io/coveralls/sportngin/sync_client.svg
|
80
149
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SyncClient
|
2
|
-
class SubMessage < Message
|
2
|
+
class SubMessage < SyncClient::Message
|
3
3
|
attr_accessor :success
|
4
4
|
|
5
5
|
def process
|
@@ -29,7 +29,7 @@ module SyncClient
|
|
29
29
|
|
30
30
|
def with_logging(&block)
|
31
31
|
SyncClient.logger.info("------------------------------------------")
|
32
|
-
SyncClient.logger.info("
|
32
|
+
SyncClient.logger.info("Received Message: #{object_type}##{action}")
|
33
33
|
yield
|
34
34
|
SyncClient.logger.info("Error Occured: #{error}") if error
|
35
35
|
SyncClient.logger.info("Processed Message: #{!!success}")
|
@@ -2,12 +2,12 @@ SyncClient.config do |config|
|
|
2
2
|
|
3
3
|
config.queuel do |c|
|
4
4
|
# c.default_queue 'VenueService'
|
5
|
-
# c.credentials token:
|
5
|
+
# c.credentials token: [token], project_id: 'project_id'
|
6
6
|
# c.engine :iron_mq
|
7
7
|
end
|
8
8
|
# config.background_task_queue SyncClient::Resque #or SyncClient::DelayedJob or write your own.
|
9
9
|
# config.queue_suffix 'suffix'
|
10
10
|
# config.logger Logger.new
|
11
|
-
# config.
|
12
|
-
# config.
|
11
|
+
# config.add_message_handler object_name, handler_class, actions
|
12
|
+
# config.add_message_handler 'StatNgin::Game', 'Resource::StatNgin::Game', [:update, :create, :destroy]
|
13
13
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'sync_client/publisher/base_publisher'
|
2
|
+
|
3
|
+
module SyncClient
|
4
|
+
module Publisher
|
5
|
+
module ActiveRecordPublisher
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
included do
|
8
|
+
include SyncClient::Publisher::BasePublisher
|
9
|
+
end
|
10
|
+
|
11
|
+
def any_attributes_changed?(attributes)
|
12
|
+
attributes.any?{|attr| send("#{attr}_changed?")}
|
13
|
+
end
|
14
|
+
|
15
|
+
def publisher_attributes
|
16
|
+
self.attributes
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SyncClient
|
2
|
+
module Publisher
|
3
|
+
module BasePublisher
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
after_create :publish_create
|
8
|
+
before_update :publish_update
|
9
|
+
after_destroy :publish_destroy
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def publish_changes_of(*attributes, options)
|
14
|
+
@queue_publisher = SyncClient::QueuePublisher.new unless @queue_publisher
|
15
|
+
@queue_publisher.add_publisher(attributes, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :queue_publisher
|
19
|
+
end
|
20
|
+
|
21
|
+
def queue_publisher
|
22
|
+
self.class.queue_publisher
|
23
|
+
end
|
24
|
+
|
25
|
+
def publish_create(options = {})
|
26
|
+
queue_publisher.publish(:create, self, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish_update(options = {})
|
30
|
+
queue_publisher.publish(:update, self, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish_destroy(options = {})
|
34
|
+
queue_publisher.publish(:destroy, self, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'sync_client/publisher/base_publisher'
|
2
|
+
|
3
|
+
module SyncClient
|
4
|
+
module Publisher
|
5
|
+
module MongoidPublisher
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include SyncClient::Publisher::BasePublisher
|
10
|
+
end
|
11
|
+
|
12
|
+
def any_attributes_changed?(attributes)
|
13
|
+
attributes.any?{|attr| send("#{attr}_changed?")}
|
14
|
+
end
|
15
|
+
|
16
|
+
def publisher_attributes
|
17
|
+
self.attributes.inject({}) { |attrs, (raw_key, raw_value)|
|
18
|
+
if raw_key =='_id'
|
19
|
+
attrs['id'] = raw_value
|
20
|
+
else
|
21
|
+
attrs[self.aliased_fields.invert.fetch(raw_key) { raw_key }] = raw_value
|
22
|
+
end
|
23
|
+
attrs
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SyncClient
|
2
|
+
module Publisher
|
3
|
+
module PoroPublisher
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def publish_to(*args)
|
8
|
+
@queue_publisher ||= SyncClient::QueuePublisher.new
|
9
|
+
options = args.last.is_a?(Hash) ? args.pop : {:for => :sync}
|
10
|
+
args.each do |end_point|
|
11
|
+
@queue_publisher.add_publisher([], options.merge(:to => end_point))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :queue_publisher
|
16
|
+
end
|
17
|
+
|
18
|
+
def queue_publisher
|
19
|
+
self.class.queue_publisher
|
20
|
+
end
|
21
|
+
|
22
|
+
def sync(action=:sync)
|
23
|
+
queue_publisher.publish(action, self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def publisher_attributes
|
27
|
+
instance_variable_names.inject({}) do |result, var|
|
28
|
+
result.merge Hash[var.gsub('@', ''), instance_variable_get(var)]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,50 +1,18 @@
|
|
1
|
+
require 'sync_client/publisher/active_record_publisher'
|
2
|
+
require 'sync_client/publisher/mongoid_publisher'
|
3
|
+
require 'sync_client/publisher/poro_publisher'
|
4
|
+
|
1
5
|
module SyncClient
|
2
6
|
module Publisher
|
3
7
|
extend ActiveSupport::Concern
|
4
8
|
|
5
9
|
included do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
module ClassMethods
|
12
|
-
def publish_changes_of(*attributes, options)
|
13
|
-
@queue_publisher = SyncClient::QueuePublisher.new unless @queue_publisher
|
14
|
-
@queue_publisher.add_publisher(attributes, options)
|
15
|
-
end
|
16
|
-
|
17
|
-
attr_reader :queue_publisher
|
18
|
-
end
|
19
|
-
|
20
|
-
def queue_publisher
|
21
|
-
self.class.queue_publisher
|
22
|
-
end
|
23
|
-
|
24
|
-
def publish_update
|
25
|
-
queue_publisher.publish(:update, self)
|
26
|
-
end
|
27
|
-
|
28
|
-
def publish_destroy
|
29
|
-
queue_publisher.publish(:destroy, self)
|
30
|
-
end
|
31
|
-
|
32
|
-
def publish_create
|
33
|
-
queue_publisher.publish(:create, self)
|
34
|
-
end
|
35
|
-
|
36
|
-
def publisher_attributes
|
37
|
-
if self.respond_to?(:aliased_fields)
|
38
|
-
self.attributes.inject({}) { |attrs, (raw_key, raw_value)|
|
39
|
-
if raw_key =='_id'
|
40
|
-
attrs['id'] = raw_value
|
41
|
-
else
|
42
|
-
attrs[self.aliased_fields.invert.fetch(raw_key) { raw_key }] = raw_value
|
43
|
-
end
|
44
|
-
attrs
|
45
|
-
}
|
10
|
+
if defined?(::ActiveRecord::Base) && self <= ::ActiveRecord::Base
|
11
|
+
include SyncClient::Publisher::ActiveRecordPublisher
|
12
|
+
elsif defined?(::Mongoid::Document) && self.included_modules.include?(::Mongoid::Document)
|
13
|
+
include SyncClient::Publisher::MongoidPublisher
|
46
14
|
else
|
47
|
-
|
15
|
+
include SyncClient::Publisher::PoroPublisher
|
48
16
|
end
|
49
17
|
end
|
50
18
|
end
|
@@ -10,10 +10,10 @@ module SyncClient
|
|
10
10
|
self.sync_queues << SyncClient::SyncQueue.new(attributes, options)
|
11
11
|
end
|
12
12
|
|
13
|
-
def publish(action, object)
|
13
|
+
def publish(action, object, options = {})
|
14
14
|
sync_queues.each do |sync_queue|
|
15
15
|
SyncClient.logger.info("#{sync_queue.inspect}")
|
16
|
-
queue_message(action, object, sync_queue.queue).publish if sync_queue.publishable?(action, object)
|
16
|
+
queue_message(action, object, sync_queue.queue).publish if options[:force] || sync_queue.publishable?(action, object)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -10,7 +10,7 @@ module SyncClient
|
|
10
10
|
def initialize(attributes, options)
|
11
11
|
@attributes = attributes
|
12
12
|
@queue = options[:to]
|
13
|
-
@callbacks = options[:for]
|
13
|
+
@callbacks = options[:for] ? Array(options[:for]) : CALLBACK_DEFAULTS
|
14
14
|
@options = options
|
15
15
|
end
|
16
16
|
|
@@ -19,7 +19,7 @@ module SyncClient
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def update?(action, object)
|
22
|
-
action.to_s != 'update' ||
|
22
|
+
action.to_s != 'update' || object.any_attributes_changed?(attributes)
|
23
23
|
end
|
24
24
|
|
25
25
|
def resolve_condition(object)
|
data/lib/sync_client/version.rb
CHANGED
@@ -2,13 +2,13 @@ SyncClient.config do |config|
|
|
2
2
|
|
3
3
|
config.queuel do |c|
|
4
4
|
c.default_queue 'Dummy'
|
5
|
-
|
5
|
+
c.credentials token: '', project_id: ''
|
6
6
|
# c.engine :iron_mq
|
7
7
|
end
|
8
8
|
config.background_task_queue SyncClient::InlineTaskQueue
|
9
9
|
config.queue_suffix ''
|
10
10
|
config.logger Logger.new(STDOUT)
|
11
|
-
|
12
|
-
# config.
|
11
|
+
|
12
|
+
# config.add_message_handler object_name, handler_class, actions
|
13
13
|
config.add_message_handler 'Dummy::Game', 'Game', [:update, :create, :destroy]
|
14
14
|
end
|