chasqui 0.9.3 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +60 -39
  4. data/Vagrantfile +15 -0
  5. data/lib/chasqui.rb +28 -55
  6. data/lib/chasqui/broker.rb +3 -3
  7. data/lib/chasqui/{multi_broker.rb → brokers/redis_broker.rb} +11 -11
  8. data/lib/chasqui/config.rb +26 -6
  9. data/lib/chasqui/queue_adapter.rb +14 -0
  10. data/lib/chasqui/queue_adapters/redis_queue_adapter.rb +51 -0
  11. data/lib/chasqui/subscriber.rb +56 -49
  12. data/lib/chasqui/subscriptions.rb +67 -0
  13. data/lib/chasqui/version.rb +1 -1
  14. data/lib/chasqui/worker.rb +81 -0
  15. data/spec/integration/pubsub_examples.rb +20 -20
  16. data/spec/integration/resque_spec.rb +1 -1
  17. data/spec/integration/setup/subscribers.rb +25 -9
  18. data/spec/integration/sidekiq_spec.rb +1 -1
  19. data/spec/lib/chasqui/{multi_broker_spec.rb → brokers/redis_broker_spec.rb} +54 -26
  20. data/spec/lib/chasqui/cli_spec.rb +1 -1
  21. data/spec/lib/chasqui/config_spec.rb +121 -0
  22. data/spec/lib/chasqui/fake_queue_adapter_spec.rb +5 -0
  23. data/spec/lib/chasqui/queue_adapters/redis_queue_adapter_spec.rb +73 -0
  24. data/spec/lib/chasqui/subscriber_spec.rb +95 -43
  25. data/spec/lib/chasqui/subscriptions_spec.rb +60 -0
  26. data/spec/lib/chasqui/worker_spec.rb +96 -0
  27. data/spec/lib/chasqui_spec.rb +32 -191
  28. data/spec/spec_helper.rb +1 -1
  29. data/spec/support/chasqui_spec_helpers.rb +28 -0
  30. data/spec/support/fake_queue_adapter.rb +3 -0
  31. data/spec/support/fake_subscriber.rb +2 -1
  32. data/spec/support/shared_examples/queue_adapter_examples.rb +13 -0
  33. metadata +25 -18
  34. data/lib/chasqui/subscription.rb +0 -53
  35. data/lib/chasqui/workers/resque_worker.rb +0 -25
  36. data/lib/chasqui/workers/sidekiq_worker.rb +0 -45
  37. data/lib/chasqui/workers/worker.rb +0 -34
  38. data/spec/lib/chasqui/subscription_spec.rb +0 -35
  39. data/spec/lib/chasqui/workers/resque_worker_spec.rb +0 -27
  40. data/spec/lib/chasqui/workers/sidekiq_worker_spec.rb +0 -34
@@ -0,0 +1,14 @@
1
+ module Chasqui
2
+ module QueueAdapter
3
+ def bind(subscriber)
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def unbind(subscriber)
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+
12
+ module QueueAdapters
13
+ end
14
+ end
@@ -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
@@ -1,74 +1,81 @@
1
- require 'set'
2
-
3
1
  module Chasqui
2
+ module Subscriber
3
+ attr_reader :event
4
4
 
5
- HandlerAlreadyRegistered = Class.new StandardError
5
+ def self.included(klass)
6
+ klass.extend Chasqui::Subscriber::ClassMethods
7
+ end
6
8
 
7
- class Subscriber
8
- attr_accessor :redis, :current_event
9
- attr_reader :queue, :channel
9
+ def initialize(options={})
10
+ @event = options.fetch(:event)
10
11
 
11
- def initialize(queue, channel)
12
- @queue = queue
13
- @channel = channel
12
+ @logger = options[:logger]
13
+ @redis = options[:redis]
14
14
  end
15
15
 
16
- def on(event_name, &block)
17
- pattern = pattern_for_event event_name
16
+ def redis
17
+ @redis ||= Chasqui.redis
18
+ end
18
19
 
19
- if handler_patterns.include? pattern
20
- raise HandlerAlreadyRegistered.new "handler already registered for event: #{event_name}"
21
- else
22
- handler_patterns << pattern
23
- define_handler_method pattern, &block
24
- end
20
+ def logger
21
+ @logger ||= Chasqui.logger
22
+ end
23
+
24
+ def perform(payload)
25
+ raise NotImplementedError
25
26
  end
26
27
 
27
- def perform(redis_for_worker, event)
28
- self.redis = redis_for_worker
29
- self.current_event = event
28
+ class << self
29
+ attr_reader :subscribers
30
30
 
31
- matching_handler_patterns_for(event['event']).each do |pattern|
32
- call_handler pattern, *event['data']
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
- def matching_handler_patterns_for(event_name)
37
- handler_patterns.select do |pattern|
38
- pattern =~ event_name
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
- def call_handler(pattern, *args)
43
- send "handler__#{pattern.to_s}", *args
44
- end
48
+ def subscribe(args={})
49
+ queue = args.fetch :queue, Chasqui.default_queue
45
50
 
46
- def evaluate(&block)
47
- @self_before_instance_eval = eval "self", block.binding
48
- instance_eval &block
49
- end
51
+ subscriber_config.channels = prefixed_channels args
52
+ subscriber_config.queue = queue
50
53
 
51
- private
54
+ Chasqui::Subscriber.register_subscriber self
55
+ end
52
56
 
53
- def handler_patterns
54
- @handler_patterns ||= Set.new
55
- end
57
+ def channels
58
+ subscriber_config.channels
59
+ end
56
60
 
57
- def method_missing(method, *args, &block)
58
- if @self_before_instance_eval
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
- def pattern_for_event(event_name)
66
- /\A#{event_name.to_s.downcase.gsub('*', '.*')}\z/
67
- end
65
+ private
68
66
 
69
- def define_handler_method(pattern, &block)
70
- self.class.send :define_method, "handler__#{pattern.to_s}", &block
71
- end
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
@@ -1,3 +1,3 @@
1
1
  module Chasqui
2
- VERSION = "0.9.3"
2
+ VERSION = '1.0.0-rc1'
3
3
  end
@@ -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 |namespace, start_workers_method|
3
+ shared_examples 'pubsub' do |start_workers_method|
4
4
 
5
5
  def events_to_publish
6
6
  [
7
- { event: 'user.signup', data: ['Kelly'] },
8
- { event: 'account.credit', data: [1337, 'Kelly'] },
9
- { event: 'account.debit', data: [10, 'Kelly'] },
10
- { event: 'user.signup', data: ['Travis'] },
11
- { event: 'account.debit', data: [9000, 'Kelly'] },
12
- { event: 'user.cancel', data: ['Kelly'] },
13
- { event: 'account.credit', data: [42, 'Travis'] },
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
- { event: 'user.signup', data: ['Kelly'] },
21
- { event: 'user.signup', data: ['Travis'] },
20
+ { channel: 'user.signup', payload: ['Kelly'] },
21
+ { channel: 'user.signup', payload: ['Travis'] },
22
22
  ],
23
23
  'app2' => [
24
- { event: 'account.credit', data: [1337, 'Kelly'] },
25
- { event: 'account.debit', data: [10, 'Kelly'] },
26
- { event: 'account.debit', data: [9000, 'Kelly'] },
27
- { event: 'user.cancel', data: ['Kelly'] },
28
- { event: 'account.credit', data: [42, 'Travis'] },
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.channel = 'integration'
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[:event], *event[:data]
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 = @redis.blpop "#{namespace}:#{subscriber_queue}:event_log"
66
+ _, payload = Chasqui.redis.blpop "#{subscriber_queue}:event_log"
67
67
  actual = JSON.parse payload
68
- expect(actual['event']).to eq(expected[:event])
69
- expect(actual['data']).to eq(expected[:data])
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', :resque, :start_resque_workers
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, args
2
- event = subscriber.current_event
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
- Chasqui.subscribe channel: 'integration', queue: 'app1' do
8
- on('user.signup') { |*args| log_event self, args }
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
- Chasqui.subscribe channel: 'integration', queue: 'app2' do
12
- on('account.*') { |*args| log_event self, args }
13
- on('user.cancel') { |*args| log_event self, args }
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