chasqui 0.9.3 → 1.0.0.pre.rc1
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 +60 -39
- data/Vagrantfile +15 -0
- data/lib/chasqui.rb +28 -55
- data/lib/chasqui/broker.rb +3 -3
- data/lib/chasqui/{multi_broker.rb → brokers/redis_broker.rb} +11 -11
- data/lib/chasqui/config.rb +26 -6
- data/lib/chasqui/queue_adapter.rb +14 -0
- data/lib/chasqui/queue_adapters/redis_queue_adapter.rb +51 -0
- data/lib/chasqui/subscriber.rb +56 -49
- data/lib/chasqui/subscriptions.rb +67 -0
- data/lib/chasqui/version.rb +1 -1
- data/lib/chasqui/worker.rb +81 -0
- data/spec/integration/pubsub_examples.rb +20 -20
- data/spec/integration/resque_spec.rb +1 -1
- data/spec/integration/setup/subscribers.rb +25 -9
- data/spec/integration/sidekiq_spec.rb +1 -1
- data/spec/lib/chasqui/{multi_broker_spec.rb → brokers/redis_broker_spec.rb} +54 -26
- data/spec/lib/chasqui/cli_spec.rb +1 -1
- data/spec/lib/chasqui/config_spec.rb +121 -0
- data/spec/lib/chasqui/fake_queue_adapter_spec.rb +5 -0
- data/spec/lib/chasqui/queue_adapters/redis_queue_adapter_spec.rb +73 -0
- data/spec/lib/chasqui/subscriber_spec.rb +95 -43
- data/spec/lib/chasqui/subscriptions_spec.rb +60 -0
- data/spec/lib/chasqui/worker_spec.rb +96 -0
- data/spec/lib/chasqui_spec.rb +32 -191
- data/spec/spec_helper.rb +1 -1
- data/spec/support/chasqui_spec_helpers.rb +28 -0
- data/spec/support/fake_queue_adapter.rb +3 -0
- data/spec/support/fake_subscriber.rb +2 -1
- data/spec/support/shared_examples/queue_adapter_examples.rb +13 -0
- metadata +25 -18
- data/lib/chasqui/subscription.rb +0 -53
- data/lib/chasqui/workers/resque_worker.rb +0 -25
- data/lib/chasqui/workers/sidekiq_worker.rb +0 -45
- data/lib/chasqui/workers/worker.rb +0 -34
- data/spec/lib/chasqui/subscription_spec.rb +0 -35
- data/spec/lib/chasqui/workers/resque_worker_spec.rb +0 -27
- data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +0 -34
@@ -0,0 +1,51 @@
|
|
1
|
+
module Chasqui
|
2
|
+
module QueueAdapters
|
3
|
+
class RedisQueueAdapter
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators :Chasqui, :redis
|
6
|
+
|
7
|
+
def bind(subscriber)
|
8
|
+
Chasqui::Worker.create subscriber
|
9
|
+
|
10
|
+
subscriber.channels.each do |channel|
|
11
|
+
redis.sadd key(channel), queue_description(subscriber)
|
12
|
+
worker_redis.sadd 'queues', subscriber.queue
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def unbind(subscriber)
|
17
|
+
subscriber.channels.each do |channel|
|
18
|
+
redis.srem key(channel), queue_description(subscriber)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def key(channel)
|
25
|
+
"subscriptions:#{channel}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def queue_description(subscriber)
|
29
|
+
queue_name = [worker_namespace, 'queue', subscriber.queue].compact.join(':')
|
30
|
+
"#{worker_backend}/Chasqui::Workers::#{subscriber.name}/#{queue_name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def worker_redis
|
34
|
+
case worker_backend
|
35
|
+
when :resque
|
36
|
+
Resque.redis
|
37
|
+
when :sidekiq
|
38
|
+
Sidekiq.redis { |r| r }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def worker_namespace
|
43
|
+
worker_redis.namespace if worker_redis.respond_to? :namespace
|
44
|
+
end
|
45
|
+
|
46
|
+
def worker_backend
|
47
|
+
Chasqui.config.worker_backend
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/chasqui/subscriber.rb
CHANGED
@@ -1,74 +1,81 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
module Chasqui
|
2
|
+
module Subscriber
|
3
|
+
attr_reader :event
|
4
4
|
|
5
|
-
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend Chasqui::Subscriber::ClassMethods
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
attr_reader :queue, :channel
|
9
|
+
def initialize(options={})
|
10
|
+
@event = options.fetch(:event)
|
10
11
|
|
11
|
-
|
12
|
-
@
|
13
|
-
@channel = channel
|
12
|
+
@logger = options[:logger]
|
13
|
+
@redis = options[:redis]
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def redis
|
17
|
+
@redis ||= Chasqui.redis
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
def logger
|
21
|
+
@logger ||= Chasqui.logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform(payload)
|
25
|
+
raise NotImplementedError
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
self.current_event = event
|
28
|
+
class << self
|
29
|
+
attr_reader :subscribers
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
def register_subscriber(klass)
|
32
|
+
@subscribers ||= Set.new
|
33
|
+
@subscribers << klass
|
34
|
+
Chasqui.register klass
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
+
)
|
39
46
|
end
|
40
|
-
end
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
end
|
48
|
+
def subscribe(args={})
|
49
|
+
queue = args.fetch :queue, Chasqui.default_queue
|
45
50
|
|
46
|
-
|
47
|
-
|
48
|
-
instance_eval &block
|
49
|
-
end
|
51
|
+
subscriber_config.channels = prefixed_channels args
|
52
|
+
subscriber_config.queue = queue
|
50
53
|
|
51
|
-
|
54
|
+
Chasqui::Subscriber.register_subscriber self
|
55
|
+
end
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
def channels
|
58
|
+
subscriber_config.channels
|
59
|
+
end
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
@self_before_instance_eval.send method, *args, &block
|
60
|
-
else
|
61
|
-
super
|
61
|
+
def queue
|
62
|
+
subscriber_config.queue
|
62
63
|
end
|
63
|
-
end
|
64
64
|
|
65
|
-
|
66
|
-
/\A#{event_name.to_s.downcase.gsub('*', '.*')}\z/
|
67
|
-
end
|
65
|
+
private
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
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
72
|
|
73
|
+
if prefix
|
74
|
+
channels.map { |c| "#{prefix}.#{c}" }
|
75
|
+
else
|
76
|
+
channels
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
73
80
|
end
|
74
81
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Chasqui
|
2
|
+
class Subscriptions
|
3
|
+
attr_reader :queue_adapter
|
4
|
+
|
5
|
+
def initialize(queue_adapter)
|
6
|
+
@subscriptions = {}
|
7
|
+
@subscribers ||= {}
|
8
|
+
@queue_adapter = queue_adapter
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(subscriber)
|
12
|
+
subscriber.channels.each do |channel|
|
13
|
+
register_one channel, subscriber
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def unregister(subscriber)
|
18
|
+
subscriber.channels.each do |channel|
|
19
|
+
unregister_one channel, subscriber
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def find(channel, queue)
|
24
|
+
@subscriptions[queue.to_s][channel.to_s].values
|
25
|
+
end
|
26
|
+
|
27
|
+
def subscribers
|
28
|
+
@subscribers.values
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscribed?(subscriber)
|
32
|
+
@subscribers.key? subscriber.object_id
|
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
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/chasqui/version.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Chasqui
|
2
|
+
module Chasqui::Workers
|
3
|
+
end
|
4
|
+
|
5
|
+
class Worker
|
6
|
+
class << self
|
7
|
+
|
8
|
+
BACKENDS = {
|
9
|
+
resque: 'ResqueWorkerFactory',
|
10
|
+
sidekiq: 'SidekiqWorkerFactory'
|
11
|
+
}
|
12
|
+
|
13
|
+
def create(subscriber)
|
14
|
+
workers[subscriber.object_id] ||= create_worker(subscriber)
|
15
|
+
end
|
16
|
+
|
17
|
+
def workers
|
18
|
+
@workers ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def check_for_worker_backend
|
24
|
+
unless BACKENDS.keys.include? Chasqui.worker_backend
|
25
|
+
msg = "Chasqui.config.worker_backend must be one of #{BACKENDS.keys}"
|
26
|
+
raise ConfigurationError.new msg
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def worker_factory
|
31
|
+
Chasqui.const_get BACKENDS[Chasqui.worker_backend]
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_worker(subscriber)
|
35
|
+
check_for_worker_backend
|
36
|
+
worker = worker_factory.create subscriber
|
37
|
+
Chasqui::Workers.const_set subscriber.name, worker
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ResqueWorkerFactory
|
44
|
+
def self.create(subscriber)
|
45
|
+
Class.new do
|
46
|
+
@queue = subscriber.queue
|
47
|
+
@subscriber = subscriber
|
48
|
+
|
49
|
+
class << self
|
50
|
+
attr_reader :subscriber
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.perform(event)
|
54
|
+
instance = @subscriber.new event: event, logger: Resque.logger
|
55
|
+
instance.perform event['payload']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SidekiqWorkerFactory
|
62
|
+
def self.create(subscriber)
|
63
|
+
Class.new do
|
64
|
+
include Sidekiq::Worker
|
65
|
+
sidekiq_options 'queue' => 'foo-queue'
|
66
|
+
|
67
|
+
@subscriber = subscriber
|
68
|
+
|
69
|
+
class << self
|
70
|
+
attr_reader :subscriber
|
71
|
+
end
|
72
|
+
|
73
|
+
def perform(event)
|
74
|
+
instance = self.class.subscriber.new event: event, logger: logger
|
75
|
+
instance.perform event['payload']
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -1,31 +1,31 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
shared_examples 'pubsub' do |
|
3
|
+
shared_examples 'pubsub' do |start_workers_method|
|
4
4
|
|
5
5
|
def events_to_publish
|
6
6
|
[
|
7
|
-
{
|
8
|
-
{
|
9
|
-
{
|
10
|
-
{
|
11
|
-
{
|
12
|
-
{
|
13
|
-
{
|
7
|
+
{ channel: 'user.signup', payload: ['Kelly'] },
|
8
|
+
{ channel: 'account.credit', payload: [1337, 'Kelly'] },
|
9
|
+
{ channel: 'account.debit', payload: [10, 'Kelly'] },
|
10
|
+
{ channel: 'user.signup', payload: ['Travis'] },
|
11
|
+
{ channel: 'account.debit', payload: [9000, 'Kelly'] },
|
12
|
+
{ channel: 'user.cancel', payload: ['Kelly'] },
|
13
|
+
{ channel: 'account.credit', payload: [42, 'Travis'] },
|
14
14
|
]
|
15
15
|
end
|
16
16
|
|
17
17
|
def expected_events
|
18
18
|
{
|
19
19
|
'app1' => [
|
20
|
-
{
|
21
|
-
{
|
20
|
+
{ channel: 'user.signup', payload: ['Kelly'] },
|
21
|
+
{ channel: 'user.signup', payload: ['Travis'] },
|
22
22
|
],
|
23
23
|
'app2' => [
|
24
|
-
{
|
25
|
-
{
|
26
|
-
{
|
27
|
-
{
|
28
|
-
{
|
24
|
+
{ channel: 'account.credit', payload: [1337, 'Kelly'] },
|
25
|
+
{ channel: 'account.debit', payload: [10, 'Kelly'] },
|
26
|
+
{ channel: 'account.debit', payload: [9000, 'Kelly'] },
|
27
|
+
{ channel: 'user.cancel', payload: ['Kelly'] },
|
28
|
+
{ channel: 'account.credit', payload: [42, 'Travis'] },
|
29
29
|
],
|
30
30
|
}
|
31
31
|
end
|
@@ -37,7 +37,7 @@ shared_examples 'pubsub' do |namespace, start_workers_method|
|
|
37
37
|
@redis.keys('*').each { |k| @redis.del k }
|
38
38
|
|
39
39
|
Chasqui.configure do |c|
|
40
|
-
c.
|
40
|
+
c.channel_prefix = 'integration'
|
41
41
|
c.redis = @redis_url
|
42
42
|
end
|
43
43
|
|
@@ -56,17 +56,17 @@ shared_examples 'pubsub' do |namespace, start_workers_method|
|
|
56
56
|
|
57
57
|
it 'works' do
|
58
58
|
events_to_publish.each do |event|
|
59
|
-
Chasqui.publish event[:
|
59
|
+
Chasqui.publish event[:channel], *event[:payload]
|
60
60
|
end
|
61
61
|
|
62
62
|
begin
|
63
63
|
Timeout::timeout(10) do
|
64
64
|
expected_events.each do |subscriber_queue, events|
|
65
65
|
events.each do |expected|
|
66
|
-
_, payload =
|
66
|
+
_, payload = Chasqui.redis.blpop "#{subscriber_queue}:event_log"
|
67
67
|
actual = JSON.parse payload
|
68
|
-
expect(actual['
|
69
|
-
expect(actual['
|
68
|
+
expect(actual['channel']).to eq(expected[:channel])
|
69
|
+
expect(actual['payload']).to eq(expected[:payload])
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'integration/pubsub_examples'
|
3
3
|
|
4
4
|
describe "resque integration", integration: true do
|
5
|
-
include_examples 'pubsub', :
|
5
|
+
include_examples 'pubsub', :start_resque_workers
|
6
6
|
|
7
7
|
def start_resque_workers
|
8
8
|
@subscriber_queues.each do |queue|
|
@@ -1,14 +1,30 @@
|
|
1
|
-
def log_event subscriber,
|
2
|
-
|
3
|
-
payload = { event: event['event'], data: args }.to_json
|
4
|
-
subscriber.redis.rpush "#{subscriber.queue}:event_log", payload
|
1
|
+
def log_event subscriber, payload
|
2
|
+
Chasqui.redis.rpush "#{subscriber.class.queue}:event_log", subscriber.event.to_json
|
5
3
|
end
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
class UserSignupSubscriber
|
6
|
+
include Chasqui::Subscriber
|
7
|
+
subscribe channel: 'user.signup', queue: 'app1'
|
8
|
+
|
9
|
+
def perform(payload)
|
10
|
+
log_event self, payload
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
class AccountSubscriber
|
15
|
+
include Chasqui::Subscriber
|
16
|
+
subscribe channel: ['account.credit', 'account.debit'], queue: 'app2'
|
17
|
+
|
18
|
+
def perform(payload)
|
19
|
+
log_event self, payload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class UserCancelSubscriber
|
24
|
+
include Chasqui::Subscriber
|
25
|
+
subscribe channel: 'user.cancel', queue: 'app2'
|
26
|
+
|
27
|
+
def perform(payload)
|
28
|
+
log_event self, payload
|
29
|
+
end
|
14
30
|
end
|