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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7b83716114be3ede50a3559cb742d776cdecd1f
4
- data.tar.gz: df453dfaa501a592ddc6b3c650fcae09a9343123
3
+ metadata.gz: e2a3b1a24a648748e026c9938d71f77aeda97a7f
4
+ data.tar.gz: cf1b2e7b5d261c31980b46edc42fcff4048374a2
5
5
  SHA512:
6
- metadata.gz: 91b367f3cd65f4bceffd73ebbdd6ef9d9af344741d797ebb8909d7b372c8246c99cee930b3b0a3cf09f316cbd48e84f11f82cfcd16f2251acdaa63c089fba7be
7
- data.tar.gz: 420990d6feaa68f81d4db1ec6076a0c2b8fa2e5bd137dcf41a222d89734fa8e560e5d2e8cbb07a455d22a827e020c43c744f886e78a6f17e3f60c9e255f73f55
6
+ metadata.gz: bedd779b43a73f7df0021bf31c19aa686093432e9488f7b1d32be64a066defc788b040596d19a662ddca6715245c36f0d2eb5a810b3ad26eaaf2d05ceceefdf1
7
+ data.tar.gz: a06828b09503066ea658447bcc0af0099339be201538421e94923bf138c8d25926eae97264c99291aca5259be3dfcf53fccf72ca6acf8f580ab092322f1ae48f
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+ .vagrant/
data/README.md CHANGED
@@ -3,11 +3,13 @@
3
3
 
4
4
  # Chasqui
5
5
 
6
- Chasqui is a simple, lightweight, persistent implementation of the publish-subscribe (pub/sub)
7
- messaging pattern for service oriented architectures.
6
+ Chasqui is a simple, lightweight, persistent implementation of the
7
+ publish-subscribe (pub-sub) messaging pattern for service oriented
8
+ architectures.
8
9
 
9
- Chasqui delivers messages to subscribers in a Resque-compatible format. If you are already
10
- using Resque and/or Sidekiq, Chasqui will make a wonderful companion to your architecture.
10
+ Chasqui delivers messages to subscribers in a Resque-compatible format. If you
11
+ are already using Resque and/or Sidekiq, Chasqui will make a wonderful
12
+ companion to your architecture.
11
13
 
12
14
  ## Installation
13
15
 
@@ -25,22 +27,26 @@ Or install it yourself as:
25
27
 
26
28
  ## Dependencies
27
29
 
28
- Chasqui uses Redis to queue events and manage subscriptions. You can install
29
- redis with your favorite package manager, such as homebrew, yum, or apt, or if
30
- you prefer, you can run `vagrant up` to run Redis in a virtual machine.
30
+ Chasqui uses [Redis](http://redis.io/) to queue events and manage
31
+ subscriptions. You can install Redis with your favorite package manager, such
32
+ as homebrew, yum, or apt, or if you prefer, you can run `vagrant up` to run
33
+ Redis in a virtual machine.
31
34
 
32
35
  ## Quick Start
33
36
 
34
- Chasqui consistents of two components - a client and a broker. The broker's
35
- responsibility is to forward published events to registered subscribers. The
36
- client can both publish events and register subscribers.
37
+ Chasqui consists of two components - a client and a broker. Clients can both
38
+ publish to a channel and subscribe to events on one or more channels. The
39
+ broker transforms incoming events into Resque (or Sidekiq) jobs and places them
40
+ on one or more queues according to the currently registered subscribers. Under
41
+ the hood, a subscriber is simply a Sidekiq/Resque worker that processes jobs
42
+ placed on a queue.
37
43
 
38
44
  ### Start the broker
39
45
 
40
46
  chasqui -r redis://localhost:6379/0 -q my-app
41
47
 
42
- Your broker must use the same redis connection as your sidekiq (or resque)
43
- workers. For a list of available broker options, run `chasqui --help`.
48
+ Your broker must use the same Redis connection as your Sidekiq/Resque workers.
49
+ For a list of available broker options, see `chasqui --help`.
44
50
 
45
51
  ### Publish events
46
52
 
@@ -48,8 +54,7 @@ Publishing events is simple.
48
54
 
49
55
  # file: publisher.rb
50
56
  require 'chasqui'
51
- Chasqui.publish 'user.sign-up', 'Luke Skywalker'
52
- Chasqui.publish 'user.cancel', 'Dart Vader', 'invalid use of the force'
57
+ Chasqui.publish 'user.sign-up', name: 'Darth Vader'
53
58
 
54
59
  Be sure to run the publisher, broker, and subscribers in separate terminal
55
60
  windows.
@@ -58,34 +63,35 @@ windows.
58
63
 
59
64
  ### Subscribe to events
60
65
 
61
- Subscribing to events is also simple. The following example tells chasqui to
62
- forward events to the subscriber's 'my-app' queue, for which chasqui will
63
- generate the appropriate worker class. Within the subscriber block, you define
64
- one or more `on` blocks in which you place your application logic for handling
65
- an event.
66
+ Subscribing to events is also simple. In the following example, we create a
67
+ subscriber to handle events published to the `user.sign-up` channel.
66
68
 
67
69
  # file: subscriber1.rb
68
70
  require 'chasqui'
69
71
 
70
- Chasqui.subscribe queue: 'my-app' do
72
+ class UserSignUpSubscriber
73
+ include Chasqui::Subscriber
74
+ subscribe channel: 'user.sign-up'
71
75
 
72
- on 'user.sign-up' do |user_id|
73
- # do something when the user signs up
76
+ def perform(payload)
77
+ # Do something when the user signs up.
78
+ #
79
+ # User.create(name: payload[:user])
80
+ # => #<User:0X00fe346 @name="Darth Vader">
74
81
  end
75
-
76
- on 'user.cancel' do |user_id, reason|
77
- # do something else when user cancels
78
- end
79
-
80
82
  end
81
83
 
82
- You can have as many subscribers as you like, but __each subscriber must have
83
- its own unique queue name__.
84
+ ### Running Sidekiq subscribers
84
85
 
85
86
  Here is how you can run the subscriber as a sidekiq worker:
86
87
 
87
88
  sidekiq -r subscriber.rb
88
89
 
90
+ For more information on running Sidekiq, please refer to the
91
+ [Sidekiq documentation](https://github.com/mperham/sidekiq).
92
+
93
+ ### Running Resque subscribers
94
+
89
95
  To run the resque worker, you first need to create a Rakefile.
90
96
 
91
97
  # Rakefile
@@ -104,25 +110,40 @@ Then you can run the resque worker to start processing events.
104
110
 
105
111
  rake resque:work
106
112
 
113
+ For more information on running Resque workers, please refer to
114
+ the [resque documentation](https://github.com/resque/resque).
115
+
107
116
  ## Why Chasqui?
108
117
 
109
- * Reduces coupling between applications
110
- * Integrates with the popular sidekiq and resque background worker libraries
111
- * Queues events for registered subscribers even if a subscriber is unavailable
118
+ * Reduces coupling between applications.
119
+ * Simple to learn and use.
120
+ * Integrates with the popular Sidekiq and Resque background worker libraries.
121
+ * Queues events for registered subscribers even if a subscriber is unavailable.
122
+ * Subscribers can benefit from Sidekiq's built-in retry functionality for
123
+ failed jobs.
112
124
 
113
125
  ## Limitations
114
126
 
115
- In order for chasqui to work properly, the publisher, broker, and all
116
- subscribers must connect to the same Redis database.
127
+ In order for Chasqui to work properly, the publisher, broker, and all
128
+ subscribers must connect to the same Redis database. Chasqui is intentionally
129
+ simple and may not have all of the features you would expect from a messaging
130
+ system. If Chasqui is missing a feature that you think it should have, please
131
+ consider [opening a GitHub issue](https://github.com/jbgo/chasqui/issues/new)
132
+ to discuss your feature proposal.
117
133
 
118
134
  ## Contributing
119
135
 
120
- * For new functionality, please open an issue for discussion before creating a pull request.
121
- * For bug fixes, you are welcome to create a pull request without first opening an issue.
136
+ * For new functionality, please open an issue for discussion before creating a
137
+ pull request.
138
+ * For bug fixes, you are welcome to create a pull request without first opening
139
+ an issue.
122
140
  * Except for documentation changes, tests are required with all pull requests.
123
- * Please be polite and respectful when discussing the project with maintainers and your fellow contributors.
141
+ * Please be polite and respectful when discussing the project with maintainers
142
+ and your fellow contributors.
124
143
 
125
144
  ## Code of Conduct
126
145
 
127
- If you are unsure whether or not your communication may be inappropriate, please consult the [Chasqui Code of Conduct](code-of-conduct.md).
128
- If you even suspect harassment or abuse, please report it to the email address listed in the Code of Conduct.
146
+ If you are unsure whether or not your communication may be inappropriate,
147
+ please consult the [Chasqui Code of Conduct](code-of-conduct.md). If you even
148
+ suspect harassment or abuse, please report it to the email address listed in
149
+ the Code of Conduct.
data/Vagrantfile ADDED
@@ -0,0 +1,15 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant.configure(2) do |config|
5
+ config.vm.box = 'hashicorp/precise64'
6
+
7
+ config.vm.network 'forwarded_port', guest: 6379, host: 6379
8
+
9
+ config.vm.provision "shell", inline: <<-SHELL
10
+ sudo apt-get update
11
+ sudo apt-get install -y redis-server
12
+ sudo sed -ie 's/bind 127.0.0.1/bind 0.0.0.0/' /etc/redis/redis.conf
13
+ sudo service redis-server restart
14
+ SHELL
15
+ end
data/lib/chasqui.rb CHANGED
@@ -7,88 +7,61 @@ require 'redis-namespace'
7
7
  require "chasqui/version"
8
8
  require "chasqui/config"
9
9
  require "chasqui/broker"
10
- require "chasqui/multi_broker"
10
+ require "chasqui/brokers/redis_broker"
11
+ require "chasqui/queue_adapter"
12
+ require "chasqui/queue_adapters/redis_queue_adapter"
11
13
  require "chasqui/subscriber"
12
- require "chasqui/subscription"
13
- require "chasqui/workers/worker"
14
- require "chasqui/workers/resque_worker"
15
- require "chasqui/workers/sidekiq_worker"
14
+ require "chasqui/subscriptions"
15
+ require "chasqui/worker"
16
16
 
17
17
  module Chasqui
18
+
19
+ class ConfigurationError < StandardError; end
20
+
18
21
  class << self
19
22
  extend Forwardable
20
- def_delegators :config, :redis, :channel, :inbox, :inbox_queue, :logger
23
+ def_delegators :config, *CONFIG_SETTINGS
24
+ def_delegators :subscriptions, :register, :unregister
21
25
 
22
26
  def configure(&block)
23
- @config ||= Config.new
24
- yield @config
27
+ yield config
25
28
  end
26
29
 
27
30
  def config
28
31
  @config ||= Config.new
29
32
  end
30
33
 
31
- def publish(event, *args)
32
- redis.lpush inbox_queue, build_payload(event, *args).to_json
33
- end
34
-
35
- def subscribe(options={}, &block)
36
- queue = options.fetch :queue
37
- channel = options.fetch :channel, config.channel
38
-
39
- create_subscription(queue, channel).tap do |subscription|
40
- subscription.subscriber.evaluate(&block) if block_given?
41
- redis.sadd subscription_key(channel), subscription.subscription_id
42
- end
43
- end
44
-
45
- def unsubscribe(channel, options={}, &block)
46
- queue = options.fetch :queue
47
- subscription = subscriptions[queue.to_s]
48
-
49
- if subscription
50
- redis.srem subscription_key(channel), subscription.subscription_id
51
- subscription.subscription_id
52
- end
53
- end
54
-
55
- def subscription(queue)
56
- subscriptions[queue.to_s]
34
+ def publish(channel, *args)
35
+ redis.lpush inbox_queue, build_event(channel, *args).to_json
57
36
  end
58
37
 
59
- def subscriber_class_name(queue)
60
- queue_name_constant = queue.split(':').last.gsub(/[^\w]/, '_')
61
- "Subscriber__#{queue_name_constant}".to_sym
62
- end
63
-
64
- def subscription_key(channel)
65
- "subscriptions:#{channel}"
38
+ def subscriptions
39
+ @subscriptions ||= Subscriptions.new build_queue_adapter
66
40
  end
67
41
 
68
42
  private
69
43
 
70
- def create_subscription(queue, channel)
71
- subscriptions[queue.to_s] ||= Subscription.new queue, channel
72
- end
73
-
74
- def subscriptions
75
- @subscriptions ||= {}
76
- end
77
-
78
- def build_payload(event, *args)
44
+ def build_event(channel, *args)
79
45
  opts = extract_job_options!(*args)
80
46
 
81
- payload = { event: event, channel: channel, data: args }
82
- payload[:retry] = opts[:retry] || opts['retry'] if opts
47
+ payload = { channel: channel, payload: args }
48
+ payload[:retry] = fetch_option(opts, :retry, true) || false
83
49
  payload[:created_at] = Time.now.to_f.to_s
84
50
 
85
51
  payload
86
52
  end
87
53
 
88
54
  def extract_job_options!(*args)
89
- if args.last.kind_of?(Hash)
90
- args.last.delete(:job_options)
91
- end
55
+ opts = args.last.delete(:job_options) if args.last.kind_of?(Hash)
56
+ opts ||= {}
57
+ end
58
+
59
+ def fetch_option(opts, key, default=nil)
60
+ opts.fetch key.to_sym, opts.fetch(key.to_s, default)
61
+ end
62
+
63
+ def build_queue_adapter
64
+ queue_adapter.new
92
65
  end
93
66
  end
94
67
  end
@@ -4,7 +4,7 @@ class Chasqui::Broker
4
4
  attr_reader :config, :redis, :redis_namespace
5
5
 
6
6
  extend Forwardable
7
- def_delegators :@config, :inbox, :logger
7
+ def_delegators :@config, :inbox_queue, :logger
8
8
 
9
9
  ShutdownSignals = %w(INT QUIT ABRT TERM).freeze
10
10
 
@@ -24,7 +24,7 @@ class Chasqui::Broker
24
24
  install_signal_handlers
25
25
 
26
26
  logger.info "broker started with pid #{Process.pid}"
27
- logger.info "configured to fetch events from #{inbox} on #{redis.inspect}"
27
+ logger.info "configured to fetch events from #{inbox_queue} on #{redis.inspect}"
28
28
 
29
29
  until_shutdown_requested { forward_event }
30
30
  end
@@ -35,7 +35,7 @@ class Chasqui::Broker
35
35
 
36
36
  class << self
37
37
  def start
38
- Chasqui::MultiBroker.new.start
38
+ Chasqui::RedisBroker.new.start
39
39
  end
40
40
  end
41
41
 
@@ -1,6 +1,6 @@
1
1
  require 'securerandom'
2
2
 
3
- class Chasqui::MultiBroker < Chasqui::Broker
3
+ class Chasqui::RedisBroker < Chasqui::Broker
4
4
 
5
5
  def forward_event
6
6
  event = receive or return
@@ -17,22 +17,22 @@ class Chasqui::MultiBroker < Chasqui::Broker
17
17
  end
18
18
 
19
19
  def in_progress_queue
20
- with_namespace inbox, 'in_progress'
20
+ with_namespace inbox_queue, 'in_progress'
21
21
  end
22
22
 
23
- def inbox_queue
24
- with_namespace inbox
23
+ def namespaced_inbox_queue
24
+ with_namespace inbox_queue
25
25
  end
26
26
 
27
- def build_job(queue, event)
27
+ def build_job(queue, job_class, event)
28
28
  {
29
- class: "Chasqui::#{Chasqui.subscriber_class_name(queue)}",
29
+ class: job_class,
30
30
  args: [event],
31
31
  queue: 'my-queue',
32
32
  jid: SecureRandom.hex(12),
33
33
  created_at: (event['created_at'] || Time.now).to_f,
34
34
  enqueued_at: Time.now.to_f,
35
- retry: !!event['retry']
35
+ retry: event['retry']
36
36
  }.to_json
37
37
  end
38
38
 
@@ -57,7 +57,7 @@ class Chasqui::MultiBroker < Chasqui::Broker
57
57
  end
58
58
 
59
59
  def dequeue
60
- redis.brpoplpush(inbox_queue, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
60
+ redis.brpoplpush(namespaced_inbox_queue, in_progress_queue, timeout: config.broker_poll_interval).tap do |event|
61
61
  if event.nil?
62
62
  logger.debug "reached timeout for broker poll interval: #{config.broker_poll_interval} seconds"
63
63
  end
@@ -65,8 +65,8 @@ class Chasqui::MultiBroker < Chasqui::Broker
65
65
  end
66
66
 
67
67
  def dispatch(event, subscription_id)
68
- backend, queue = subscription_id.split('/', 2)
69
- job = build_job queue, event
68
+ backend, job_class, queue = subscription_id.split('/', 3)
69
+ job = build_job queue, job_class, event
70
70
 
71
71
  logger.debug "dispatching event queue=#{queue} backend=#{backend} job=#{job}"
72
72
 
@@ -79,7 +79,7 @@ class Chasqui::MultiBroker < Chasqui::Broker
79
79
  end
80
80
 
81
81
  def subscriptions_for(event)
82
- subscription_key = Chasqui.subscription_key event['channel']
82
+ subscription_key = "subscriptions:#{event['channel']}"
83
83
  redis.smembers with_namespace(subscription_key)
84
84
  end
85
85
 
@@ -1,10 +1,11 @@
1
1
  module Chasqui
2
2
 
3
3
  Defaults = {
4
+ default_queue: 'chasqui-subscribers',
4
5
  inbox_queue: 'inbox',
5
6
  redis_namespace: 'chasqui',
6
- publish_channel: '__default',
7
- broker_poll_interval: 3
7
+ broker_poll_interval: 3,
8
+ queue_adapter: -> { QueueAdapters::RedisQueueAdapter }
8
9
  }.freeze
9
10
 
10
11
  class ConfigurationError < StandardError
@@ -12,22 +13,31 @@ module Chasqui
12
13
 
13
14
  CONFIG_SETTINGS = [
14
15
  :broker_poll_interval,
15
- :channel,
16
+ :channel_prefix,
17
+ :default_queue,
16
18
  :inbox_queue,
17
19
  :logger,
20
+ :queue_adapter,
18
21
  :redis,
19
22
  :worker_backend
20
23
  ]
21
24
 
22
25
  class Config < Struct.new(*CONFIG_SETTINGS)
23
- def channel
24
- self[:channel] ||= Defaults.fetch(:publish_channel)
26
+ def default_queue
27
+ self[:default_queue] ||= Defaults.fetch(:default_queue)
25
28
  end
26
29
 
27
30
  def inbox_queue
28
31
  self[:inbox_queue] ||= Defaults.fetch(:inbox_queue)
29
32
  end
30
- alias inbox inbox_queue
33
+
34
+ def queue_adapter
35
+ self[:queue_adapter] ||= Defaults.fetch(:queue_adapter).call
36
+ end
37
+
38
+ def worker_backend
39
+ self[:worker_backend] ||= choose_worker_backend
40
+ end
31
41
 
32
42
  def redis
33
43
  unless self[:redis]
@@ -74,5 +84,15 @@ module Chasqui
74
84
  def broker_poll_interval
75
85
  self[:broker_poll_interval] ||= Defaults.fetch(:broker_poll_interval)
76
86
  end
87
+
88
+ private
89
+
90
+ def choose_worker_backend
91
+ if Object.const_defined? :Sidekiq
92
+ :sidekiq
93
+ elsif Object.const_defined? :Resque
94
+ :resque
95
+ end
96
+ end
77
97
  end
78
98
  end