chasqui 1.0.0.pre.rc1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +90 -78
- data/chasqui.gemspec +1 -1
- data/lib/chasqui.rb +83 -5
- data/lib/chasqui/cli.rb +8 -2
- data/lib/chasqui/config.rb +70 -8
- data/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter.rb +6 -12
- data/lib/chasqui/subscriber.rb +1 -78
- data/lib/chasqui/subscription_builder.rb +135 -0
- data/lib/chasqui/subscription_builder/resque_subscription_builder.rb +31 -0
- data/lib/chasqui/subscription_builder/sidekiq_subscription_builder.rb +27 -0
- data/lib/chasqui/subscriptions.rb +20 -38
- data/lib/chasqui/version.rb +1 -1
- data/spec/integration/pubsub_examples.rb +1 -0
- data/spec/integration/setup/resque.rb +38 -1
- data/spec/integration/setup/sidekiq.rb +40 -1
- data/spec/lib/chasqui/brokers/redis_broker_spec.rb +12 -10
- data/spec/lib/chasqui/config_spec.rb +3 -3
- data/spec/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter_spec.rb +6 -19
- data/spec/lib/chasqui/subscriber_spec.rb +4 -114
- data/spec/lib/chasqui/subscription_builder/resque_subscription_builder_spec.rb +20 -0
- data/spec/lib/chasqui/subscription_builder/sidekiq_subscription_builder_spec.rb +27 -0
- data/spec/lib/chasqui/subscription_builder_spec.rb +41 -0
- data/spec/lib/chasqui/subscriptions_spec.rb +4 -4
- data/spec/lib/chasqui_spec.rb +76 -12
- data/spec/support/chasqui_spec_helpers.rb +0 -21
- data/spec/support/fake_worker.rb +2 -0
- data/spec/support/shared_examples/subscription_builder_examples.rb +99 -0
- metadata +22 -16
- data/lib/chasqui/worker.rb +0 -81
- data/spec/integration/setup/subscribers.rb +0 -30
- data/spec/lib/chasqui/worker_spec.rb +0 -96
- data/spec/support/fake_subscriber.rb +0 -13
@@ -1,22 +1,16 @@
|
|
1
1
|
module Chasqui
|
2
|
-
module
|
2
|
+
module QueueAdapter
|
3
3
|
class RedisQueueAdapter
|
4
4
|
extend Forwardable
|
5
5
|
def_delegators :Chasqui, :redis
|
6
6
|
|
7
7
|
def bind(subscriber)
|
8
|
-
|
9
|
-
|
10
|
-
subscriber.channels.each do |channel|
|
11
|
-
redis.sadd key(channel), queue_description(subscriber)
|
12
|
-
worker_redis.sadd 'queues', subscriber.queue
|
13
|
-
end
|
8
|
+
redis.sadd key(subscriber.channel), queue_description(subscriber)
|
9
|
+
worker_redis.sadd 'queues', subscriber.queue
|
14
10
|
end
|
15
11
|
|
16
12
|
def unbind(subscriber)
|
17
|
-
subscriber.
|
18
|
-
redis.srem key(channel), queue_description(subscriber)
|
19
|
-
end
|
13
|
+
redis.srem key(subscriber.channel), queue_description(subscriber)
|
20
14
|
end
|
21
15
|
|
22
16
|
private
|
@@ -27,7 +21,7 @@ module Chasqui
|
|
27
21
|
|
28
22
|
def queue_description(subscriber)
|
29
23
|
queue_name = [worker_namespace, 'queue', subscriber.queue].compact.join(':')
|
30
|
-
"#{worker_backend}
|
24
|
+
"#{worker_backend}/#{subscriber.worker.name}/#{queue_name}"
|
31
25
|
end
|
32
26
|
|
33
27
|
def worker_redis
|
@@ -35,7 +29,7 @@ module Chasqui
|
|
35
29
|
when :resque
|
36
30
|
Resque.redis
|
37
31
|
when :sidekiq
|
38
|
-
Sidekiq.redis { |r| r }
|
32
|
+
::Sidekiq.redis { |r| r }
|
39
33
|
end
|
40
34
|
end
|
41
35
|
|
data/lib/chasqui/subscriber.rb
CHANGED
@@ -1,81 +1,4 @@
|
|
1
1
|
module Chasqui
|
2
|
-
|
3
|
-
attr_reader :event
|
4
|
-
|
5
|
-
def self.included(klass)
|
6
|
-
klass.extend Chasqui::Subscriber::ClassMethods
|
7
|
-
end
|
8
|
-
|
9
|
-
def initialize(options={})
|
10
|
-
@event = options.fetch(:event)
|
11
|
-
|
12
|
-
@logger = options[:logger]
|
13
|
-
@redis = options[:redis]
|
14
|
-
end
|
15
|
-
|
16
|
-
def redis
|
17
|
-
@redis ||= Chasqui.redis
|
18
|
-
end
|
19
|
-
|
20
|
-
def logger
|
21
|
-
@logger ||= Chasqui.logger
|
22
|
-
end
|
23
|
-
|
24
|
-
def perform(payload)
|
25
|
-
raise NotImplementedError
|
26
|
-
end
|
27
|
-
|
28
|
-
class << self
|
29
|
-
attr_reader :subscribers
|
30
|
-
|
31
|
-
def register_subscriber(klass)
|
32
|
-
@subscribers ||= Set.new
|
33
|
-
@subscribers << klass
|
34
|
-
Chasqui.register klass
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
module ClassMethods
|
39
|
-
SubscriberConfig = Struct.new :channels, :queue
|
40
|
-
|
41
|
-
def subscriber_config
|
42
|
-
@subscriber_config ||= SubscriberConfig.new(
|
43
|
-
Chasqui.channel_prefix,
|
44
|
-
Chasqui.default_queue
|
45
|
-
)
|
46
|
-
end
|
47
|
-
|
48
|
-
def subscribe(args={})
|
49
|
-
queue = args.fetch :queue, Chasqui.default_queue
|
50
|
-
|
51
|
-
subscriber_config.channels = prefixed_channels args
|
52
|
-
subscriber_config.queue = queue
|
53
|
-
|
54
|
-
Chasqui::Subscriber.register_subscriber self
|
55
|
-
end
|
56
|
-
|
57
|
-
def channels
|
58
|
-
subscriber_config.channels
|
59
|
-
end
|
60
|
-
|
61
|
-
def queue
|
62
|
-
subscriber_config.queue
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def prefixed_channels(args)
|
68
|
-
channel = args.fetch :channel, Chasqui.channel_prefix
|
69
|
-
prefix = args.fetch :prefix, Chasqui.channel_prefix
|
70
|
-
|
71
|
-
channels = channel.respond_to?(:each) ? channel : [channel]
|
72
|
-
|
73
|
-
if prefix
|
74
|
-
channels.map { |c| "#{prefix}.#{c}" }
|
75
|
-
else
|
76
|
-
channels
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
2
|
+
class Subscriber < Struct.new(:channel, :queue, :worker)
|
80
3
|
end
|
81
4
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Chasqui
|
2
|
+
# A namespace for defining dynamically generated worker classes for callable
|
3
|
+
# objects.
|
4
|
+
module Workers
|
5
|
+
end
|
6
|
+
|
7
|
+
# Provides the context used in {Chasqui.subscribe} to bind workers to
|
8
|
+
# channels via the {#on} method.
|
9
|
+
class SubscriptionBuilder
|
10
|
+
# The collection of currently registered subscriptions.
|
11
|
+
# @return [Chasqui::Subscriptions]
|
12
|
+
attr_reader :subscriptions
|
13
|
+
|
14
|
+
# Default options for calls to {#on}.
|
15
|
+
# @return [Hash]
|
16
|
+
attr_reader :default_options
|
17
|
+
|
18
|
+
# @visibility private
|
19
|
+
# You should not instantiate this class directly. Instead use the
|
20
|
+
# {Chasqui.subscribe} method to create a subscription builder context.
|
21
|
+
def initialize(subscriptions, options={})
|
22
|
+
@subscriptions = subscriptions
|
23
|
+
@default_options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
# Bind a worker to a channel.
|
27
|
+
#
|
28
|
+
# The broker will place jobs on the worker's queue for each event published
|
29
|
+
# to the given channel.
|
30
|
+
#
|
31
|
+
# @param channel [String] the channel name
|
32
|
+
# @param worker_or_proc [#perform,.perform,#call] a Sidekiq Worker
|
33
|
+
# class, Resque worker class, or proc to handle events published to
|
34
|
+
# channel. If a proc is used as a worker, +#on+ will define a new worker
|
35
|
+
# class that delegates +#perform+ to +proc#call+.
|
36
|
+
# @param options [Hash]
|
37
|
+
#
|
38
|
+
# @option options [String] :queue the worker queue.
|
39
|
+
# When given, this option will override the queue defined by the worker
|
40
|
+
# class. This option is recommended when using a proc as a worker.
|
41
|
+
# @option options [String] :queue_prefix prefix for queue.
|
42
|
+
# When supplied, the value of this option is prepended to the queue name.
|
43
|
+
# Use this option to namespace your queues in order to prevent collisions
|
44
|
+
# with queues from other applicaitons sharing the same Redis database.
|
45
|
+
def on(channel, worker_or_proc, options={})
|
46
|
+
options = default_options.merge(options)
|
47
|
+
worker = build_worker(channel, worker_or_proc, options)
|
48
|
+
|
49
|
+
queue = full_queue_name(worker, options)
|
50
|
+
set_queue_name(worker, queue)
|
51
|
+
|
52
|
+
subscriptions.register Chasqui::Subscriber.new(channel, queue, worker)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @visibility private
|
56
|
+
# Instantiate a new subscription builder suitable for the configured
|
57
|
+
# {Chasqui.worker_backend worker_backend}.
|
58
|
+
def self.builder(subscriptions, options={})
|
59
|
+
builder_for_backend.new subscriptions, options
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def get_queue_name(worker)
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_queue_name(worker, queue)
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_worker_class(channel, callable, options)
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
def redefine_perform_method(worker, &block)
|
77
|
+
raise NotImplementedError
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.builder_for_backend
|
83
|
+
case Chasqui.worker_backend
|
84
|
+
when :resque
|
85
|
+
ResqueSubscriptionBuilder
|
86
|
+
when :sidekiq
|
87
|
+
SidekiqSubscriptionBuilder
|
88
|
+
else
|
89
|
+
msg = <<-ERR.gsub(/^ {8}/, '')
|
90
|
+
No worker backend configured.
|
91
|
+
|
92
|
+
# To configure a worker backend:
|
93
|
+
Chasqui.config do |c|
|
94
|
+
c.worker_backend = :resque # or :sidekiq
|
95
|
+
end
|
96
|
+
ERR
|
97
|
+
raise Chasqui::ConfigurationError.new msg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def full_queue_name(worker, options={})
|
102
|
+
queue = options.fetch :queue, get_queue_name(worker)
|
103
|
+
prefix = options[:queue_prefix]
|
104
|
+
|
105
|
+
prefix ? "#{prefix}:#{queue}" : queue
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_worker(channel, worker_or_proc, options={})
|
109
|
+
worker = worker_or_proc
|
110
|
+
|
111
|
+
if worker.respond_to? :call
|
112
|
+
worker = define_worker_class(channel, worker_or_proc, options)
|
113
|
+
Chasqui::Workers.const_set worker_class_name(channel), worker
|
114
|
+
end
|
115
|
+
|
116
|
+
redefine_perform_method(worker) do |klass|
|
117
|
+
klass.send :define_method, :perform_with_event do |event|
|
118
|
+
perform_without_event event, *event['payload']
|
119
|
+
end
|
120
|
+
|
121
|
+
klass.send :alias_method, :perform_without_event, :perform
|
122
|
+
klass.send :alias_method, :perform, :perform_with_event
|
123
|
+
end
|
124
|
+
|
125
|
+
worker
|
126
|
+
end
|
127
|
+
|
128
|
+
def worker_class_name(channel)
|
129
|
+
segments = channel.split(/[^\w]/).map(&:downcase)
|
130
|
+
name = segments.each { |w| w[0] = w[0].upcase }.join
|
131
|
+
|
132
|
+
"#{name}Worker".freeze
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Chasqui
|
2
|
+
class ResqueSubscriptionBuilder < SubscriptionBuilder
|
3
|
+
protected
|
4
|
+
|
5
|
+
def get_queue_name(worker)
|
6
|
+
worker.instance_variable_get :@queue
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_queue_name(worker, queue)
|
10
|
+
worker.instance_variable_set :@queue, queue
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_worker_class(channel, callable, options)
|
14
|
+
Class.new do
|
15
|
+
@queue = Chasqui.default_queue
|
16
|
+
define_singleton_method :perform, callable
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def redefine_perform_method(worker)
|
21
|
+
return if worker.respond_to?(:perform_with_event)
|
22
|
+
|
23
|
+
worker.instance_eval do
|
24
|
+
class << self
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Chasqui
|
2
|
+
class SidekiqSubscriptionBuilder < SubscriptionBuilder
|
3
|
+
protected
|
4
|
+
|
5
|
+
def get_queue_name(worker)
|
6
|
+
worker.sidekiq_options['queue']
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_queue_name(worker, queue)
|
10
|
+
worker.sidekiq_options queue: queue
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_worker_class(channel, callable, options)
|
14
|
+
Class.new do
|
15
|
+
include ::Sidekiq::Worker
|
16
|
+
sidekiq_options queue: Chasqui.default_queue
|
17
|
+
define_method :perform, callable
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def redefine_perform_method(worker, &block)
|
22
|
+
return if worker.instance_methods.include?(:perform_with_event)
|
23
|
+
|
24
|
+
yield worker
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -9,15 +9,29 @@ module Chasqui
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def register(subscriber)
|
12
|
-
subscriber.
|
13
|
-
|
14
|
-
|
12
|
+
queue = subscriber.queue.to_s
|
13
|
+
channel = subscriber.channel.to_s
|
14
|
+
|
15
|
+
@subscriptions[queue] ||= {}
|
16
|
+
@subscriptions[queue][channel] ||= {}
|
17
|
+
@subscriptions[queue][channel][subscriber.worker] = subscriber
|
18
|
+
|
19
|
+
@subscribers[subscriber.worker] = subscriber
|
20
|
+
|
21
|
+
queue_adapter.bind subscriber
|
15
22
|
end
|
16
23
|
|
17
24
|
def unregister(subscriber)
|
18
|
-
subscriber.
|
19
|
-
|
25
|
+
queue = subscriber.queue.to_s
|
26
|
+
channel = subscriber.channel.to_s
|
27
|
+
|
28
|
+
queue_adapter.unbind subscriber
|
29
|
+
|
30
|
+
if @subscriptions[queue] && @subscriptions[queue][channel]
|
31
|
+
@subscriptions[queue][channel].delete subscriber.worker
|
20
32
|
end
|
33
|
+
|
34
|
+
@subscribers.delete subscriber.worker
|
21
35
|
end
|
22
36
|
|
23
37
|
def find(channel, queue)
|
@@ -29,39 +43,7 @@ module Chasqui
|
|
29
43
|
end
|
30
44
|
|
31
45
|
def subscribed?(subscriber)
|
32
|
-
@subscribers.key? subscriber.
|
33
|
-
end
|
34
|
-
|
35
|
-
def autoregister!
|
36
|
-
Subscriber.subscribers.each { |s| register s }
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def register_one(channel, subscriber)
|
42
|
-
q = subscriber.queue.to_s
|
43
|
-
c = channel.to_s
|
44
|
-
|
45
|
-
@subscriptions[q] ||= {}
|
46
|
-
@subscriptions[q][c] ||= {}
|
47
|
-
@subscriptions[q][c][subscriber.object_id] = subscriber
|
48
|
-
|
49
|
-
@subscribers[subscriber.object_id] = subscriber
|
50
|
-
|
51
|
-
queue_adapter.bind subscriber
|
52
|
-
end
|
53
|
-
|
54
|
-
def unregister_one(channel, subscriber)
|
55
|
-
q = subscriber.queue.to_s
|
56
|
-
c = channel.to_s
|
57
|
-
|
58
|
-
queue_adapter.unbind subscriber
|
59
|
-
|
60
|
-
if @subscriptions[q] && @subscriptions[q][c]
|
61
|
-
@subscriptions[q][c].delete subscriber.object_id
|
62
|
-
end
|
63
|
-
|
64
|
-
@subscribers.delete subscriber.object_id
|
46
|
+
@subscribers.key? subscriber.worker
|
65
47
|
end
|
66
48
|
end
|
67
49
|
end
|
data/lib/chasqui/version.rb
CHANGED
@@ -67,6 +67,7 @@ shared_examples 'pubsub' do |start_workers_method|
|
|
67
67
|
actual = JSON.parse payload
|
68
68
|
expect(actual['channel']).to eq(expected[:channel])
|
69
69
|
expect(actual['payload']).to eq(expected[:payload])
|
70
|
+
expect(actual['payload']).to eq(actual['worker_args'])
|
70
71
|
end
|
71
72
|
end
|
72
73
|
end
|
@@ -4,4 +4,41 @@ require 'resque'
|
|
4
4
|
Resque.redis = ENV['REDIS_URL'] if ENV['REDIS_URL']
|
5
5
|
Resque.redis.namespace = :resque
|
6
6
|
|
7
|
-
|
7
|
+
def log_event(worker, event, *args)
|
8
|
+
queue = worker.instance_variable_get(:@queue)
|
9
|
+
event['worker_args'] = args
|
10
|
+
event['class'] = worker.name
|
11
|
+
|
12
|
+
Chasqui.redis.rpush "#{queue}:event_log", event.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
class UserSignupWorker
|
16
|
+
@queue = 'app1'
|
17
|
+
|
18
|
+
def self.perform(event, *args)
|
19
|
+
log_event self, event, *args
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TransactionWorker
|
24
|
+
@queue = 'app2'
|
25
|
+
|
26
|
+
def self.perform(event, *args)
|
27
|
+
log_event self, event, *args
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class UserCancelWorker
|
32
|
+
@queue = 'app2'
|
33
|
+
|
34
|
+
def self.perform(event, *args)
|
35
|
+
log_event self, event, *args
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Chasqui.subscribe do
|
40
|
+
on 'user.signup', UserSignupWorker
|
41
|
+
on 'user.cancel', UserCancelWorker
|
42
|
+
on 'account.credit', TransactionWorker
|
43
|
+
on 'account.debit', TransactionWorker
|
44
|
+
end
|