serf 0.2.0.alpha1 → 0.3.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.
data/Gemfile CHANGED
@@ -3,9 +3,12 @@ source 'http://rubygems.org'
3
3
  # Example:
4
4
  # gem 'activesupport', '>= 2.3.5'
5
5
 
6
+ # Requirements for both clients and servers.
6
7
  gem 'activemodel', '~> 3.1.3'
7
8
  gem 'activesupport', '~> 3.1.3'
8
- gem 'eventmachine', '~> 0.12.10'
9
+ gem 'i18n', '~> 0.6.0' # For ActiveSupport
10
+ # Used by Serf::Messages::*
11
+ gem 'uuidtools', '~> 2.1.2'
9
12
 
10
13
  # Add dependencies to develop your gem here.
11
14
  # Include everything needed to run rake, tests, features, etc.
@@ -20,4 +23,7 @@ group :development, :test do
20
23
  #gem 'log4r', '~> 1.1.9'
21
24
  gem 'msgpack', '~> 0.4.6'
22
25
  #gem 'multi_json', '~> 1.0.3'
26
+
27
+ # For Server Side of things
28
+ gem 'eventmachine', '~> 0.12.10'
23
29
  end
data/Gemfile.lock CHANGED
@@ -28,6 +28,7 @@ GEM
28
28
  rspec-expectations (2.3.0)
29
29
  diff-lcs (~> 1.1.2)
30
30
  rspec-mocks (2.3.0)
31
+ uuidtools (2.1.2)
31
32
  yard (0.6.8)
32
33
 
33
34
  PLATFORMS
@@ -38,8 +39,10 @@ DEPENDENCIES
38
39
  activesupport (~> 3.1.3)
39
40
  bundler (~> 1.0.0)
40
41
  eventmachine (~> 0.12.10)
42
+ i18n (~> 0.6.0)
41
43
  jeweler (~> 1.6.4)
42
44
  msgpack (~> 0.4.6)
43
45
  rcov
44
46
  rspec (~> 2.3.0)
47
+ uuidtools (~> 2.1.2)
45
48
  yard (~> 0.6.0)
data/README.md CHANGED
@@ -5,23 +5,190 @@ Serf is a library that scaffolds distributed systems that are architected using
5
5
  Event-Driven Service Oriented Architecture design in combinations with
6
6
  the Command Query Responsibility Separation pattern.
7
7
 
8
- Middleware
8
+ Philosophy
9
9
  ==========
10
10
 
11
- Airbrake
12
- --------
11
+ The underlying idea of Serf is to define a standard Ruby code interface
12
+ that standalone gems can follow to create a business verticals services.
13
+ These gems are intended to run as standalone processes in a distributed
14
+ manner.
13
15
 
14
- You can use Airbrake's middleware to catch exceptions.
16
+ Fundamentally, a service creates an abstraction of:
17
+ 1. Messages
18
+ 2. Handlers
15
19
 
16
- # example.su
17
- require 'rack'
18
- require 'airbrake'
20
+ Messages are the representation of data, documents, etc that are
21
+ marshalled from client to service, which represents the business
22
+ level interaction of the service.
23
+
24
+ Messages may be commands that a service must handle.
25
+ Messages may be events that a service emits.
26
+ Messages may be documents that represent state or business data.
27
+
28
+ Handlers are the code that is executed over Messages.
29
+ Handlers may process Command Messages.
30
+ Handlers may process observed Events that 3rd party services emit.
31
+
32
+ Services SHOULD declare a manifest to map messages to handlers, which
33
+ allows Serf to route messages and to determine blocking or non-blocking modes.
34
+
35
+ Serf App and Channels
36
+ =====================
37
+
38
+ A Serf App is a Rack-like application that accepts an ENV hash as input.
39
+ This ENV hash is simply the hash representation of a message to be processed.
40
+ The Serf App, as configured by registered manifests, will:
41
+ 1. validate the message
42
+ 2. route the message to the proper handler
43
+ 3. run the handler in blocking or non-blocking mode.
44
+ 4. publish the handler's results to results or error channels.
45
+ a. These channels are normally message queuing channels.
46
+ b. We only require the channel instance to respond to the 'publish'
47
+ method and accept a message (or message hash) as the argument.
48
+ 5. return the handler's results to the caller if it is blocking mode.
49
+ a. In non-blocking mode, an MessageAcceptedEvent is returned instead
50
+ because the message will be run by the EventMachine deferred threadpool
51
+ by default.
52
+
53
+ Not implemented as yet:
54
+ 1. Create a message queue listener or web endpoint to accept messages
55
+ and run them through the built Serf App.
56
+ 2. Message Queue channels to route responses to pub/sub locations.
57
+
58
+ Service Libraries
59
+ =================
60
+
61
+ 1. Service Libraries SHOULD implement message classes that include the
62
+ ::Serf::Message helper module.
63
+ a. Barring that, they should conform to the idea that messages may be
64
+ hashes that define at least one attribute: 'kind'.
65
+ 2. Serialization of the messages SHOULD BE Json or MessagePack (I hate XML).
66
+ 3. Service Libraries SHOULD implement handler classes that include the
67
+ ::Serf::Handler helper module.
68
+ 4. Handler methods MUST receive a message as an options hash, symbolized keys.
69
+ 5. Handler methods MUST return zero or more messages.
70
+ 6. Handler methods SHOULD handle catch their business logic exceptions and
71
+ return them as specialized messages that can be forwarded down error channels.
72
+ Uncaught exceptions that are then caught by Serf are published as
73
+ generic Serf::CaughtExceptionEvents, and are harder to deal with.
74
+
75
+ Example
76
+ =======
77
+
78
+ # Require our libraries
79
+ require 'log4r'
80
+ require 'serf/handler'
81
+ require 'serf/message'
82
+ require 'serf/builder'
83
+
84
+ # create a simple logger for this example
85
+ logger = Log4r::Logger.new 'my_logger'
86
+ logger.outputters = Log4r::FileOutputter.new(
87
+ 'fileOutputter',
88
+ filename: 'log.txt')
89
+
90
+ # my_lib/my_handler.rb
91
+ class MyHandler
92
+ include Serf::Handler
93
+
94
+ receives(
95
+ 'my_message',
96
+ with: :submit_my_message)
97
+
98
+ def initialize(options={})
99
+ @logger = options[:logger]
100
+ end
101
+
102
+ def submit_my_message(message={})
103
+ @logger.info "In Submit Match Result: #{message.inspect.to_s}"
104
+ # Do work Here...
105
+ # And return other messages as results of work, or nil for nothing.
106
+ return nil
107
+ end
108
+
109
+ end
110
+
111
+ # my_lib/my_message.rb
112
+ class MyMessage
113
+ include Serf::Message
114
+
115
+ attr_accessor :data
116
+
117
+ validates_presence_of :data
118
+
119
+ def initialize(options={})
120
+ @hi = options[:data] || 'some data here'
121
+ end
19
122
 
20
- Airbrake.configure do |config|
21
- config.api_key = 'my_api_key'
22
123
  end
23
124
 
24
- use Airbrake::Rack
125
+ # my_lib/manifest.rb
126
+ MANIFEST = {
127
+ 'my_message' => {
128
+ # Optional Definition of an implementation class.
129
+ #message_class => 'other_name_space/my_other_message_implementation',
130
+ # Declares which handler
131
+ handler: 'my_handler',
132
+ # Default is true to process in background.
133
+ async: true
134
+ }
135
+ }
136
+
137
+ # Helper class for this example to receive result or error messages
138
+ # and pipe it into our logger.
139
+ class MyChannel
140
+ def initialize(name, logger)
141
+ @name = name
142
+ @logger = logger
143
+ end
144
+ def publish(message)
145
+ @logger.info "#{@name} #{message}"
146
+ #@logger.info message
147
+ end
148
+ end
149
+
150
+ # Create a new builder for this serf app.
151
+ builder = Serf::Builder.new do
152
+ # Registers different service libary manifests.
153
+ register MANIFEST
154
+ # Can define arguments to pass to the 'my_handler' initialize method.
155
+ config(
156
+ 'my_handler',
157
+ logger: logger)
158
+ # Create result and error channels for the handler result messages.
159
+ error_channel MyChannel.new('errorchannel: ', logger)
160
+ results_channel MyChannel.new('resultschannel: ', logger)
161
+ # We pass in a logger to our Serf code: Serfer and Runners.
162
+ logger logger
163
+ # Optionally define a not found handler...
164
+ # Defaults to raising an ArgumentError, 'Not Found'.
165
+ #not_found lambda {|x| puts x}
166
+ end
167
+ app = builder.to_app
168
+
169
+ # Start event machine loop.
170
+ EM.run do
171
+ # On the next tick
172
+ EM.next_tick do
173
+ logger.info "Start Tick #{Thread.current.object_id}"
174
+ # This will submit a 'my_message' message (as a hash) to the Serf App.
175
+ results = app.call('kind' => 'my_message')
176
+ # Because we declared the 'my_message' kind to be handled async, we
177
+ # should get a MessageAcceptedEvent as the results.
178
+ logger.info "In Tick Results: #{results.inspect}"
179
+ begin
180
+ # Here, we're going to submit a message that we don't handle.
181
+ # By default, an exception will be raised.
182
+ app.call('kind' => 'unhandled_message_kind')
183
+ rescue => e
184
+ puts "Caught in Tick: #{e.inspect}"
185
+ end
186
+ logger.info "End Tick #{Thread.current.object_id}"
187
+ end
188
+ EM.add_timer 2 do
189
+ EM.stop
190
+ end
191
+ end
25
192
 
26
193
 
27
194
  Contributing to serf
data/lib/serf/builder.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'serf/serfer'
2
+ require 'serf/runners/direct_runner'
3
+ require 'serf/runners/em_runner'
4
+ require 'serf/util/null_object'
2
5
 
3
6
  module Serf
4
7
 
@@ -25,13 +28,27 @@ module Serf
25
28
  return builder
26
29
  end
27
30
 
28
- def initialize(options={}, &block)
29
- @use = []
31
+ def initialize(app=nil, &block)
32
+ # Configuration about the routes and apps to run.
30
33
  @manifest = {}
31
34
  @config = {}
32
- @not_found = options[:not_found]
33
- @serfer_class = options.fetch(:serfer_class) { ::Serf::Serfer }
34
- @serfer_options = options[:serfer_options] || {}
35
+ @not_found = app
36
+
37
+ # Implementing classes of our app
38
+ # NOTE: runner_class and async_runner_class are only used if actual
39
+ # runner instances are omitted in the configuration.
40
+ @serfer_class = ::Serf::Serfer
41
+ @serfer_options = {}
42
+ @runner_class = ::Serf::Runners::DirectRunner
43
+ @async_runner_class = ::Serf::Runners::EmRunner
44
+
45
+ # Utility and messaging channels for our Runners
46
+ # NOTE: these are only used if the builder needs to instantiage runners.
47
+ @results_channel = ::Serf::Util::NullObject.new
48
+ @error_channel = ::Serf::Util::NullObject.new
49
+ @logger = ::Serf::Util::NullObject.new
50
+
51
+ # configure based on a given block.
35
52
  instance_eval(&block) if block_given?
36
53
  end
37
54
 
@@ -39,10 +56,6 @@ module Serf
39
56
  self.new(default_app, &block).to_app
40
57
  end
41
58
 
42
- def use(middleware, *args, &block)
43
- @use << proc { |app| middleware.new(app, *args, &block) }
44
- end
45
-
46
59
  def register(manifest)
47
60
  @manifest.merge! manifest
48
61
  end
@@ -55,18 +68,42 @@ module Serf
55
68
  @not_found = app
56
69
  end
57
70
 
58
- def to_app
59
- app = generate_routes
60
- return @use.reverse.inject(app) { |a,e| e[a] }
71
+ def serfer_class(serfer_class)
72
+ @serfer_class = serfer_class
73
+ end
74
+
75
+ def serfer_class(serfer_options)
76
+ @serfer_options = serfer_options
77
+ end
78
+
79
+ def runner(runner)
80
+ @runner = runner
81
+ end
82
+
83
+ def async_runner(runner)
84
+ @async_runner = runner
85
+ end
86
+
87
+ def results_channel(results_channel)
88
+ @results_channel = results_channel
89
+ end
90
+
91
+ def error_channel(error_channel)
92
+ @error_channel = error_channel
61
93
  end
62
94
 
63
- private
95
+ def logger(logger)
96
+ @logger = logger
97
+ end
64
98
 
65
- def generate_routes
99
+ def to_app
100
+ # Our async and sync messages & handlers.
66
101
  kinds = {}
67
102
  handlers = {}
103
+ async_kinds = {}
68
104
  async_handlers = {}
69
105
 
106
+ # Iterate our manifests to build out handlers and message classes
70
107
  @manifest.each do |kind, options|
71
108
  # Instantiate our handler with any possible configuration.
72
109
  handler_str = options.fetch(:handler)
@@ -74,28 +111,62 @@ module Serf
74
111
  args, block = @config.fetch(handler_str) { [[], nil] }
75
112
  handler = handler_class.new *args, &block
76
113
 
77
- # Then put it into the proper map of handlers for either
78
- # synchronous or asynchronous processing.
79
- async = options.fetch(:async) { true }
80
- (async ? async_handlers : handlers)[kind] = handler
81
-
82
114
  # Get the implementing message serialization class.
83
115
  # For a given message kind, we may have a different (or nil)
84
116
  # implementing class. If nil, then we're not going to try to
85
117
  # create a message class to validate before passing to handler.
86
118
  message_class = options.fetch(:message_class) { kind }
87
- kinds[kind] = message_class && message_class.camelize.constantize
119
+ message_class = message_class && message_class.camelize.constantize
120
+
121
+ # Put handlers and kinds into the proper map of handlers for either
122
+ # synchronous or asynchronous processing.
123
+ async = options.fetch(:async) { true }
124
+ if async
125
+ async_kinds[kind] = message_class if message_class
126
+ async_handlers[kind] = handler
127
+ else
128
+ kinds[kind] = message_class if message_class
129
+ handlers[kind] = handler
130
+ end
88
131
  end
89
132
 
90
- # We create the serfer class to handle all the messages.
91
- return @serfer_class.new(
92
- @serfer_options.merge(
93
- kinds: kinds,
94
- handlers: handlers,
95
- async_handlers: async_handlers,
96
- not_found: @not_found))
97
- end
133
+ # Get or make our runner
134
+ runner = @runner || @runner_class.new(
135
+ results_channel: @results_channel,
136
+ error_channel: @error_channel,
137
+ logger: @logger)
138
+
139
+ # By default, we go to the not_found app.
140
+ app = @not_found
141
+
142
+ # If we have synchronous handlers, insert before not_found app.
143
+ if handlers.size > 0
144
+ # create the serfer class to run synchronous handlers
145
+ app = @serfer_class.new(
146
+ @serfer_options.merge(
147
+ kinds: kinds,
148
+ handlers: handlers,
149
+ runner: runner,
150
+ not_found: app))
151
+ end
98
152
 
153
+ # If we have async handlers, insert before current app stack.
154
+ if async_handlers.size > 0
155
+ # Get or make our async wrapper
156
+ async_runner = @async_runner || @async_runner_class.new(
157
+ runner: runner,
158
+ logger: @logger)
159
+ # create the serfer class to run async handlers
160
+ app = @serfer_class.new(
161
+ @serfer_options.merge(
162
+ kinds: async_kinds,
163
+ handlers: async_handlers,
164
+ runner: async_runner,
165
+ not_found: app))
166
+ end
167
+
168
+ return app
169
+ end
99
170
  end
100
171
 
101
172
  end
data/lib/serf/error.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Serf
2
+
3
+ ##
4
+ # Module used to tag caught exceptions in this library's code to
5
+ # let clients (of the library) know that said exceptions came from
6
+ # within this code.
7
+ #
8
+ module Error
9
+ end
10
+
11
+ end
data/lib/serf/handler.rb CHANGED
@@ -4,6 +4,8 @@ require 'active_support/core_ext/hash/keys'
4
4
  require 'active_support/core_ext/object/blank'
5
5
  require 'active_support/ordered_options'
6
6
 
7
+ require 'serf/error'
8
+
7
9
  module Serf
8
10
 
9
11
  module Handler
@@ -37,14 +39,17 @@ module Serf
37
39
  #
38
40
  def call(env={})
39
41
  # Just to stringify the environment keys
40
- env = env.dup.stringify_keys
42
+ env = env.symbolize_keys
41
43
  # Make sure a kind was set, and that we can handle it.
42
- message_kind = env['kind']
44
+ message_kind = env[:kind]
43
45
  raise ArgumentError, 'No "kind" in call env' if message_kind.blank?
44
46
  method = self.class.serf_actions[message_kind]
45
47
  raise ArgumentError, "#{message_kind} not found" if method.blank?
46
48
  # Now execute the method with the environment parameters
47
49
  self.send method, env
50
+ rescue => e
51
+ e.extend ::Serf::Error
52
+ raise e
48
53
  end
49
54
  end
50
55
 
data/lib/serf/message.rb CHANGED
@@ -19,13 +19,14 @@ module Serf
19
19
  included do
20
20
  class_attribute :kind
21
21
  send 'kind=', self.to_s.tableize.singularize
22
+ self.include_root_in_json = false
22
23
  end
23
24
 
24
25
  module InstanceMethods
25
26
 
26
27
  def attributes
27
28
  {
28
- 'kind' => kind
29
+ :kind => kind
29
30
  }
30
31
  end
31
32
 
@@ -39,6 +40,14 @@ module Serf
39
40
 
40
41
  end
41
42
 
43
+ module ClassMethods
44
+
45
+ def parse(*args)
46
+ self.new *args
47
+ end
48
+
49
+ end
50
+
42
51
  end
43
52
 
44
53
  end
@@ -0,0 +1,46 @@
1
+ require 'serf/message'
2
+ require 'serf/util/uuidable'
3
+
4
+ module Serf
5
+ module Messages
6
+
7
+ ##
8
+ # An event message to signal that the serf code caught an
9
+ # exception during the processing of some message, which is
10
+ # represented by the context field.
11
+ #
12
+ # Instances of this class are norminally published to an
13
+ # error channel for out of band processing/notification.
14
+ #
15
+ class CaughtExceptionEvent
16
+ include ::Serf::Message
17
+ include ::Serf::Util::Uuidable
18
+
19
+ attr_accessor :context
20
+ attr_accessor :error_message
21
+ attr_accessor :error_backtrace
22
+
23
+ def initialize(options={})
24
+ @context = options[:context]
25
+ @error_message = options[:error_message]
26
+ @error_backtrace = options[:error_backtrace]
27
+ @uuid = options[:uuid]
28
+ end
29
+
30
+ def attributes
31
+ {
32
+ 'context' => @context,
33
+ 'error_message' => @error_message,
34
+ 'error_backtrace' => @error_backtrace,
35
+ 'uuid' => uuid
36
+ }.merge!(super)
37
+ end
38
+
39
+ def to_s
40
+ attributes.to_s
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ require 'serf/message'
2
+ require 'serf/util/uuidable'
3
+
4
+ module Serf
5
+ module Messages
6
+
7
+ ##
8
+ # This event signals that the serf process has accepted a message
9
+ # for processing at a later time. These events are normally sent
10
+ # in response to calling clients, which is a separate signal
11
+ # than any errors given.
12
+ #
13
+ class MessageAcceptedEvent
14
+ include ::Serf::Message
15
+ include ::Serf::Util::Uuidable
16
+
17
+ attr_accessor :message
18
+
19
+ def initialize(options={})
20
+ @message = options[:message]
21
+ @uuid = options[:uuid]
22
+ end
23
+
24
+ def attributes
25
+ {
26
+ 'message' => @message,
27
+ 'uuid' => uuid
28
+ }.merge!(super)
29
+ end
30
+
31
+ def to_s
32
+ attributes.to_s
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,74 @@
1
+ require 'serf/messages/caught_exception_event'
2
+ require 'serf/util/null_object'
3
+
4
+ module Serf
5
+ module Runners
6
+
7
+ ##
8
+ # Direct runner drives the execution of a handler for given messages.
9
+ # This class deals with error handling and publishing handler results
10
+ # to proper error or results channels.
11
+ #
12
+ class DirectRunner
13
+
14
+ def initialize(options={})
15
+ # Mandatory, we want both results and error channels.
16
+ @results_channel = options.fetch(:results_channel)
17
+ @error_channel = options.fetch(:error_channel)
18
+
19
+ # For caught exceptions, we're going to publish an error event
20
+ # over our error channel. This defines our error event message class.
21
+ @error_event_class = options.fetch(:error_event_class) {
22
+ ::Serf::Messages::CaughtExceptionEvent
23
+ }
24
+
25
+ # Our default logger
26
+ @logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
27
+ end
28
+
29
+ def run(handler, params)
30
+ with_error_handling(params) do
31
+ results = handler.call params
32
+ publish_results results
33
+ return results
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ ##
40
+ # Loop over the results and publish them to the results channel.
41
+ # Any error in publishing individual messages will result in
42
+ # a log event and an error channel event.
43
+ def publish_results(results)
44
+ results = Array(results)
45
+ results.each do |message|
46
+ with_error_handling(message) do
47
+ @results_channel.publish message
48
+ end
49
+ end
50
+ return nil
51
+ end
52
+
53
+ ##
54
+ # A block wrapper to handle errors when executing a block.
55
+ #
56
+ def with_error_handling(context=nil)
57
+ yield
58
+ rescue => e
59
+ error_event = @error_event_class.new(
60
+ context: context,
61
+ error_message: e.inspect,
62
+ error_backtrace: e.backtrace.join("\n"))
63
+
64
+ # log the error to our logger, and to our error channel.
65
+ @logger.error error_event
66
+ @error_channel.publish error_event
67
+
68
+ # We're done, so just return this error.
69
+ return error_event
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,51 @@
1
+ require 'eventmachine'
2
+
3
+ require 'serf/messages/message_accepted_event'
4
+
5
+ module Serf
6
+ module Runners
7
+
8
+ ##
9
+ # This runner simply wraps another runner to execute in the
10
+ # EventMachine deferred threadpool.
11
+ #
12
+ # NOTE: Because the Serfer class validates messages before
13
+ # sending them to runners (and handlers), this class simply
14
+ # responds to the calling client with an 'MessageAcceptedEvent'
15
+ # to signal that the message will be processed later.
16
+ #
17
+ # Errors caught here will simply be logged. This is because
18
+ # the wrapped runner *MUST* handle its own errors. If an error
19
+ # should propagate up here, then it was most likely an error
20
+ # that occurred in a rescue block... we don't want to complicate
21
+ # any extra publishing to error channels because that may have
22
+ # been the cause of the error.
23
+ #
24
+ class EmRunner
25
+
26
+ def initialize(options={})
27
+ # Manditory: Need a runner because EmRunner is just a wrapper.
28
+ @runner = options.fetch(:runner)
29
+
30
+ @mae_class = options.fetch(:message_accepted_event_class) {
31
+ ::Serf::Messages::MessageAcceptedEvent
32
+ }
33
+ @evm = options.fetch(:event_machine) { ::EventMachine }
34
+ @logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
35
+ end
36
+
37
+ def run(handler, params)
38
+ @evm.defer(proc do
39
+ begin
40
+ @runner.run handler, params
41
+ rescue => e
42
+ @logger.error "#{e.inspect}\n\n#{e.backtrace.join("\n")}"
43
+ end
44
+ end)
45
+ return @mae_class.new message: params
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
data/lib/serf/serfer.rb CHANGED
@@ -1,78 +1,54 @@
1
- require 'eventmachine'
2
-
3
- require 'serf/util/null_object'
1
+ require 'serf/error'
4
2
 
5
3
  module Serf
6
4
 
7
- ##
8
- # The Serfer is a rack app endpoint that:
9
5
  class Serfer
10
6
 
11
7
  def initialize(options={})
8
+ # Manditory, needs a runner.
9
+ @runner = options.fetch(:runner)
10
+
12
11
  # Options for handling the requests
13
12
  @kinds = options[:kinds] || {}
14
13
  @handlers = options[:handlers] || {}
15
- @async_handlers = options[:async_handlers] || {}
16
- @not_found = options[:not_found]
17
-
18
- # Other processing aspects
19
- @em = options.fetch(:event_machine) { ::EM }
20
- @logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
14
+ @not_found = options[:not_found] || proc do
15
+ raise ArgumentError, 'Handler Not Found'
16
+ end
21
17
  end
22
18
 
23
19
  ##
24
20
  # Rack-like call to handle a message
25
21
  #
26
22
  def call(env)
27
- kind = env['kind']
23
+ # We normalize by symbolizing the env keys
24
+ params = env.symbolize_keys
28
25
 
29
- # Do a message_class validation if we have it listed.
30
- # And use the message attributes instead of raw env when passing
31
- # to message handler.
32
- message_class = @kinds[kind]
33
- if message_class
34
- message = message_class.new env
35
- raise message.errors.full_messages.join('. ') unless message.valid?
36
- params = message.attributes
37
- else
38
- params = env.stringify_keys
39
- end
40
-
41
- # Run an asynchronous handler if we have it.
42
- handler = @async_handlers[kind]
26
+ # Pull the kind out of the env.
27
+ kind = params[:kind]
28
+ handler = @handlers[kind]
43
29
  if handler
44
- @em.defer(proc do
45
- begin
46
- handler.call params
47
- rescue => e
48
- @logger.error e
30
+ # Do a message_class validation if we have it listed.
31
+ # And use the message attributes instead of raw env when passing
32
+ # to message handler.
33
+ message_class = @kinds[kind]
34
+ if message_class
35
+ message = message_class.parse params
36
+ unless message.valid?
37
+ raise ArgumentError, message.errors.full_messages.join('. ')
49
38
  end
50
- end)
51
- return [
52
- 202,
53
- {
54
- 'Content-Type' => 'text/plain'
55
- },
56
- ['Accepted']
57
- ]
58
- end
59
-
60
- # Run a synchronous handler if we have it.
61
- handler = @handlers[kind]
62
- return handler.call(params) if handler
63
-
64
- # run a not found
65
- return @not_found.call(params) if @not_found
39
+ params = message.attributes.symbolize_keys
40
+ end
66
41
 
67
- # we can't handle this kind.
68
- return [
69
- 404,
70
- {
71
- 'Content-Type' => 'text/plain',
72
- 'X-Cascade' => 'pass'
73
- },
74
- ['Not Found']
75
- ]
42
+ # Let's run the handler
43
+ return @runner.run(handler, params) if handler
44
+ else
45
+ # we can't handle this kind.
46
+ return @not_found.call env
47
+ end
48
+ rescue => e
49
+ e.extend(::Serf::Error)
50
+ raise e
76
51
  end
77
52
  end
53
+
78
54
  end
@@ -0,0 +1,18 @@
1
+ require 'uuidtools'
2
+
3
+ module Serf
4
+ module Util
5
+
6
+ ##
7
+ # Helper module to include UUIDs as message fields.
8
+ #
9
+ module Uuidable
10
+
11
+ def uuid
12
+ @uuid ||= UUIDTools::UUID.random_create.to_s
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
data/lib/serf/version.rb CHANGED
@@ -2,9 +2,9 @@ module Serf
2
2
 
3
3
  module Version
4
4
  MAJOR = 0
5
- MINOR = 2
5
+ MINOR = 3
6
6
  PATCH = 0
7
- BUILD = 'alpha1'
7
+ BUILD = nil
8
8
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join '.'
9
9
  end
10
10
 
data/serf.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "serf"
8
- s.version = "0.2.0.alpha1"
8
+ s.version = "0.3.0"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Benjamin Yu"]
12
- s.date = "2011-12-12"
12
+ s.date = "2011-12-23"
13
13
  s.description = "Event-Driven SOA with CQRS"
14
14
  s.email = "benjaminlyu@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -27,10 +27,16 @@ Gem::Specification.new do |s|
27
27
  "Rakefile",
28
28
  "lib/serf.rb",
29
29
  "lib/serf/builder.rb",
30
+ "lib/serf/error.rb",
30
31
  "lib/serf/handler.rb",
31
32
  "lib/serf/message.rb",
33
+ "lib/serf/messages/caught_exception_event.rb",
34
+ "lib/serf/messages/message_accepted_event.rb",
35
+ "lib/serf/runners/direct_runner.rb",
36
+ "lib/serf/runners/em_runner.rb",
32
37
  "lib/serf/serfer.rb",
33
38
  "lib/serf/util/null_object.rb",
39
+ "lib/serf/util/uuidable.rb",
34
40
  "lib/serf/version.rb",
35
41
  "serf.gemspec",
36
42
  "spec/serf_spec.rb",
@@ -48,34 +54,40 @@ Gem::Specification.new do |s|
48
54
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
55
  s.add_runtime_dependency(%q<activemodel>, ["~> 3.1.3"])
50
56
  s.add_runtime_dependency(%q<activesupport>, ["~> 3.1.3"])
51
- s.add_runtime_dependency(%q<eventmachine>, ["~> 0.12.10"])
57
+ s.add_runtime_dependency(%q<i18n>, ["~> 0.6.0"])
58
+ s.add_runtime_dependency(%q<uuidtools>, ["~> 2.1.2"])
52
59
  s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
53
60
  s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
54
61
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
55
62
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
56
63
  s.add_development_dependency(%q<rcov>, [">= 0"])
57
64
  s.add_development_dependency(%q<msgpack>, ["~> 0.4.6"])
65
+ s.add_development_dependency(%q<eventmachine>, ["~> 0.12.10"])
58
66
  else
59
67
  s.add_dependency(%q<activemodel>, ["~> 3.1.3"])
60
68
  s.add_dependency(%q<activesupport>, ["~> 3.1.3"])
61
- s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
69
+ s.add_dependency(%q<i18n>, ["~> 0.6.0"])
70
+ s.add_dependency(%q<uuidtools>, ["~> 2.1.2"])
62
71
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
63
72
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
64
73
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
65
74
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
66
75
  s.add_dependency(%q<rcov>, [">= 0"])
67
76
  s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
77
+ s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
68
78
  end
69
79
  else
70
80
  s.add_dependency(%q<activemodel>, ["~> 3.1.3"])
71
81
  s.add_dependency(%q<activesupport>, ["~> 3.1.3"])
72
- s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
82
+ s.add_dependency(%q<i18n>, ["~> 0.6.0"])
83
+ s.add_dependency(%q<uuidtools>, ["~> 2.1.2"])
73
84
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
74
85
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
75
86
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
76
87
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
77
88
  s.add_dependency(%q<rcov>, [">= 0"])
78
89
  s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
90
+ s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
79
91
  end
80
92
  end
81
93
 
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.alpha1
5
- prerelease: 6
4
+ version: 0.3.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Benjamin Yu
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-12 00:00:00.000000000Z
12
+ date: 2011-12-23 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
- requirement: &70254700994980 !ruby/object:Gem::Requirement
16
+ requirement: &70304875304400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.1.3
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70254700994980
24
+ version_requirements: *70304875304400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &70254700994500 !ruby/object:Gem::Requirement
27
+ requirement: &70304875303800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,21 +32,32 @@ dependencies:
32
32
  version: 3.1.3
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70254700994500
35
+ version_requirements: *70304875303800
36
36
  - !ruby/object:Gem::Dependency
37
- name: eventmachine
38
- requirement: &70254700994020 !ruby/object:Gem::Requirement
37
+ name: i18n
38
+ requirement: &70304875303260 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
42
42
  - !ruby/object:Gem::Version
43
- version: 0.12.10
43
+ version: 0.6.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70254700994020
46
+ version_requirements: *70304875303260
47
+ - !ruby/object:Gem::Dependency
48
+ name: uuidtools
49
+ requirement: &70304875302780 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.1.2
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70304875302780
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: rspec
49
- requirement: &70254700993540 !ruby/object:Gem::Requirement
60
+ requirement: &70304875302300 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ~>
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: 2.3.0
55
66
  type: :development
56
67
  prerelease: false
57
- version_requirements: *70254700993540
68
+ version_requirements: *70304875302300
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: yard
60
- requirement: &70254700993060 !ruby/object:Gem::Requirement
71
+ requirement: &70304875301820 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ~>
@@ -65,10 +76,10 @@ dependencies:
65
76
  version: 0.6.0
66
77
  type: :development
67
78
  prerelease: false
68
- version_requirements: *70254700993060
79
+ version_requirements: *70304875301820
69
80
  - !ruby/object:Gem::Dependency
70
81
  name: bundler
71
- requirement: &70254700992580 !ruby/object:Gem::Requirement
82
+ requirement: &70304875301340 !ruby/object:Gem::Requirement
72
83
  none: false
73
84
  requirements:
74
85
  - - ~>
@@ -76,10 +87,10 @@ dependencies:
76
87
  version: 1.0.0
77
88
  type: :development
78
89
  prerelease: false
79
- version_requirements: *70254700992580
90
+ version_requirements: *70304875301340
80
91
  - !ruby/object:Gem::Dependency
81
92
  name: jeweler
82
- requirement: &70254700992100 !ruby/object:Gem::Requirement
93
+ requirement: &70304875206820 !ruby/object:Gem::Requirement
83
94
  none: false
84
95
  requirements:
85
96
  - - ~>
@@ -87,10 +98,10 @@ dependencies:
87
98
  version: 1.6.4
88
99
  type: :development
89
100
  prerelease: false
90
- version_requirements: *70254700992100
101
+ version_requirements: *70304875206820
91
102
  - !ruby/object:Gem::Dependency
92
103
  name: rcov
93
- requirement: &70254700991540 !ruby/object:Gem::Requirement
104
+ requirement: &70304875206340 !ruby/object:Gem::Requirement
94
105
  none: false
95
106
  requirements:
96
107
  - - ! '>='
@@ -98,10 +109,10 @@ dependencies:
98
109
  version: '0'
99
110
  type: :development
100
111
  prerelease: false
101
- version_requirements: *70254700991540
112
+ version_requirements: *70304875206340
102
113
  - !ruby/object:Gem::Dependency
103
114
  name: msgpack
104
- requirement: &70254700991060 !ruby/object:Gem::Requirement
115
+ requirement: &70304875205860 !ruby/object:Gem::Requirement
105
116
  none: false
106
117
  requirements:
107
118
  - - ~>
@@ -109,7 +120,18 @@ dependencies:
109
120
  version: 0.4.6
110
121
  type: :development
111
122
  prerelease: false
112
- version_requirements: *70254700991060
123
+ version_requirements: *70304875205860
124
+ - !ruby/object:Gem::Dependency
125
+ name: eventmachine
126
+ requirement: &70304875205280 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 0.12.10
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *70304875205280
113
135
  description: Event-Driven SOA with CQRS
114
136
  email: benjaminlyu@gmail.com
115
137
  executables: []
@@ -128,10 +150,16 @@ files:
128
150
  - Rakefile
129
151
  - lib/serf.rb
130
152
  - lib/serf/builder.rb
153
+ - lib/serf/error.rb
131
154
  - lib/serf/handler.rb
132
155
  - lib/serf/message.rb
156
+ - lib/serf/messages/caught_exception_event.rb
157
+ - lib/serf/messages/message_accepted_event.rb
158
+ - lib/serf/runners/direct_runner.rb
159
+ - lib/serf/runners/em_runner.rb
133
160
  - lib/serf/serfer.rb
134
161
  - lib/serf/util/null_object.rb
162
+ - lib/serf/util/uuidable.rb
135
163
  - lib/serf/version.rb
136
164
  - serf.gemspec
137
165
  - spec/serf_spec.rb
@@ -151,13 +179,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
179
  version: '0'
152
180
  segments:
153
181
  - 0
154
- hash: 1721782691913874176
182
+ hash: -3406748221232182748
155
183
  required_rubygems_version: !ruby/object:Gem::Requirement
156
184
  none: false
157
185
  requirements:
158
- - - ! '>'
186
+ - - ! '>='
159
187
  - !ruby/object:Gem::Version
160
- version: 1.3.1
188
+ version: '0'
161
189
  requirements: []
162
190
  rubyforge_project:
163
191
  rubygems_version: 1.8.10