serf 0.4.1 → 0.5.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/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
|