sync_client 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +80 -0
- data/Rakefile +27 -0
- data/app/models/jobs/sync_client_jobs/publish.rb +12 -0
- data/app/models/sync_client/message.rb +15 -0
- data/app/models/sync_client/pub_message.rb +34 -0
- data/app/models/sync_client/service_resource/base.rb +27 -0
- data/app/models/sync_client/sub_message.rb +38 -0
- data/lib/generators/sync_client/install_generator.rb +24 -0
- data/lib/generators/templates/script +8 -0
- data/lib/generators/templates/sync_client.rb +13 -0
- data/lib/sync_client.rb +46 -0
- data/lib/sync_client/configurations/message_handler.rb +15 -0
- data/lib/sync_client/configurator.rb +53 -0
- data/lib/sync_client/configurators/message_handlers.rb +15 -0
- data/lib/sync_client/engine.rb +13 -0
- data/lib/sync_client/poller.rb +12 -0
- data/lib/sync_client/publisher.rb +52 -0
- data/lib/sync_client/queue_publisher.rb +28 -0
- data/lib/sync_client/sync_queue.rb +32 -0
- data/lib/sync_client/task_queue/delayed_job.rb +7 -0
- data/lib/sync_client/task_queue/inline_task_queue.rb +7 -0
- data/lib/sync_client/task_queue/resque.rb +7 -0
- data/lib/sync_client/tasks.rb +9 -0
- data/lib/sync_client/version.rb +3 -0
- data/lib/sync_client/worker.rb +15 -0
- data/test/app/pub_message_test.rb +33 -0
- data/test/app/service_resource/base_test.rb +18 -0
- data/test/app/sub_message_test.rb +42 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/game.rb +17 -0
- data/test/dummy/app/models/player.rb +8 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +61 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/sync_client.rb +14 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/lib/tasks/sync_client.rake +5 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/script/sync_client +8 -0
- data/test/lib/configurator_test.rb +14 -0
- data/test/lib/publisher_test.rb +43 -0
- data/test/lib/queue_publisher_test.rb +34 -0
- data/test/lib/sync_queue_test.rb +60 -0
- data/test/sync_client_test.rb +12 -0
- data/test/test_helper.rb +39 -0
- metadata +335 -0
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# SyncClient
|
2
|
+
|
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
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'sync_client'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Then setup your app as a client (if it isn't already):
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ rails g sync_client:install
|
23
|
+
```
|
24
|
+
|
25
|
+
Edit configuation in `config/initializers/sync_client.rb`
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Within the model you want to publish attributes to a service include something like the following:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class Team
|
33
|
+
include SyncClient::Publisher
|
34
|
+
publish_changes_of :name, :color, to: :queue, for: [:update, :destroy], if: lambda{|team| !team.name.nil?}
|
35
|
+
# options:
|
36
|
+
# to: name of queue for publishing (required)
|
37
|
+
# for: callbacks for publishing (default: :create, :update, :destroy)
|
38
|
+
# if/unless: condition for publishing
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
|
43
|
+
A priority is used for publishing to ensure eventual delivery if the message queue does not respond. Supported priority queues include Delayed Job and Resque.
|
44
|
+
|
45
|
+
Run the message queue poller:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
$ bundle exec script/sync_client start
|
49
|
+
$ bundle exec script/sync_client status
|
50
|
+
$ bundle exec script/sync_client restart
|
51
|
+
$ bundle exec script/sync_client stop
|
52
|
+
```
|
53
|
+
|
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
|
+
|
62
|
+
def create
|
63
|
+
Game.create(:game_id => self.id, :starts_at => self.starts_at, :ends_at => self.ends_at)
|
64
|
+
end
|
65
|
+
|
66
|
+
def update
|
67
|
+
game = Game.find(self.id).first
|
68
|
+
game.update_attributes(:starts_at => self.starts_at, :ends_at => self.ends_at)
|
69
|
+
end
|
70
|
+
|
71
|
+
def destroy
|
72
|
+
game = Game.find(self.id)
|
73
|
+
game.each do |ga|
|
74
|
+
ga.destroy
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
Bundler::GemHelper.install_tasks
|
16
|
+
|
17
|
+
require 'rake/testtask'
|
18
|
+
|
19
|
+
Rake::TestTask.new(:test) do |t|
|
20
|
+
t.libs << 'lib'
|
21
|
+
t.libs << 'test'
|
22
|
+
t.pattern = 'test/**/*_test.rb'
|
23
|
+
t.verbose = false
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
task :default => :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SyncClient
|
2
|
+
class Message
|
3
|
+
attr_accessor :action
|
4
|
+
attr_accessor :object_type
|
5
|
+
attr_accessor :queue
|
6
|
+
attr_accessor :object_attributes
|
7
|
+
|
8
|
+
def initialize(attrs)
|
9
|
+
attrs = {} unless attrs
|
10
|
+
attrs.each do |key, value|
|
11
|
+
send("#{key}=", value) if self.respond_to?("#{key}=")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SyncClient
|
2
|
+
class PubMessage < Message
|
3
|
+
|
4
|
+
def publish
|
5
|
+
SyncClient.background_task_queue.enqueue(Jobs::SyncClientJobs::Publish, self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def synchronous_publish
|
9
|
+
with_logging do
|
10
|
+
Queuel.with(queue_with_suffix).push package
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def package
|
15
|
+
{:action => action, :object_type => object_type_with_service, :object_attributes => object_attributes}
|
16
|
+
end
|
17
|
+
|
18
|
+
def object_type_with_service
|
19
|
+
service = Rails.application.class.parent_name
|
20
|
+
"#{service}::#{object_type}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def queue_with_suffix
|
24
|
+
SyncClient.queue_suffix.blank? ? queue.to_s : "#{queue}_#{SyncClient.queue_suffix}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_logging(&block)
|
28
|
+
SyncClient.logger.info("------------------------------------------")
|
29
|
+
SyncClient.logger.info("Publishing Message: #{object_type}##{action}")
|
30
|
+
SyncClient.logger.info("To: #{queue_with_suffix}")
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SyncClient
|
2
|
+
module ServiceResource
|
3
|
+
class Base
|
4
|
+
attr_accessor :error
|
5
|
+
|
6
|
+
# add logic to handle params
|
7
|
+
def initialize(attrs)
|
8
|
+
attrs = {} unless attrs
|
9
|
+
attrs.each do |key, value|
|
10
|
+
send("#{key}=", value) if self.respond_to?("#{key}=")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SyncClient
|
2
|
+
class SubMessage < Message
|
3
|
+
attr_accessor :success
|
4
|
+
|
5
|
+
def process
|
6
|
+
with_logging do
|
7
|
+
self.success = message_handler.send(action.to_sym) if handler_present?
|
8
|
+
end
|
9
|
+
!!success
|
10
|
+
end
|
11
|
+
|
12
|
+
def handler_present?
|
13
|
+
return true if handler_class and handler_class.actions.include?(action.to_sym)
|
14
|
+
self.success = true #still setting to true to remove message from queue
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
def handler_class
|
19
|
+
::SyncClient.handlers[object_type]
|
20
|
+
end
|
21
|
+
|
22
|
+
def message_handler
|
23
|
+
@handler ||= handler_class.handler.new(object_attributes)
|
24
|
+
end
|
25
|
+
|
26
|
+
def error
|
27
|
+
message_handler.error if handler_class and message_handler.error
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_logging(&block)
|
31
|
+
SyncClient.logger.info("------------------------------------------")
|
32
|
+
SyncClient.logger.info("Recieved Message: #{object_type}##{action}")
|
33
|
+
yield
|
34
|
+
SyncClient.logger.info("Error Occured: #{error}") if error
|
35
|
+
SyncClient.logger.info("Processed Message: #{!!success}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
module SyncClient
|
3
|
+
module Generators
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path("../../templates", __FILE__)
|
6
|
+
desc "Creates a SyncClient initializer and copy other files to your application."
|
7
|
+
# class_option :bleh
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
template "sync_client.rb", "config/initializers/sync_client.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_executable_file
|
14
|
+
if ActiveSupport::VERSION::MAJOR >= 4
|
15
|
+
prefix = 'bin'
|
16
|
+
else
|
17
|
+
prefix = 'script'
|
18
|
+
end
|
19
|
+
template "script", "#{prefix}/sync_client"
|
20
|
+
chmod "#{prefix}/sync_client", 0755
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
SyncClient.config do |config|
|
2
|
+
|
3
|
+
config.queuel do |c|
|
4
|
+
# c.default_queue 'VenueService'
|
5
|
+
# c.credentials token: 'asdufasdf8a7sd8fa7sdf', project_id: 'project_id'
|
6
|
+
# c.engine :iron_mq
|
7
|
+
end
|
8
|
+
# config.background_task_queue SyncClient::Resque #or SyncClient::DelayedJob or write your own.
|
9
|
+
# config.queue_suffix 'suffix'
|
10
|
+
# config.logger Logger.new
|
11
|
+
# config.add_message_object_handler object_name, handler_class, actions
|
12
|
+
# config.add_message_object_handler 'StatNgin::Game', 'Resource::StatNgin::Game', [:update, :create, :destroy]
|
13
|
+
end
|
data/lib/sync_client.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'queuel'
|
3
|
+
|
4
|
+
require 'sync_client/publisher'
|
5
|
+
require 'sync_client/queue_publisher'
|
6
|
+
require 'sync_client/sync_queue'
|
7
|
+
require 'sync_client/version'
|
8
|
+
require "sync_client/engine"
|
9
|
+
require 'sync_client/configurator'
|
10
|
+
require 'sync_client/worker'
|
11
|
+
require 'sync_client/poller'
|
12
|
+
|
13
|
+
module SyncClient
|
14
|
+
|
15
|
+
def self.version_string
|
16
|
+
"SyncClient version #{SyncClient::VERSION}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.config
|
20
|
+
@config ||= Configurator.new
|
21
|
+
if block_given?
|
22
|
+
yield @config
|
23
|
+
end
|
24
|
+
@config
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.handlers
|
28
|
+
config.handlers
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.actions
|
32
|
+
Configurator::ACTIONS
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.logger
|
36
|
+
config.sync_logger
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.queue_suffix
|
40
|
+
config.suffix
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.background_task_queue
|
44
|
+
config.task_queue
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SyncClient
|
2
|
+
module Configurations
|
3
|
+
class MessageHandler
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :handler
|
6
|
+
attr_reader :actions
|
7
|
+
|
8
|
+
def initialize(name, handler, actions = [])
|
9
|
+
@name = name.to_s.singularize
|
10
|
+
@handler = handler.classify.constantize
|
11
|
+
@actions = actions
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'sync_client/configurators/message_handlers'
|
2
|
+
require 'sync_client/task_queue/delayed_job'
|
3
|
+
require 'sync_client/task_queue/resque'
|
4
|
+
require 'sync_client/task_queue/inline_task_queue'
|
5
|
+
|
6
|
+
module SyncClient
|
7
|
+
class Configurator
|
8
|
+
private
|
9
|
+
attr_writer :message_handlers
|
10
|
+
attr_writer :suffix
|
11
|
+
attr_writer :sync_logger
|
12
|
+
attr_writer :task_queue
|
13
|
+
|
14
|
+
public
|
15
|
+
attr_reader :message_handlers
|
16
|
+
attr_reader :sync_logger
|
17
|
+
attr_reader :suffix
|
18
|
+
attr_reader :task_queue
|
19
|
+
|
20
|
+
ACTIONS = [:create, :update, :destroy]
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
self.message_handlers = Configurators::MessageHandlers.new
|
24
|
+
self.sync_logger = Logger.new(STDOUT)
|
25
|
+
self.task_queue = SyncClient::InlineTaskQueue
|
26
|
+
end
|
27
|
+
|
28
|
+
def queuel
|
29
|
+
::Queuel.configure { |c| yield c }
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_message_handler(message, handler, actions)
|
33
|
+
message_handlers.add_message_handler message, handler, actions
|
34
|
+
end
|
35
|
+
|
36
|
+
def handlers
|
37
|
+
message_handlers.message_handlers
|
38
|
+
end
|
39
|
+
|
40
|
+
def queue_suffix(queue_suffix)
|
41
|
+
self.suffix = queue_suffix
|
42
|
+
end
|
43
|
+
|
44
|
+
def logger(logger)
|
45
|
+
self.sync_logger = logger
|
46
|
+
end
|
47
|
+
|
48
|
+
def background_task_queue(queue)
|
49
|
+
self.task_queue = queue
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'sync_client/configurations/message_handler'
|
2
|
+
module SyncClient
|
3
|
+
module Configurators
|
4
|
+
class MessageHandlers
|
5
|
+
attr_reader :message_handlers
|
6
|
+
def initialize
|
7
|
+
@message_handlers = {}.with_indifferent_access
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_message_handler(name, handler, actions)
|
11
|
+
message_handlers[name] ||= Configurations::MessageHandler.new(name, handler, actions)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SyncClient
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
initializer "sync_client.eager_load", after: :set_autoload_paths do |app|
|
5
|
+
paths["app/models"].each do |load_path|
|
6
|
+
matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/
|
7
|
+
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
|
8
|
+
require_dependency file.sub(matcher, '\1')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|