chasqui 1.0.0.pre.rc1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +90 -78
  4. data/chasqui.gemspec +1 -1
  5. data/lib/chasqui.rb +83 -5
  6. data/lib/chasqui/cli.rb +8 -2
  7. data/lib/chasqui/config.rb +70 -8
  8. data/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter.rb +6 -12
  9. data/lib/chasqui/subscriber.rb +1 -78
  10. data/lib/chasqui/subscription_builder.rb +135 -0
  11. data/lib/chasqui/subscription_builder/resque_subscription_builder.rb +31 -0
  12. data/lib/chasqui/subscription_builder/sidekiq_subscription_builder.rb +27 -0
  13. data/lib/chasqui/subscriptions.rb +20 -38
  14. data/lib/chasqui/version.rb +1 -1
  15. data/spec/integration/pubsub_examples.rb +1 -0
  16. data/spec/integration/setup/resque.rb +38 -1
  17. data/spec/integration/setup/sidekiq.rb +40 -1
  18. data/spec/lib/chasqui/brokers/redis_broker_spec.rb +12 -10
  19. data/spec/lib/chasqui/config_spec.rb +3 -3
  20. data/spec/lib/chasqui/{queue_adapters → queue_adapter}/redis_queue_adapter_spec.rb +6 -19
  21. data/spec/lib/chasqui/subscriber_spec.rb +4 -114
  22. data/spec/lib/chasqui/subscription_builder/resque_subscription_builder_spec.rb +20 -0
  23. data/spec/lib/chasqui/subscription_builder/sidekiq_subscription_builder_spec.rb +27 -0
  24. data/spec/lib/chasqui/subscription_builder_spec.rb +41 -0
  25. data/spec/lib/chasqui/subscriptions_spec.rb +4 -4
  26. data/spec/lib/chasqui_spec.rb +76 -12
  27. data/spec/support/chasqui_spec_helpers.rb +0 -21
  28. data/spec/support/fake_worker.rb +2 -0
  29. data/spec/support/shared_examples/subscription_builder_examples.rb +99 -0
  30. metadata +22 -16
  31. data/lib/chasqui/worker.rb +0 -81
  32. data/spec/integration/setup/subscribers.rb +0 -30
  33. data/spec/lib/chasqui/worker_spec.rb +0 -96
  34. data/spec/support/fake_subscriber.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2a3b1a24a648748e026c9938d71f77aeda97a7f
4
- data.tar.gz: cf1b2e7b5d261c31980b46edc42fcff4048374a2
3
+ metadata.gz: 529cc6f7637b86eb3f4e6f2186e17432b766b9a0
4
+ data.tar.gz: 4d0d17f7b715880207944527b13828d7272f78eb
5
5
  SHA512:
6
- metadata.gz: bedd779b43a73f7df0021bf31c19aa686093432e9488f7b1d32be64a066defc788b040596d19a662ddca6715245c36f0d2eb5a810b3ad26eaaf2d05ceceefdf1
7
- data.tar.gz: a06828b09503066ea658447bcc0af0099339be201538421e94923bf138c8d25926eae97264c99291aca5259be3dfcf53fccf72ca6acf8f580ab092322f1ae48f
6
+ metadata.gz: 2ca9133c707e182c426b59e8d94cd6bc4c95836e21ef152b55e22194cb129eeeedaccab5e1dd1aeed285d9ce36944791417c5367671c15d9940a2b6f4fe5bebd
7
+ data.tar.gz: 8aeff03699366f0c44c73d077ee868412cca52b0dc61016f517aa7ae2c4df3807ffe613888214f7155b23852e306e24f71ff82c072e5d1e9d6fdf6ac9435fc90
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  .vagrant/
16
+ .ruby-version
data/README.md CHANGED
@@ -3,138 +3,150 @@
3
3
 
4
4
  # Chasqui
5
5
 
6
- Chasqui is a simple, lightweight, persistent implementation of the
7
- publish-subscribe (pub-sub) messaging pattern for service oriented
8
- architectures.
9
-
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.
6
+ Chasqui adds persistent
7
+ [publish-subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)
8
+ (pub-sub) messaging capabilities to Sidekiq and Resque workers.
13
9
 
14
10
  ## Installation
15
11
 
16
- Add this line to your application's Gemfile:
12
+ Add this line to your application's Gemfile
17
13
 
18
14
  gem 'chasqui'
19
15
 
20
- And then execute:
16
+ then execute
21
17
 
22
18
  $ bundle
23
19
 
24
- Or install it yourself as:
20
+ or install it yourself as
25
21
 
26
22
  $ gem install chasqui
27
23
 
28
24
  ## Dependencies
29
25
 
30
- Chasqui uses [Redis](http://redis.io/) to queue events and manage
26
+ Chasqui uses [Redis](http://redis.io/) to store events and manage
31
27
  subscriptions. You can install Redis with your favorite package manager, such
32
28
  as homebrew, yum, or apt, or if you prefer, you can run `vagrant up` to run
33
- Redis in a virtual machine.
29
+ Redis in a virtual machine. If you already have Resque or Sidekiq working, then
30
+ you already have everything you need to get started with Chasqui.
34
31
 
35
32
  ## Quick Start
36
33
 
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.
43
-
44
34
  ### Start the broker
45
35
 
46
- chasqui -r redis://localhost:6379/0 -q my-app
36
+ chasqui -r redis://localhost:6379/0
47
37
 
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`.
38
+ The broker is a ruby daemon that listens for events (messages) published to
39
+ channels (topics) and forwards those events to registered subscribers. In
40
+ order to work, your broker must use the same Redis database as your
41
+ Sidekiq/Resque workers. For a list of available broker options, see `chasqui
42
+ --help`.
50
43
 
51
- ### Publish events
44
+ ### Publish an events
52
45
 
53
- Publishing events is simple.
46
+ Chasqui.publish 'order.purchased', user, order
54
47
 
55
- # file: publisher.rb
56
- require 'chasqui'
57
- Chasqui.publish 'user.sign-up', name: 'Darth Vader'
48
+ Publish an event to the `order.purchased` channel and include information
49
+ about the user and order that triggered the event. Any arguments after the
50
+ channel name must be JSON-serializable.
58
51
 
59
- Be sure to run the publisher, broker, and subscribers in separate terminal
60
- windows.
52
+ ### Define workers to handle events
61
53
 
62
- ruby publisher.rb
54
+ With Sidekiq
63
55
 
64
- ### Subscribe to events
56
+ class OrderPublishWorker
57
+ include Sidekiq::Worker
58
+ sidekiq_options queue: 'pubsub' # you can use any options sidekiq supports
65
59
 
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.
60
+ def perform(event, user, order_details)
61
+ # custom logic to handle the event
62
+ end
63
+ end
68
64
 
69
- # file: subscriber1.rb
70
- require 'chasqui'
65
+ With Resque
71
66
 
72
- class UserSignUpSubscriber
73
- include Chasqui::Subscriber
74
- subscribe channel: 'user.sign-up'
67
+ class OrderPublishWorker
68
+ @queue = 'pubsub' # choice of queue name is up to you
75
69
 
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">
70
+ def self.perform(event, user, order_details)
71
+ # custom logic to handle the event
81
72
  end
82
73
  end
83
74
 
84
- ### Running Sidekiq subscribers
75
+ The `OrderPublishWorker` is a normal Sidekiq (or Resque) worker. The first
76
+ argument to the perform method is a [Chasqui::Event](#) object, and the
77
+ remaining arguments are the same arguments you passed to `Chasqui.publish`.
78
+
79
+ ### Subscribe to events
80
+
81
+ Chasqui.subscribe do
82
+ on 'order.purchased', PurchasedOrderWorker
83
+ # ...more subscriptions
84
+ end
85
+
86
+ The above code tells Chasqui to place events published to the `order.purchased`
87
+ channel on `PurchaseOrderWorker`'s queue.
85
88
 
86
- Here is how you can run the subscriber as a sidekiq worker:
89
+ You can also use a callable object instead of a worker class to handle events.
87
90
 
88
- sidekiq -r subscriber.rb
91
+ Chasqui.subscribe queue: 'app_id:pubsub' do
92
+ on 'order.purchased', ->(event, user, order) {
93
+ logger.info event.to_json
94
+ }
95
+ end
89
96
 
90
- For more information on running Sidekiq, please refer to the
91
- [Sidekiq documentation](https://github.com/mperham/sidekiq).
97
+ ### Running Subscribers
92
98
 
93
- ### Running Resque subscribers
99
+ With Sidekiq
94
100
 
95
- To run the resque worker, you first need to create a Rakefile.
101
+ bundle exec sidekiq -q app_id:pubsub
96
102
 
97
- # Rakefile
98
- require 'resque'
99
- require 'resque/tasks'
103
+ With Resque
100
104
 
101
- task 'resque:setup' => ['chasqui:subscriber']
105
+ QUEUES=app_id:pubsub bundle exec rake resque:work
102
106
 
103
- namespace :chasqui do
104
- task :subscriber do
105
- require './subscriber.rb'
106
- end
107
+ Subscribers are normal Sidekiq or Resque workers, and can take advantage of all
108
+ available features and plugins. Please refer to the documentation for those
109
+ libraries for detailed instructions.
110
+
111
+ * [Sidekiq documentation](https://github.com/mperham/sidekiq)
112
+ * [Resque documentation](https://github.com/resque/resque)
113
+
114
+ ### Configuration
115
+
116
+ Chasqui.configure do |c|
117
+ c.redis = 'redis://my-redis.example.com:6379'
118
+ ...
107
119
  end
108
120
 
109
- Then you can run the resque worker to start processing events.
121
+ For a full list of configuration options, see the
122
+ [Chasqui::Config documentation](http://www.rubydoc.info/gems/chasqui/Chasqui/Config).
123
+
124
+ ## Unsubscribing
110
125
 
111
- rake resque:work
126
+ Chasqui.unsubscribe 'order.purchased', 'app_id:pubsub'
112
127
 
113
- For more information on running Resque workers, please refer to
114
- the [resque documentation](https://github.com/resque/resque).
128
+ If you no longer wish to handle events for a channel, you should unsubscribe
129
+ the worker so that the Chasqui broker stops placing jobs on that worker's
130
+ queue.
115
131
 
116
132
  ## Why Chasqui?
117
133
 
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.
134
+ * Persistent - events don't get lost when the broker restarts or your workers
135
+ are not running.
136
+ * Integrates with the proven Sidekiq and Resque background worker libraries.
137
+ * Reduces service coupling - publishers have no knowledge of subscribers and
138
+ subscribers have no knowledge of publishers.
124
139
 
125
140
  ## Limitations
126
141
 
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.
142
+ Chasqui requires that the publisher, broker, and all subscribers must connect
143
+ to the same Redis database. If your applications use separate Redis databases,
144
+ they will not be able to communicate with each other using Chasqui.
133
145
 
134
146
  ## Contributing
135
147
 
136
- * For new functionality, please open an issue for discussion before creating a
137
- pull request.
148
+ * For feature requests, please [open an issue](https://github.com/jbgo/chasqui/issues/new)
149
+ to discuss the proposed feature.
138
150
  * For bug fixes, you are welcome to create a pull request without first opening
139
151
  an issue.
140
152
  * Except for documentation changes, tests are required with all pull requests.
@@ -143,7 +155,7 @@ to discuss your feature proposal.
143
155
 
144
156
  ## Code of Conduct
145
157
 
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
158
+ If you are unsure whether or not your communication is appropriate for Chasqui
159
+ please consult the [Chasqui Code of Conduct](code-of-conduct.md). If you
148
160
  suspect harassment or abuse, please report it to the email address listed in
149
161
  the Code of Conduct.
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Chasqui::VERSION
9
9
  spec.authors = ["Jordan Bach"]
10
10
  spec.email = ["jordan@opensolitude.com"]
11
- spec.summary = %q{Chasqui is a simple, lightweight, persistent implementation of the publish-subscribe (pub/sub) messaging pattern for service oriented architectures.}
11
+ spec.summary = %q{Chasqui adds persistent publish-subscribe (pub-sub) messaging capabilities to Sidekiq and Resque workers.}
12
12
  spec.homepage = "https://github.com/jbgo/chasqui"
13
13
  spec.license = "MIT"
14
14
 
@@ -9,36 +9,114 @@ require "chasqui/config"
9
9
  require "chasqui/broker"
10
10
  require "chasqui/brokers/redis_broker"
11
11
  require "chasqui/queue_adapter"
12
- require "chasqui/queue_adapters/redis_queue_adapter"
12
+ require "chasqui/queue_adapter/redis_queue_adapter"
13
13
  require "chasqui/subscriber"
14
14
  require "chasqui/subscriptions"
15
- require "chasqui/worker"
15
+ require "chasqui/subscription_builder"
16
+ require "chasqui/subscription_builder/resque_subscription_builder"
17
+ require "chasqui/subscription_builder/sidekiq_subscription_builder"
16
18
 
19
+ # A persistent implementation of the publish-subscribe messaging pattern for
20
+ # Resque and Sidekiq workers.
17
21
  module Chasqui
18
22
 
19
- class ConfigurationError < StandardError; end
20
-
21
23
  class << self
22
24
  extend Forwardable
23
25
  def_delegators :config, *CONFIG_SETTINGS
24
- def_delegators :subscriptions, :register, :unregister
25
26
 
27
+ # Yields an object for configuring Chasqui.
28
+ #
29
+ # @example
30
+ # Chasqui.configure do |c|
31
+ # c.redis = 'redis://my-redis.example.com:6379'
32
+ # ...
33
+ # end
34
+ #
35
+ # @see Config See Chasqui::Config for a full list of configuration options.
36
+ #
37
+ # @yieldparam config [Config]
26
38
  def configure(&block)
27
39
  yield config
28
40
  end
29
41
 
42
+ # @visibility private
43
+ #
44
+ # Returns the Chasqui configuration object.
45
+ #
46
+ # @see Config See Chasqui::Config for a full list of configuration options.
47
+ #
48
+ # @return [Config]
30
49
  def config
31
50
  @config ||= Config.new
32
51
  end
33
52
 
53
+ # Publish an event to a channel.
54
+ #
55
+ # @param channel [String] the channel name
56
+ # @param args [Array<#to_json>] an array of JSON serializable objects that
57
+ # comprise the event's payload.
34
58
  def publish(channel, *args)
35
59
  redis.lpush inbox_queue, build_event(channel, *args).to_json
36
60
  end
37
61
 
62
+ # Subscribe workers to channels.
63
+ #
64
+ # Chasqui.subscribe(queue: 'high-priority') do
65
+ # on 'channel1', Worker1
66
+ # on 'channel2', Worker2
67
+ # on 'channel3', ->(event) { ... }, queue: 'low-priority'
68
+ # ...
69
+ # end
70
+ #
71
+ # The +.subscribe+ method creates a context for registering workers to
72
+ # receive events for specified channels. Within a subscribe block you make
73
+ # calls to the {SubscriptionBuilder#on #on} method to create subscriptions.
74
+ #
75
+ # {SubscriptionBuilder#on #on} expects a channel name as the first argument
76
+ # and either a Resque/Sidekiq worker as the second argument or a callable
77
+ # object, such as a proc, lambda, or any object that responds to +#call+.
78
+ #
79
+ # @see SubscriptionBuilder#on
80
+ #
81
+ # @param [Hash] options default options for calls to +#on+. The defaults
82
+ # will be overriden by options supplied to the +#on+ method directly.
83
+ # See {Chasqui::SubscriptionBuilder#on} for available options.
84
+ def subscribe(options={})
85
+ builder = SubscriptionBuilder.builder(subscriptions, options)
86
+ builder.instance_eval &Proc.new
87
+ end
88
+
89
+ # @visibility private
90
+ #
91
+ # Returns the registered subscriptions.
92
+ #
93
+ # @return [Subscriptions]
38
94
  def subscriptions
39
95
  @subscriptions ||= Subscriptions.new build_queue_adapter
40
96
  end
41
97
 
98
+ # Unsubscribe workers from a channel.
99
+ #
100
+ # When you unsubscribe from a channel, the broker will stop placing jobs on
101
+ # the worker queue. When only given +channel+ and +queue+ arguments,
102
+ # +#unsubscribe+ will unsubscribe all workers using that channel and queue.
103
+ # When the additional +worker+ argument is given, Chasqui will only
104
+ # unsubscribe the given worker.
105
+ #
106
+ # @param channel [String] the channel name
107
+ # @param queue [String] the queue name
108
+ # @param worker [.perform,#perform,#call] the worker class or proc for a
109
+ # a currently subscribed worker
110
+ def unsubscribe(channel, queue, worker=nil)
111
+ subscribers = if worker
112
+ [Subscriber.new(channel, queue, worker)]
113
+ else
114
+ subscriptions.find channel, queue
115
+ end
116
+
117
+ subscribers.each { |sub| subscriptions.unregister sub }
118
+ end
119
+
42
120
  private
43
121
 
44
122
  def build_event(channel, *args)
@@ -44,7 +44,7 @@ class Chasqui::CLI
44
44
  opts = {}
45
45
 
46
46
  @parser = OptionParser.new do |o|
47
- o.banner = "Usage: #{argv[0]} [options]"
47
+ o.banner = "Usage: chasqui [options]"
48
48
 
49
49
  o.on('-f', '--logfile PATH', 'log file path') do |arg|
50
50
  opts[:logfile] = arg
@@ -71,7 +71,13 @@ class Chasqui::CLI
71
71
  end
72
72
  end
73
73
 
74
- @parser.parse!(argv)
74
+ begin
75
+ @parser.parse!(argv)
76
+ rescue OptionParser::InvalidOption => ex
77
+ puts "Error: #{ex.message}\n\n#{@parser.help}"
78
+ exit 1
79
+ end
80
+
75
81
  @options = OpenStruct.new opts
76
82
  end
77
83
 
@@ -1,16 +1,10 @@
1
1
  module Chasqui
2
2
 
3
- Defaults = {
4
- default_queue: 'chasqui-subscribers',
5
- inbox_queue: 'inbox',
6
- redis_namespace: 'chasqui',
7
- broker_poll_interval: 3,
8
- queue_adapter: -> { QueueAdapters::RedisQueueAdapter }
9
- }.freeze
10
-
3
+ # Raised when configured settings prevent Chasqui from working correctly.
11
4
  class ConfigurationError < StandardError
12
5
  end
13
6
 
7
+ # @visibility private
14
8
  CONFIG_SETTINGS = [
15
9
  :broker_poll_interval,
16
10
  :channel_prefix,
@@ -22,7 +16,75 @@ module Chasqui
22
16
  :worker_backend
23
17
  ]
24
18
 
19
+ # Stores and manages all Chasqui configuration settings.
25
20
  class Config < Struct.new(*CONFIG_SETTINGS)
21
+
22
+ # @!attribute broker_poll_interval
23
+ # How long the broker daemon waits for an event before pausing to handle
24
+ # signals. Default: +3+
25
+ # @return [Fixnum] seconds
26
+
27
+ # @!attribute channel_prefix
28
+ # A string to prepend to channel names for all published events. This is
29
+ # useful for namespacing channel names to prevent collisions with other
30
+ # applications that may choose the same channel name for a different type
31
+ # of event. Default: +nil+
32
+ #
33
+ # @example
34
+ # Chasqui.configure do |c|
35
+ # c.channel_prefix = 'com.example.app1'
36
+ # end
37
+ #
38
+ # # publishes to channel: "com.example.app1.user.signup"
39
+ # Chasqui.publish 'user.signup', user
40
+ #
41
+ # @return [String]
42
+
43
+ # @!attribute default_queue
44
+ # The queue to use when a worker class does not define a queue and a
45
+ # queue option is not supplied to {Chasqui::SubscriptionBuilder#on #on}.
46
+ # Default: +"chasqui-workers"+
47
+ # @return [String]
48
+
49
+ # @!attribute inbox_queue
50
+ # The queue that stores published events until they are delivered to
51
+ # subscriber (worker) queues. Default: "chasqui-inbox"
52
+ # @return [String]
53
+
54
+ # @!attribute logger
55
+ # The logger to use for the Chasqui broker. Default: +Logger.new(STDOUT)+
56
+ # @return [Logger]
57
+
58
+ # @!attribute [rw] queue_adapter
59
+ # @api private
60
+ # The queue adapter to use for binding queues to channels.
61
+ # @return [Chasqui::QueueAdapter]
62
+
63
+ # @!attribute redis
64
+ # Customize the Redis databse connection Chasqui uses.
65
+ # Default: +"redis://localhost:6379/0"+
66
+ # @return [Redis,String,Hash]
67
+
68
+ # @!attribute worker_backend
69
+ # The type of worker that will handle events in class to
70
+ # {Chasqui.subscribe}. Can be either +resque+ or +sidekiq+. Chasqui will
71
+ # attempt to auto-detect the +worker_backend+ if either library is
72
+ # loaded. Default: +nil+
73
+ # @return [Symbol]
74
+
75
+ # @visibility private
76
+ # Default values for all configuration settings.
77
+ Defaults = {
78
+ broker_poll_interval: 3,
79
+ channel_prefix: nil,
80
+ default_queue: 'chasqui-workers',
81
+ inbox_queue: 'chasqui-inbox',
82
+ logger: STDOUT,
83
+ queue_adapter: -> { QueueAdapter::RedisQueueAdapter },
84
+ redis_namespace: 'chasqui',
85
+ worker_backend: nil
86
+ }.freeze
87
+
26
88
  def default_queue
27
89
  self[:default_queue] ||= Defaults.fetch(:default_queue)
28
90
  end