serf 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +113 -63
- data/lib/serf/builder.rb +94 -65
- data/lib/serf/message.rb +11 -15
- data/lib/serf/middleware/uuid_tagger.rb +31 -0
- data/lib/serf/runners/direct_runner.rb +19 -33
- data/lib/serf/runners/em_runner.rb +14 -3
- data/lib/serf/serfer.rb +34 -15
- data/lib/serf/util/regexp_matcher.rb +29 -0
- data/lib/serf/util/route_endpoint.rb +37 -0
- data/lib/serf/util/route_set.rb +82 -0
- data/lib/serf/util/with_error_handling.rb +44 -0
- data/lib/serf/version.rb +2 -2
- data/serf.gemspec +7 -3
- metadata +28 -24
- data/lib/serf/handler.rb +0 -91
data/README.md
CHANGED
@@ -65,12 +65,10 @@ Service Libraries
|
|
65
65
|
hashes that define at least one attribute: 'kind'.
|
66
66
|
2. Serialization of the messages SHOULD BE Json or MessagePack (I hate XML).
|
67
67
|
a. `to_hash` is also included.
|
68
|
-
3.
|
69
|
-
::Serf::Handler helper module.
|
70
|
-
4. Handler methods MUST receive a message as either as an options hash
|
68
|
+
3. Handler methods MUST receive a message as either as an options hash
|
71
69
|
(with symbolized keys) or an instance of a declared Message.
|
72
|
-
|
73
|
-
|
70
|
+
4. Handler methods MUST return zero or more messages.
|
71
|
+
5. Handler methods SHOULD handle catch their business logic exceptions and
|
74
72
|
return them as specialized messages that can be forwarded down error channels.
|
75
73
|
Uncaught exceptions that are then caught by Serf are published as
|
76
74
|
generic Serf::CaughtExceptionEvents, and are harder to deal with.
|
@@ -105,7 +103,7 @@ aware that:
|
|
105
103
|
1. Message classes MUST implement `Message.parse(env={})` class method
|
106
104
|
if said message class is to be used as the target object representation
|
107
105
|
of a received message (from ENV hash).
|
108
|
-
a. Serf
|
106
|
+
a. Serf code makes this assumption when it finds an ENV
|
109
107
|
hash that is to be parsed into a message object.
|
110
108
|
|
111
109
|
|
@@ -115,15 +113,35 @@ Example
|
|
115
113
|
# Require our libraries
|
116
114
|
require 'active_model'
|
117
115
|
require 'log4r'
|
118
|
-
require '
|
119
|
-
|
116
|
+
require 'json'
|
117
|
+
|
120
118
|
require 'serf/builder'
|
119
|
+
require 'serf/message'
|
120
|
+
require 'serf/middleware/uuid_tagger'
|
121
121
|
|
122
122
|
# create a simple logger for this example
|
123
|
-
|
124
|
-
logger.outputters = Log4r::FileOutputter.new(
|
123
|
+
outputter = Log4r::FileOutputter.new(
|
125
124
|
'fileOutputter',
|
126
125
|
filename: 'log.txt')
|
126
|
+
['top_level',
|
127
|
+
'serf',
|
128
|
+
'handler',
|
129
|
+
'results_channel',
|
130
|
+
'error_channel'].each do |name|
|
131
|
+
logger = Log4r::Logger.new name
|
132
|
+
logger.outputters = outputter
|
133
|
+
end
|
134
|
+
|
135
|
+
# Helper class for this example to receive result or error messages
|
136
|
+
# and pipe it into our logger.
|
137
|
+
class MyChannel
|
138
|
+
def initialize(logger)
|
139
|
+
@logger = logger
|
140
|
+
end
|
141
|
+
def publish(message)
|
142
|
+
@logger.info "#{message}"
|
143
|
+
end
|
144
|
+
end
|
127
145
|
|
128
146
|
# my_lib/my_message.rb
|
129
147
|
class MyMessage
|
@@ -135,92 +153,107 @@ Example
|
|
135
153
|
validates_presence_of :data
|
136
154
|
|
137
155
|
def initialize(options={})
|
138
|
-
@
|
156
|
+
@data = options[:data]
|
157
|
+
end
|
158
|
+
|
159
|
+
# We define this for Serf::Message serialization.
|
160
|
+
def attributes
|
161
|
+
{ 'data' => data }
|
139
162
|
end
|
140
163
|
|
141
164
|
end
|
142
165
|
|
143
166
|
# my_lib/my_handler.rb
|
144
167
|
class MyHandler
|
145
|
-
include Serf::Handler
|
146
|
-
|
147
|
-
# Declare handlers for a 'my_message' message kind with a Message class.
|
148
|
-
receives(
|
149
|
-
'my_message',
|
150
|
-
as: MyMessage,
|
151
|
-
with: :submit_my_message)
|
152
|
-
|
153
|
-
# This handler of 'other_message' doesn't need a Message class,
|
154
|
-
# and will just work off the ENV hash.
|
155
|
-
receives(
|
156
|
-
'other_message',
|
157
|
-
with: :submit_other_message)
|
158
168
|
|
159
169
|
def initialize(options={})
|
160
170
|
@logger = options[:logger]
|
161
171
|
end
|
162
172
|
|
163
173
|
def submit_my_message(message)
|
164
|
-
@logger.info "In Submit My Message: #{message.
|
174
|
+
@logger.info "In Submit My Message: #{message.to_json}"
|
165
175
|
# Validate message because we have implement my_message with it.
|
166
176
|
unless message.valid?
|
167
177
|
raise ArgumentError, message.errors.full_messages.join(',')
|
168
178
|
end
|
169
179
|
# Do work Here...
|
170
|
-
# And return
|
171
|
-
return
|
180
|
+
# And return 0 or more messages as result. Nil is valid response.
|
181
|
+
return { kind: 'my_message_results' }
|
172
182
|
end
|
173
183
|
|
174
184
|
def submit_other_message(message={})
|
175
185
|
# The message here is the ENV hash because we didn't declare
|
176
186
|
# an :as option with `receives`.
|
177
|
-
@logger.info "In Submit
|
178
|
-
return
|
187
|
+
@logger.info "In Submit OtherMessage: #{message.inspect.to_s}"
|
188
|
+
return [
|
189
|
+
{ kind: 'other_message_result1' },
|
190
|
+
{ kind: 'other_message_result2' }
|
191
|
+
]
|
179
192
|
end
|
180
193
|
|
194
|
+
def raises_error(message={})
|
195
|
+
@logger.info 'In Raises Error, about to raise error'
|
196
|
+
raise 'My Handler Runtime Error'
|
197
|
+
end
|
198
|
+
|
199
|
+
def regexp_matched(message={})
|
200
|
+
@logger.info "RegExp Matched #{message.inspect}"
|
201
|
+
nil
|
202
|
+
end
|
181
203
|
end
|
182
204
|
|
183
|
-
# my_lib/
|
184
|
-
|
185
|
-
|
205
|
+
# my_lib/routes.rb
|
206
|
+
ROUTES = {
|
207
|
+
# Declare a matcher and a list of routes to endpoints.
|
208
|
+
'my_message' => [{
|
186
209
|
# Declares which handler to use. This is the tableized
|
187
210
|
# name of the class. It will be constantized by the serf code.
|
188
211
|
handler: 'my_handler',
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
212
|
+
action: :submit_my_message,
|
213
|
+
|
214
|
+
# Define a parser that will build up a message object.
|
215
|
+
# Default: nil, no parsing done.
|
216
|
+
# Or name of registered parser to use.
|
217
|
+
message_parser: 'my_parser',
|
218
|
+
|
219
|
+
# Default is process in foreground.
|
220
|
+
#background: false
|
221
|
+
}],
|
222
|
+
'other_message' => [{
|
223
|
+
handler: 'my_handler',
|
224
|
+
action: :submit_other_message,
|
225
|
+
background: true
|
226
|
+
}, {
|
227
|
+
handler: 'my_handler',
|
228
|
+
action: :raises_error,
|
229
|
+
background: true
|
230
|
+
}],
|
231
|
+
/^events\/.*$/ => [{
|
193
232
|
handler: 'my_handler',
|
194
|
-
|
195
|
-
|
233
|
+
action: :regexp_matched,
|
234
|
+
background: true
|
235
|
+
}]
|
196
236
|
}
|
197
237
|
|
198
|
-
# Helper class for this example to receive result or error messages
|
199
|
-
# and pipe it into our logger.
|
200
|
-
class MyChannel
|
201
|
-
def initialize(name, logger)
|
202
|
-
@name = name
|
203
|
-
@logger = logger
|
204
|
-
end
|
205
|
-
def publish(message)
|
206
|
-
@logger.info "#{@name} #{message}"
|
207
|
-
#@logger.info message
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
238
|
# Create a new builder for this serf app.
|
212
239
|
builder = Serf::Builder.new do
|
213
|
-
#
|
214
|
-
|
240
|
+
# Include some middleware
|
241
|
+
use Serf::Middleware::UuidTagger
|
242
|
+
|
243
|
+
# Registers routes from different service libary manifests.
|
244
|
+
routes ROUTES
|
245
|
+
|
215
246
|
# Can define arguments to pass to the 'my_handler' initialize method.
|
216
|
-
|
217
|
-
|
218
|
-
|
247
|
+
handler 'my_handler', MyHandler.new(logger: ::Log4r::Logger['handler'])
|
248
|
+
message_parser 'my_parser', MyMessage
|
249
|
+
|
219
250
|
# Create result and error channels for the handler result messages.
|
220
|
-
error_channel MyChannel.new('
|
221
|
-
results_channel MyChannel.new('
|
251
|
+
error_channel MyChannel.new(::Log4r::Logger['error_channel'])
|
252
|
+
results_channel MyChannel.new(::Log4r::Logger['results_channel'])
|
253
|
+
|
222
254
|
# We pass in a logger to our Serf code: Serfer and Runners.
|
223
|
-
logger
|
255
|
+
logger ::Log4r::Logger['serf']
|
256
|
+
|
224
257
|
# Optionally define a not found handler...
|
225
258
|
# Defaults to raising an ArgumentError, 'Not Found'.
|
226
259
|
#not_found lambda {|x| puts x}
|
@@ -228,21 +261,39 @@ Example
|
|
228
261
|
app = builder.to_app
|
229
262
|
|
230
263
|
# Start event machine loop.
|
264
|
+
logger = ::Log4r::Logger['top_level']
|
231
265
|
EM.run do
|
232
266
|
# On the next tick
|
233
267
|
EM.next_tick do
|
234
268
|
logger.info "Start Tick #{Thread.current.object_id}"
|
269
|
+
|
235
270
|
# This will submit a 'my_message' message (as a hash) to the Serf App.
|
271
|
+
# NOTE: We should get an error message pushed to the error channel
|
272
|
+
# because no 'data' field was put in my_message as required
|
273
|
+
# And the Result should have a CaughtExceptionEvent.
|
236
274
|
results = app.call('kind' => 'my_message')
|
237
|
-
|
275
|
+
logger.info "In Tick, MyMessage Results: #{results.inspect}"
|
276
|
+
|
277
|
+
# Here is good result
|
278
|
+
results = app.call('kind' => 'my_message', 'data' => '1234')
|
279
|
+
logger.info "In Tick, MyMessage Results: #{results.inspect}"
|
280
|
+
|
281
|
+
# This will submit 'other_message' to be handled in foreground
|
282
|
+
# Because we declared the 'other_message' kind to be handled async, we
|
238
283
|
# should get a MessageAcceptedEvent as the results.
|
239
|
-
|
284
|
+
results = app.call('kind' => 'other_message')
|
285
|
+
logger.info "In Tick, OtherMessage Results: #{results.inspect}"
|
286
|
+
|
287
|
+
# This will match a regexp
|
288
|
+
results = app.call('kind' => 'events/my_event')
|
289
|
+
logger.info "In Tick, Regexp Results: #{results.inspect}"
|
290
|
+
|
240
291
|
begin
|
241
292
|
# Here, we're going to submit a message that we don't handle.
|
242
293
|
# By default, an exception will be raised.
|
243
294
|
app.call('kind' => 'unhandled_message_kind')
|
244
295
|
rescue => e
|
245
|
-
|
296
|
+
logger.warn "Caught in Tick: #{e.inspect}"
|
246
297
|
end
|
247
298
|
logger.info "End Tick #{Thread.current.object_id}"
|
248
299
|
end
|
@@ -251,7 +302,6 @@ Example
|
|
251
302
|
end
|
252
303
|
end
|
253
304
|
|
254
|
-
|
255
305
|
Contributing to serf
|
256
306
|
====================
|
257
307
|
|
data/lib/serf/builder.rb
CHANGED
@@ -2,13 +2,14 @@ require 'serf/serfer'
|
|
2
2
|
require 'serf/runners/direct_runner'
|
3
3
|
require 'serf/runners/em_runner'
|
4
4
|
require 'serf/util/null_object'
|
5
|
+
require 'serf/util/route_set'
|
5
6
|
|
6
7
|
module Serf
|
7
8
|
|
8
9
|
##
|
9
10
|
# A Serf Builder that processes the SerfUp DSL to build a rack-like
|
10
11
|
# app to route and process received messages. This builder is
|
11
|
-
# implemented
|
12
|
+
# implemented based on code from Rack::Builder.
|
12
13
|
#
|
13
14
|
# builder = Serf::Builder.parse_file 'examples/config.su'
|
14
15
|
# builder.to_app
|
@@ -16,7 +17,7 @@ module Serf
|
|
16
17
|
# or
|
17
18
|
#
|
18
19
|
# builder = Serf::Builder.new do
|
19
|
-
# ... A SerfUp Config block here.
|
20
|
+
# ... A SerfUp Config block here.
|
20
21
|
# end
|
21
22
|
# builder.to_app
|
22
23
|
#
|
@@ -30,17 +31,19 @@ module Serf
|
|
30
31
|
|
31
32
|
def initialize(app=nil, &block)
|
32
33
|
# Configuration about the routes and apps to run.
|
33
|
-
@
|
34
|
-
@
|
34
|
+
@use = []
|
35
|
+
@route_maps = []
|
36
|
+
@handlers = {}
|
37
|
+
@message_parsers = {}
|
35
38
|
@not_found = app
|
36
39
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
40
|
+
# Factories to build objects that wire our Serf App together.
|
41
|
+
# Note that these default implementing classes are also factories
|
42
|
+
# of their own objects (i.e. - define a 'build' class method).
|
43
|
+
@serfer_factory = ::Serf::Serfer
|
44
|
+
@foreground_runner_factory = ::Serf::Runners::DirectRunner
|
45
|
+
@background_runner_factory = ::Serf::Runners::EmRunner
|
46
|
+
@route_set_factory = ::Serf::Util::RouteSet
|
44
47
|
|
45
48
|
# Utility and messaging channels for our Runners
|
46
49
|
# NOTE: these are only used if the builder needs to instantiage runners.
|
@@ -56,32 +59,40 @@ module Serf
|
|
56
59
|
self.new(default_app, &block).to_app
|
57
60
|
end
|
58
61
|
|
59
|
-
def
|
60
|
-
@
|
62
|
+
def use(middleware, *args, &block)
|
63
|
+
@use << proc { |app| middleware.new(app, *args, &block) }
|
61
64
|
end
|
62
65
|
|
63
|
-
def
|
64
|
-
@
|
66
|
+
def routes(route_map)
|
67
|
+
@route_maps << route_map
|
68
|
+
end
|
69
|
+
|
70
|
+
def handler(handler_name, handler)
|
71
|
+
@handlers[handler_name] = handler
|
72
|
+
end
|
73
|
+
|
74
|
+
def message_parser(message_parser_name, message_parser)
|
75
|
+
@message_parsers[message_parser_name] = message_parser
|
65
76
|
end
|
66
77
|
|
67
78
|
def not_found(app)
|
68
79
|
@not_found = app
|
69
80
|
end
|
70
81
|
|
71
|
-
def
|
72
|
-
@
|
82
|
+
def serfer_factory(serfer_factory)
|
83
|
+
@serfer_factory = serfer_factory
|
73
84
|
end
|
74
85
|
|
75
|
-
def
|
76
|
-
@
|
86
|
+
def foreground_runner_factory(foreground_runner_factory)
|
87
|
+
@foreground_runner_factory = foreground_runner_factory
|
77
88
|
end
|
78
89
|
|
79
|
-
def
|
80
|
-
@
|
90
|
+
def background_runner_factory(background_runner_factory)
|
91
|
+
@background_runner_factory = background_runner_factory
|
81
92
|
end
|
82
93
|
|
83
|
-
def
|
84
|
-
@
|
94
|
+
def route_set_factory(route_set_factory)
|
95
|
+
@route_set_factory = route_set_factory
|
85
96
|
end
|
86
97
|
|
87
98
|
def results_channel(results_channel)
|
@@ -97,61 +108,79 @@ module Serf
|
|
97
108
|
end
|
98
109
|
|
99
110
|
def to_app
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
111
|
+
bg_route_set = @route_set_factory.build
|
112
|
+
fg_route_set = @route_set_factory.build
|
113
|
+
|
114
|
+
@route_maps.each do |route_map|
|
115
|
+
route_map.each do |matcher, route_configs|
|
116
|
+
route_configs.each do |route_config|
|
117
|
+
# Get the required handler.
|
118
|
+
# Raises error if handler wasn't declared in config.
|
119
|
+
# Raises error if handler wasn't registered with builder.
|
120
|
+
handler_name = route_config.fetch :handler
|
121
|
+
handler = @handlers.fetch handler_name
|
122
|
+
|
123
|
+
# Get the required action/method of the handler.
|
124
|
+
# Raises error if route_config doesn't have it.
|
125
|
+
action = route_config.fetch :action
|
126
|
+
|
127
|
+
# Lookup the parser if it was defined.
|
128
|
+
# The Parser MAY be either an object or string.
|
129
|
+
# If String, then we're going to look up in parser map.
|
130
|
+
# Raises an error if a parser (string) was declared, but not
|
131
|
+
# registered with the builder.
|
132
|
+
parser = route_config[:message_parser]
|
133
|
+
parser = @message_parsers.fetch(parser) if parser.is_a?(String)
|
134
|
+
|
135
|
+
# We have the handler, action and parser.
|
136
|
+
# Now we're going to add that route to either the background
|
137
|
+
# or foreground route_set.
|
138
|
+
background = route_config.fetch(:background) { false }
|
139
|
+
(background ? bg_route_set : fg_route_set).add_route(
|
140
|
+
matcher: matcher,
|
141
|
+
handler: handler,
|
142
|
+
action: action,
|
143
|
+
message_parser: parser)
|
144
|
+
end
|
119
145
|
end
|
120
146
|
end
|
121
147
|
|
122
|
-
# Get or make our runner
|
123
|
-
|
148
|
+
# Get or make our foreground runner
|
149
|
+
fg_runner = @foreground_runner_factory.build(
|
124
150
|
results_channel: @results_channel,
|
125
151
|
error_channel: @error_channel,
|
126
152
|
logger: @logger)
|
127
153
|
|
128
|
-
#
|
129
|
-
|
154
|
+
# Get or make our background runner
|
155
|
+
bg_runner = @background_runner_factory.build(
|
156
|
+
results_channel: @results_channel,
|
157
|
+
error_channel: @error_channel,
|
158
|
+
logger: @logger)
|
130
159
|
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
not_found: app))
|
160
|
+
# We create the route_sets dependent on built routes.
|
161
|
+
route_sets = {}
|
162
|
+
if fg_route_set.size > 0
|
163
|
+
route_sets[fg_route_set] = fg_runner
|
164
|
+
end
|
165
|
+
if bg_route_set.size > 0
|
166
|
+
route_sets[bg_route_set] = bg_runner
|
139
167
|
end
|
140
168
|
|
141
|
-
#
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
169
|
+
# By default, we go to the not_found app.
|
170
|
+
app = @not_found
|
171
|
+
|
172
|
+
# But if we have routes, then we'll build a serfer to handle it.
|
173
|
+
if route_sets.size > 0
|
174
|
+
app = @serfer_factory.build(
|
175
|
+
route_sets: route_sets,
|
176
|
+
not_found: app,
|
177
|
+
error_channel: @error_channel,
|
146
178
|
logger: @logger)
|
147
|
-
# create the serfer class to run async handlers
|
148
|
-
app = @serfer_class.new(
|
149
|
-
@serfer_options.merge(
|
150
|
-
handlers: async_handlers,
|
151
|
-
runner: async_runner,
|
152
|
-
not_found: app))
|
153
179
|
end
|
154
180
|
|
181
|
+
# We're going to inject middleware here.
|
182
|
+
app = @use.reverse.inject(app) { |a,e| e[a] } if @use.size > 0
|
183
|
+
|
155
184
|
return app
|
156
185
|
end
|
157
186
|
end
|
data/lib/serf/message.rb
CHANGED
@@ -17,24 +17,20 @@ module Serf
|
|
17
17
|
send 'kind=', self.to_s.tableize.singularize
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
self.class.kind
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_hash
|
27
|
-
attributes.merge kind: kind
|
28
|
-
end
|
20
|
+
def kind
|
21
|
+
self.class.kind
|
22
|
+
end
|
29
23
|
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
def to_hash
|
25
|
+
attributes.merge kind: kind
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
def to_msgpack
|
29
|
+
to_hash.to_msgpack
|
30
|
+
end
|
37
31
|
|
32
|
+
def to_json(*args)
|
33
|
+
to_hash.to_json *args
|
38
34
|
end
|
39
35
|
|
40
36
|
module ClassMethods
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'uuidtools'
|
2
|
+
|
3
|
+
module Serf
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
##
|
7
|
+
# Middleware to add a request uuid the ENV Hash to uniquely identify
|
8
|
+
# the handling of this input. But it won't overwrite the uuid field
|
9
|
+
# if the incoming request already has it.
|
10
|
+
#
|
11
|
+
class UuidTagger
|
12
|
+
|
13
|
+
##
|
14
|
+
# @param app the app
|
15
|
+
# @options opts [String] :field the ENV field to set with a UUID.
|
16
|
+
#
|
17
|
+
def initialize(app, options={})
|
18
|
+
@app = app
|
19
|
+
@field = options.fetch(:field) { 'serf.request_uuid' }
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
env = env.dup
|
24
|
+
env[@field] ||= UUIDTools::UUID.random_create.to_s
|
25
|
+
@app.call env
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require 'serf/
|
2
|
-
require 'serf/util/null_object'
|
1
|
+
require 'serf/util/with_error_handling'
|
3
2
|
|
4
3
|
module Serf
|
5
4
|
module Runners
|
@@ -10,28 +9,33 @@ module Runners
|
|
10
9
|
# to proper error or results channels.
|
11
10
|
#
|
12
11
|
class DirectRunner
|
12
|
+
include ::Serf::Util::WithErrorHandling
|
13
13
|
|
14
14
|
def initialize(options={})
|
15
15
|
# Mandatory, we want both results and error channels.
|
16
16
|
@results_channel = options.fetch(:results_channel)
|
17
17
|
@error_channel = options.fetch(:error_channel)
|
18
18
|
|
19
|
-
#
|
20
|
-
|
21
|
-
@
|
22
|
-
::Serf::Messages::CaughtExceptionEvent
|
23
|
-
}
|
24
|
-
|
25
|
-
# Our default logger
|
26
|
-
@logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
|
19
|
+
# Optional overrides for error handling
|
20
|
+
@error_event_class = options[:error_event_class]
|
21
|
+
@logger = options[:logger]
|
27
22
|
end
|
28
23
|
|
29
|
-
def run(
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
def run(endpoints, env)
|
25
|
+
results = []
|
26
|
+
endpoints.each do |ep|
|
27
|
+
run_results = with_error_handling(env) do
|
28
|
+
params = ep.message_parser ? ep.message_parser.parse(env) : env
|
29
|
+
ep.handler.send(ep.action, params)
|
30
|
+
end
|
31
|
+
results.concat Array(run_results)
|
32
|
+
publish_results run_results
|
34
33
|
end
|
34
|
+
return results
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.build(options={})
|
38
|
+
self.new options
|
35
39
|
end
|
36
40
|
|
37
41
|
protected
|
@@ -50,24 +54,6 @@ module Runners
|
|
50
54
|
return nil
|
51
55
|
end
|
52
56
|
|
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
57
|
end
|
72
58
|
|
73
59
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
|
3
3
|
require 'serf/messages/message_accepted_event'
|
4
|
+
require 'serf/runners/direct_runner'
|
4
5
|
|
5
6
|
module Serf
|
6
7
|
module Runners
|
@@ -34,15 +35,25 @@ module Runners
|
|
34
35
|
@logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
|
35
36
|
end
|
36
37
|
|
37
|
-
def run(
|
38
|
+
def run(endpoints, env)
|
39
|
+
endpoints = endpoints.dup
|
40
|
+
env = env.dup
|
38
41
|
@evm.defer(proc do
|
39
42
|
begin
|
40
|
-
@runner.run
|
43
|
+
@runner.run endpoints, env
|
41
44
|
rescue => e
|
42
45
|
@logger.error "#{e.inspect}\n\n#{e.backtrace.join("\n")}"
|
43
46
|
end
|
44
47
|
end)
|
45
|
-
return @mae_class.new message:
|
48
|
+
return @mae_class.new message: env
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.build(options={})
|
52
|
+
options[:runner] = options.fetch(:runner) {
|
53
|
+
factory = options[:runner_factory] || ::Serf::Runners::DirectRunner
|
54
|
+
factory.build options
|
55
|
+
}
|
56
|
+
self.new options
|
46
57
|
end
|
47
58
|
|
48
59
|
end
|
data/lib/serf/serfer.rb
CHANGED
@@ -1,41 +1,60 @@
|
|
1
1
|
require 'serf/error'
|
2
|
+
require 'serf/util/with_error_handling'
|
2
3
|
|
3
4
|
module Serf
|
4
5
|
|
5
6
|
class Serfer
|
7
|
+
include ::Serf::Util::WithErrorHandling
|
6
8
|
|
7
9
|
def initialize(options={})
|
8
|
-
#
|
9
|
-
@
|
10
|
+
# Each route_set has a runner.
|
11
|
+
@route_sets = options[:route_sets] || {}
|
12
|
+
|
13
|
+
# Optional overrides for WithErrorHandling
|
14
|
+
@error_channel = options[:error_channel]
|
15
|
+
@error_event_class = options[:error_event_class]
|
16
|
+
@logger = options[:logger]
|
10
17
|
|
11
18
|
# Options for handling the requests
|
12
|
-
@handlers = options[:handlers] || {}
|
13
19
|
@not_found = options[:not_found] || proc do
|
14
20
|
raise ArgumentError, 'Handler Not Found'
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
18
24
|
##
|
19
|
-
# Rack-like call to
|
25
|
+
# Rack-like call to run set of handlers for a message
|
20
26
|
#
|
21
27
|
def call(env)
|
22
28
|
# We normalize by symbolizing the env keys
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
env = env.symbolize_keys
|
30
|
+
|
31
|
+
# We're going to concat all the results
|
32
|
+
matched_routes = false
|
33
|
+
results = []
|
34
|
+
@route_sets.each do |route_set, runner|
|
35
|
+
with_error_handling(env) do
|
36
|
+
endpoints = route_set.match_routes env
|
37
|
+
if endpoints.size > 0
|
38
|
+
matched_routes = true
|
39
|
+
results.concat Array(runner.run(endpoints, env))
|
40
|
+
end
|
41
|
+
end
|
34
42
|
end
|
43
|
+
|
44
|
+
# If we don't have any handlers, do the not_found call.
|
45
|
+
# NOTE: Purposefully not wrapping this in exception handling.
|
46
|
+
return @not_found.call env unless matched_routes
|
47
|
+
|
48
|
+
return results
|
35
49
|
rescue => e
|
36
50
|
e.extend(::Serf::Error)
|
37
51
|
raise e
|
38
52
|
end
|
53
|
+
|
54
|
+
def self.build(options={})
|
55
|
+
self.new options
|
56
|
+
end
|
57
|
+
|
39
58
|
end
|
40
59
|
|
41
60
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Serf
|
2
|
+
module Util
|
3
|
+
|
4
|
+
##
|
5
|
+
# A matcher that does a regexp match on a specific field
|
6
|
+
# in the given Env. By default, we use this to do regexp matching
|
7
|
+
# on message kinds for routing.
|
8
|
+
#
|
9
|
+
class RegexpMatcher
|
10
|
+
attr_reader :regexp
|
11
|
+
attr_reader :field
|
12
|
+
|
13
|
+
def initialize(regexp, options={})
|
14
|
+
@regexp = regexp
|
15
|
+
@field = options.fetch(:field) { :kind }
|
16
|
+
end
|
17
|
+
|
18
|
+
def ===(env)
|
19
|
+
return @regexp === env[@field]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.build(regexp)
|
23
|
+
return self.new regexp
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Serf
|
2
|
+
module Util
|
3
|
+
|
4
|
+
##
|
5
|
+
# A simple class to represent route endpoints. RouteSets
|
6
|
+
# store a list of endpoints per matcher, and Runners use endpoints
|
7
|
+
# to then execute the handlers.
|
8
|
+
#
|
9
|
+
class RouteEndpoint
|
10
|
+
# Actual handler object that defines the action method.
|
11
|
+
attr_accessor :handler
|
12
|
+
# The method to call.
|
13
|
+
attr_accessor :action
|
14
|
+
# A parser that turns the message ENV hash into a Message object
|
15
|
+
# that the handler#action uses.
|
16
|
+
attr_accessor :message_parser
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
# Mandatory parameters
|
20
|
+
@handler = options.fetch :handler
|
21
|
+
@action = options.fetch :action
|
22
|
+
|
23
|
+
# Optional parameters
|
24
|
+
@message_parser = options[:message_parser]
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Default factory method.
|
29
|
+
#
|
30
|
+
def self.build(options={})
|
31
|
+
return self.new options
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'serf/util/route_endpoint'
|
2
|
+
require 'serf/util/regexp_matcher'
|
3
|
+
|
4
|
+
module Serf
|
5
|
+
module Util
|
6
|
+
|
7
|
+
##
|
8
|
+
# RouteSets hold routing information for ENV hashes to matched endpoints.
|
9
|
+
#
|
10
|
+
class RouteSet
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@routes = {}
|
14
|
+
@matchers = []
|
15
|
+
@regexp_matcher_factory = options.fetch(:regexp_matcher_factory) {
|
16
|
+
::Serf::Util::RegexpMatcher
|
17
|
+
}
|
18
|
+
@route_endpoint_factory = options.fetch(:route_endpoint_factory) {
|
19
|
+
::Serf::Util::RouteEndpoint
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Connects a matcher (String or an Object implementing ===) to an endpoint.
|
25
|
+
#
|
26
|
+
# @option opts [Obj, String] :matcher Matches ENV Hashes to endpoints.
|
27
|
+
# Note that String and Regexp values are set up to match the
|
28
|
+
# :kind key from ENV Hashes.
|
29
|
+
# @option opts [Obj] :handler Receiver of the action.
|
30
|
+
# @option opts [Symbol, String] :action Method to call on handler.
|
31
|
+
# @option opts [#parse] :message_parser Translates ENV Hash to Message Obj.
|
32
|
+
#
|
33
|
+
def add_route(options={})
|
34
|
+
# We create our endpoint representation.
|
35
|
+
endpoint = @route_endpoint_factory.build(
|
36
|
+
handler: options.fetch(:handler),
|
37
|
+
action: options.fetch(:action),
|
38
|
+
message_parser: options[:message_parser])
|
39
|
+
|
40
|
+
# Maybe we have an non-String matcher. Handle the Regexp case.
|
41
|
+
# We only keep track of matchers if it isn't a string because
|
42
|
+
# string matchers are just pulled out of routes by key lookup.
|
43
|
+
matcher = options.fetch :matcher
|
44
|
+
matcher = @regexp_matcher_factory.build matcher if matcher.kind_of? Regexp
|
45
|
+
@matchers << matcher unless matcher.is_a? String
|
46
|
+
|
47
|
+
# We add the route (matcher+endpoint) into our routes
|
48
|
+
@routes[matcher] ||= []
|
49
|
+
@routes[matcher] << endpoint
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# @param [Hash] env The input message environment to match for routes.
|
54
|
+
# @return [Array] List of endpoints that matched.
|
55
|
+
#
|
56
|
+
def match_routes(env={})
|
57
|
+
kind = env[:kind]
|
58
|
+
routes = []
|
59
|
+
routes.concat Array(@routes[kind])
|
60
|
+
@matchers.each do |matcher|
|
61
|
+
routes.concat Array(@routes[matcher]) if matcher === env
|
62
|
+
end
|
63
|
+
return routes
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# @return [Integer] Number of routes this RouteSet tracks.
|
68
|
+
#
|
69
|
+
def size
|
70
|
+
return @routes.size
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Default factory method.
|
75
|
+
#
|
76
|
+
def self.build(options={})
|
77
|
+
self.new options
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'serf/messages/caught_exception_event'
|
2
|
+
require 'serf/util/null_object'
|
3
|
+
|
4
|
+
module Serf
|
5
|
+
module Util
|
6
|
+
|
7
|
+
##
|
8
|
+
# Helper module to rescues exceptions from executing blocks of
|
9
|
+
# code, and then logs+publishes the error event.
|
10
|
+
#
|
11
|
+
module WithErrorHandling
|
12
|
+
|
13
|
+
##
|
14
|
+
# A block wrapper to handle errors when executing a block.
|
15
|
+
#
|
16
|
+
# Including classes may have the following instance variables
|
17
|
+
# to override the default values:
|
18
|
+
# * @error_event_class - ::Serf::Messages::CaughtExceptionEvent
|
19
|
+
# * @logger - ::Serf::Util::NullObject.new
|
20
|
+
# * @error_channel - ::Serf::Util::NullObject.new
|
21
|
+
#
|
22
|
+
def with_error_handling(context=nil)
|
23
|
+
yield
|
24
|
+
rescue => e
|
25
|
+
eec = @error_event_class || ::Serf::Messages::CaughtExceptionEvent
|
26
|
+
logger = @logger || ::Serf::Util::NullObject.new
|
27
|
+
error_channel = @error_channel || ::Serf::Util::NullObject.new
|
28
|
+
error_event = eec.new(
|
29
|
+
context: context,
|
30
|
+
error_message: e.inspect,
|
31
|
+
error_backtrace: e.backtrace.join("\n"))
|
32
|
+
|
33
|
+
# log the error to our logger, and to our error channel.
|
34
|
+
logger.error error_event
|
35
|
+
error_channel.publish error_event
|
36
|
+
|
37
|
+
# We're done, so just return this error.
|
38
|
+
return error_event
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/serf/version.rb
CHANGED
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.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
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 = "2012-01-
|
12
|
+
s.date = "2012-01-26"
|
13
13
|
s.description = "Event-Driven SOA with CQRS"
|
14
14
|
s.email = "benjaminlyu@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -28,15 +28,19 @@ Gem::Specification.new do |s|
|
|
28
28
|
"lib/serf.rb",
|
29
29
|
"lib/serf/builder.rb",
|
30
30
|
"lib/serf/error.rb",
|
31
|
-
"lib/serf/handler.rb",
|
32
31
|
"lib/serf/message.rb",
|
33
32
|
"lib/serf/messages/caught_exception_event.rb",
|
34
33
|
"lib/serf/messages/message_accepted_event.rb",
|
34
|
+
"lib/serf/middleware/uuid_tagger.rb",
|
35
35
|
"lib/serf/runners/direct_runner.rb",
|
36
36
|
"lib/serf/runners/em_runner.rb",
|
37
37
|
"lib/serf/serfer.rb",
|
38
38
|
"lib/serf/util/null_object.rb",
|
39
|
+
"lib/serf/util/regexp_matcher.rb",
|
40
|
+
"lib/serf/util/route_endpoint.rb",
|
41
|
+
"lib/serf/util/route_set.rb",
|
39
42
|
"lib/serf/util/uuidable.rb",
|
43
|
+
"lib/serf/util/with_error_handling.rb",
|
40
44
|
"lib/serf/version.rb",
|
41
45
|
"serf.gemspec",
|
42
46
|
"spec/serf_spec.rb",
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-26 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &70145106081500 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.2.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70145106081500
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: i18n
|
27
|
-
requirement: &
|
27
|
+
requirement: &70145106073960 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.6.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70145106073960
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: uuidtools
|
38
|
-
requirement: &
|
38
|
+
requirement: &70145106072920 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 2.1.2
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70145106072920
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70145106071740 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 2.3.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70145106071740
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: yard
|
60
|
-
requirement: &
|
60
|
+
requirement: &70145106070620 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 0.6.0
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70145106070620
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
|
-
requirement: &
|
71
|
+
requirement: &70145106069640 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: 1.0.0
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70145106069640
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: jeweler
|
82
|
-
requirement: &
|
82
|
+
requirement: &70145106068740 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ~>
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: 1.6.4
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70145106068740
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: simplecov
|
93
|
-
requirement: &
|
93
|
+
requirement: &70145106067300 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70145106067300
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: msgpack
|
104
|
-
requirement: &
|
104
|
+
requirement: &70145106053600 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,10 +109,10 @@ dependencies:
|
|
109
109
|
version: 0.4.6
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70145106053600
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: eventmachine
|
115
|
-
requirement: &
|
115
|
+
requirement: &70145106052540 !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
118
118
|
- - ! '>='
|
@@ -120,7 +120,7 @@ dependencies:
|
|
120
120
|
version: 0.12.10
|
121
121
|
type: :development
|
122
122
|
prerelease: false
|
123
|
-
version_requirements: *
|
123
|
+
version_requirements: *70145106052540
|
124
124
|
description: Event-Driven SOA with CQRS
|
125
125
|
email: benjaminlyu@gmail.com
|
126
126
|
executables: []
|
@@ -140,15 +140,19 @@ files:
|
|
140
140
|
- lib/serf.rb
|
141
141
|
- lib/serf/builder.rb
|
142
142
|
- lib/serf/error.rb
|
143
|
-
- lib/serf/handler.rb
|
144
143
|
- lib/serf/message.rb
|
145
144
|
- lib/serf/messages/caught_exception_event.rb
|
146
145
|
- lib/serf/messages/message_accepted_event.rb
|
146
|
+
- lib/serf/middleware/uuid_tagger.rb
|
147
147
|
- lib/serf/runners/direct_runner.rb
|
148
148
|
- lib/serf/runners/em_runner.rb
|
149
149
|
- lib/serf/serfer.rb
|
150
150
|
- lib/serf/util/null_object.rb
|
151
|
+
- lib/serf/util/regexp_matcher.rb
|
152
|
+
- lib/serf/util/route_endpoint.rb
|
153
|
+
- lib/serf/util/route_set.rb
|
151
154
|
- lib/serf/util/uuidable.rb
|
155
|
+
- lib/serf/util/with_error_handling.rb
|
152
156
|
- lib/serf/version.rb
|
153
157
|
- serf.gemspec
|
154
158
|
- spec/serf_spec.rb
|
@@ -168,7 +172,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
172
|
version: '0'
|
169
173
|
segments:
|
170
174
|
- 0
|
171
|
-
hash: -
|
175
|
+
hash: -650473328553199241
|
172
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
177
|
none: false
|
174
178
|
requirements:
|
data/lib/serf/handler.rb
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
require 'active_support/core_ext/class/attribute'
|
3
|
-
require 'active_support/core_ext/hash/keys'
|
4
|
-
require 'active_support/core_ext/object/blank'
|
5
|
-
require 'active_support/ordered_options'
|
6
|
-
|
7
|
-
require 'serf/error'
|
8
|
-
|
9
|
-
module Serf
|
10
|
-
|
11
|
-
module Handler
|
12
|
-
extend ActiveSupport::Concern
|
13
|
-
|
14
|
-
included do
|
15
|
-
# In the class that includes this module, we're going to
|
16
|
-
# create an inheritable class attribute that will store
|
17
|
-
# our mappings between messages and the methods to call.
|
18
|
-
class_attribute :serf_actions
|
19
|
-
class_attribute :serf_message_classes
|
20
|
-
send(
|
21
|
-
'serf_actions=',
|
22
|
-
ActiveSupport::InheritableOptions.new)
|
23
|
-
send(
|
24
|
-
'serf_message_classes=',
|
25
|
-
ActiveSupport::InheritableOptions.new)
|
26
|
-
|
27
|
-
def self.inherited(kls) #:nodoc:
|
28
|
-
super
|
29
|
-
# Sets the current subclass class attribute to be an
|
30
|
-
# inheritable copy of the superclass options.
|
31
|
-
kls.send(
|
32
|
-
'serf_actions=',
|
33
|
-
self.serf_actions.inheritable_copy)
|
34
|
-
kls.send(
|
35
|
-
'serf_message_classes=',
|
36
|
-
self.serf_message_classes.inheritable_copy)
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
module InstanceMethods
|
42
|
-
|
43
|
-
##
|
44
|
-
# Rack-like call. It receives an environment hash, which we
|
45
|
-
# assume is a message.
|
46
|
-
#
|
47
|
-
def call(env={})
|
48
|
-
# Just to stringify the environment keys
|
49
|
-
env = env.symbolize_keys
|
50
|
-
# Make sure a kind was set, and that we can handle it.
|
51
|
-
message_kind = env[:kind]
|
52
|
-
raise ArgumentError, 'No "kind" in call env' if message_kind.blank?
|
53
|
-
method = self.class.serf_actions[message_kind]
|
54
|
-
raise ArgumentError, "#{message_kind} not found" if method.blank?
|
55
|
-
# Optionally convert the env into a Message class.
|
56
|
-
# Let the actual handler method validate if they want.
|
57
|
-
message_class = self.class.serf_message_classes[message_kind]
|
58
|
-
env = message_class.parse env if message_class
|
59
|
-
# Now execute the method with the environment parameters
|
60
|
-
self.send method, env
|
61
|
-
rescue => e
|
62
|
-
e.extend ::Serf::Error
|
63
|
-
raise e
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
module ClassMethods
|
68
|
-
|
69
|
-
##
|
70
|
-
# registers a method to handle the receipt of a message type.
|
71
|
-
# @param *args splat list of message kinds
|
72
|
-
# @options opts [Symbol] :with The method to call.
|
73
|
-
# @options opts [Object] :as The Message class to call `parse`.
|
74
|
-
#
|
75
|
-
def receives(*args)
|
76
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
77
|
-
exposed_method = options[:with]
|
78
|
-
raise ArgumentError, 'Missing "with" option' if exposed_method.blank?
|
79
|
-
message_class = options[:as]
|
80
|
-
args.each do |kind|
|
81
|
-
raise ArgumentError, 'Blank kind' if kind.blank?
|
82
|
-
self.serf_actions[kind] = exposed_method
|
83
|
-
self.serf_message_classes[kind] = message_class if message_class
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|