sync_client 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|