serf 0.2.0.alpha1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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