serf 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -4
- data/Gemfile.lock +27 -17
- data/README.md +317 -120
- data/docs/thread_pools.txt +16 -0
- data/lib/serf/builder.rb +126 -162
- data/lib/serf/command.rb +113 -0
- data/lib/serf/message.rb +16 -2
- data/lib/serf/messages/caught_exception_event.rb +10 -7
- data/lib/serf/routing/endpoint.rb +49 -0
- data/lib/serf/routing/registry.rb +66 -0
- data/lib/serf/runners/direct.rb +52 -0
- data/lib/serf/runners/event_machine.rb +71 -0
- data/lib/serf/runners/girl_friday.rb +73 -0
- data/lib/serf/runners/helper.rb +23 -0
- data/lib/serf/serfer.rb +112 -25
- data/lib/serf/util/{with_error_handling.rb → error_handling.rb} +23 -10
- data/lib/serf/util/options_extraction.rb +106 -0
- data/lib/serf/version.rb +2 -2
- data/serf.gemspec +31 -20
- metadata +60 -33
- data/lib/serf/runners/direct_runner.rb +0 -60
- data/lib/serf/runners/em_runner.rb +0 -62
- data/lib/serf/util/route_endpoint.rb +0 -37
- data/lib/serf/util/route_set.rb +0 -82
data/Gemfile
CHANGED
@@ -12,17 +12,23 @@ gem 'uuidtools', '>= 2.1.2'
|
|
12
12
|
# Add dependencies to develop your gem here.
|
13
13
|
# Include everything needed to run rake, tests, features, etc.
|
14
14
|
group :development, :test do
|
15
|
-
gem
|
16
|
-
gem
|
17
|
-
gem
|
18
|
-
gem
|
15
|
+
gem "rspec", "~> 2.8.0"
|
16
|
+
gem "yard", "~> 0.7.5"
|
17
|
+
gem "bundler", "~> 1.0.22"
|
18
|
+
gem "jeweler", "~> 1.8.3"
|
19
19
|
gem 'simplecov', '>= 0'
|
20
20
|
|
21
|
+
# For our testing
|
22
|
+
gem 'log4r', '~> 1.1.10'
|
23
|
+
|
21
24
|
# Soft Dependencies
|
22
25
|
#gem 'log4r', '~> 1.1.9'
|
23
26
|
gem 'msgpack', '>= 0.4.6'
|
24
27
|
#gem 'multi_json', '~> 1.0.3'
|
25
28
|
|
26
29
|
# For Server Side of things
|
30
|
+
|
31
|
+
# EventMachine is now optional runner
|
27
32
|
gem 'eventmachine', '>= 0.12.10'
|
33
|
+
gem 'girl_friday', '~> 0.9.7'
|
28
34
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,46 +1,56 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (3.2.
|
4
|
+
activesupport (3.2.2)
|
5
5
|
i18n (~> 0.6)
|
6
6
|
multi_json (~> 1.0)
|
7
|
+
connection_pool (0.1.0)
|
7
8
|
diff-lcs (1.1.3)
|
8
9
|
eventmachine (0.12.10)
|
10
|
+
girl_friday (0.9.7)
|
11
|
+
connection_pool (~> 0.1.0)
|
9
12
|
git (1.2.5)
|
10
13
|
i18n (0.6.0)
|
11
|
-
jeweler (1.
|
14
|
+
jeweler (1.8.3)
|
12
15
|
bundler (~> 1.0)
|
13
16
|
git (>= 1.2.5)
|
14
17
|
rake
|
18
|
+
rdoc
|
19
|
+
json (1.6.5)
|
20
|
+
log4r (1.1.10)
|
15
21
|
msgpack (0.4.6)
|
16
|
-
multi_json (1.0
|
22
|
+
multi_json (1.1.0)
|
17
23
|
rake (0.9.2.2)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
rspec-
|
22
|
-
|
23
|
-
|
24
|
+
rdoc (3.12)
|
25
|
+
json (~> 1.4)
|
26
|
+
rspec (2.8.0)
|
27
|
+
rspec-core (~> 2.8.0)
|
28
|
+
rspec-expectations (~> 2.8.0)
|
29
|
+
rspec-mocks (~> 2.8.0)
|
30
|
+
rspec-core (2.8.0)
|
31
|
+
rspec-expectations (2.8.0)
|
24
32
|
diff-lcs (~> 1.1.2)
|
25
|
-
rspec-mocks (2.
|
26
|
-
simplecov (0.
|
27
|
-
multi_json (~> 1.0
|
33
|
+
rspec-mocks (2.8.0)
|
34
|
+
simplecov (0.6.1)
|
35
|
+
multi_json (~> 1.0)
|
28
36
|
simplecov-html (~> 0.5.3)
|
29
37
|
simplecov-html (0.5.3)
|
30
38
|
uuidtools (2.1.2)
|
31
|
-
yard (0.
|
39
|
+
yard (0.7.5)
|
32
40
|
|
33
41
|
PLATFORMS
|
34
42
|
ruby
|
35
43
|
|
36
44
|
DEPENDENCIES
|
37
45
|
activesupport (>= 3.2.0)
|
38
|
-
bundler (~> 1.0.
|
46
|
+
bundler (~> 1.0.22)
|
39
47
|
eventmachine (>= 0.12.10)
|
48
|
+
girl_friday (~> 0.9.7)
|
40
49
|
i18n (>= 0.6.0)
|
41
|
-
jeweler (~> 1.
|
50
|
+
jeweler (~> 1.8.3)
|
51
|
+
log4r (~> 1.1.10)
|
42
52
|
msgpack (>= 0.4.6)
|
43
|
-
rspec (~> 2.
|
53
|
+
rspec (~> 2.8.0)
|
44
54
|
simplecov
|
45
55
|
uuidtools (>= 2.1.2)
|
46
|
-
yard (~> 0.
|
56
|
+
yard (~> 0.7.5)
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ manner.
|
|
15
15
|
|
16
16
|
Fundamentally, a service creates an abstraction of:
|
17
17
|
1. Messages
|
18
|
-
2. Handlers
|
18
|
+
2. Handlers/Commands
|
19
19
|
|
20
20
|
Messages are the representation of data, documents, etc that are
|
21
21
|
marshalled from client to service, which represents the business
|
@@ -29,22 +29,22 @@ Handlers are the code that is executed over Messages.
|
|
29
29
|
Handlers may process Command Messages.
|
30
30
|
Handlers may process observed Events that 3rd party services emit.
|
31
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
32
|
Serf App and Channels
|
36
33
|
=====================
|
37
34
|
|
38
35
|
A Serf App is a Rack-like application that accepts an ENV hash as input.
|
39
36
|
This ENV hash is simply the hash representation of a message to be processed.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
|
38
|
+
The Serf App, as configured by DSL, will:
|
39
|
+
1. route the ENV to the proper Endpoint
|
40
|
+
2. The endpoint will create a handler instance.
|
41
|
+
a. The handler's build class method will be given an ENV, which
|
42
|
+
may be handled as fit. The Command class will help by parsing it
|
43
|
+
into a Message object as registered by the implementing subclass.
|
44
|
+
3. Push the handler's results to a response channel.
|
45
|
+
a. Raised errors are caught and pushed to an error channel.
|
46
|
+
b. These channels are normally message queuing channels.
|
47
|
+
c. We only require the channel instance to respond to the 'push'
|
48
48
|
method and accept a message (or message hash) as the argument.
|
49
49
|
4. return the handler's results to the caller if it is blocking mode.
|
50
50
|
a. In non-blocking mode, an MessageAcceptedEvent is returned instead
|
@@ -65,12 +65,14 @@ 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
|
-
|
70
|
-
|
68
|
+
3. Handlers MUST implement the 'build' class method, which
|
69
|
+
MUST receive the ENV Hash as the first parameter, followed by supplemental
|
70
|
+
arguments as declared in the DSL.
|
71
|
+
4. Handler methods SHOULD return zero or more messages.
|
72
|
+
a. Raised errors are caught and pushed to error channels.
|
71
73
|
5. Handler methods SHOULD handle catch their business logic exceptions and
|
72
74
|
return them as specialized messages that can be forwarded down error channels.
|
73
|
-
Uncaught exceptions that are then caught by Serf are
|
75
|
+
Uncaught exceptions that are then caught by Serf are pushed as
|
74
76
|
generic Serf::CaughtExceptionEvents, and are harder to deal with.
|
75
77
|
|
76
78
|
|
@@ -107,27 +109,28 @@ aware that:
|
|
107
109
|
hash that is to be parsed into a message object.
|
108
110
|
|
109
111
|
|
110
|
-
Example
|
111
|
-
|
112
|
+
Example With GirlFriday
|
113
|
+
=======================
|
112
114
|
|
113
115
|
# Require our libraries
|
114
|
-
require 'active_model'
|
115
116
|
require 'log4r'
|
116
117
|
require 'json'
|
117
118
|
|
118
119
|
require 'serf/builder'
|
120
|
+
require 'serf/command'
|
119
121
|
require 'serf/message'
|
120
122
|
require 'serf/middleware/uuid_tagger'
|
123
|
+
require 'serf/util/options_extraction'
|
121
124
|
|
122
125
|
# create a simple logger for this example
|
123
126
|
outputter = Log4r::FileOutputter.new(
|
124
127
|
'fileOutputter',
|
125
128
|
filename: 'log.txt')
|
126
|
-
['
|
129
|
+
['tick',
|
127
130
|
'serf',
|
128
|
-
'
|
129
|
-
'
|
130
|
-
'
|
131
|
+
'hndl',
|
132
|
+
'resp',
|
133
|
+
'errr'].each do |name|
|
131
134
|
logger = Log4r::Logger.new name
|
132
135
|
logger.outputters = outputter
|
133
136
|
end
|
@@ -135,131 +138,307 @@ Example
|
|
135
138
|
# Helper class for this example to receive result or error messages
|
136
139
|
# and pipe it into our logger.
|
137
140
|
class MyChannel
|
138
|
-
def initialize(logger)
|
141
|
+
def initialize(logger, error=false)
|
139
142
|
@logger = logger
|
143
|
+
@error = error
|
140
144
|
end
|
141
|
-
def
|
142
|
-
@
|
145
|
+
def push(message)
|
146
|
+
if @error
|
147
|
+
@logger.fatal "#{message}"
|
148
|
+
else
|
149
|
+
@logger.debug "#{message}"
|
150
|
+
end
|
143
151
|
end
|
144
152
|
end
|
145
153
|
|
146
154
|
# my_lib/my_message.rb
|
155
|
+
# This class is stripped down minimal functionality that
|
156
|
+
# ActiveModel or Aequitas/Virtus implements.
|
147
157
|
class MyMessage
|
158
|
+
include Serf::Util::OptionsExtraction
|
148
159
|
include Serf::Message
|
149
|
-
include ActiveModel::Validations
|
150
160
|
|
151
161
|
attr_accessor :data
|
152
162
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
@data = options[:data]
|
163
|
+
def initialize(*args)
|
164
|
+
extract_options! args
|
165
|
+
self.data = opts :data
|
157
166
|
end
|
158
167
|
|
159
|
-
# We define this for Serf::Message serialization.
|
160
168
|
def attributes
|
161
169
|
{ 'data' => data }
|
162
170
|
end
|
163
171
|
|
172
|
+
def valid?
|
173
|
+
!data.nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
def full_error_messages
|
177
|
+
'Data is blank' if data.nil?
|
178
|
+
end
|
164
179
|
end
|
165
180
|
|
166
|
-
# my_lib/
|
167
|
-
class
|
181
|
+
# my_lib/my_overloaded_command.rb
|
182
|
+
class MyOverloadedCommand
|
183
|
+
include Serf::Command
|
168
184
|
|
169
|
-
|
170
|
-
|
185
|
+
self.request_factory = MyMessage
|
186
|
+
|
187
|
+
def initialize(*args)
|
188
|
+
super
|
189
|
+
raise "Constructor Error: #{opts(:name)}" if opts :raises_in_new
|
171
190
|
end
|
172
191
|
|
173
|
-
def
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
192
|
+
def call
|
193
|
+
# Set our logger
|
194
|
+
logger = ::Log4r::Logger['hndl']
|
195
|
+
|
196
|
+
# Just our name to sort things out
|
197
|
+
name = opts! :name
|
198
|
+
logger.info "#{name}: #{request.to_json}"
|
199
|
+
|
200
|
+
raise "Forcing Error in #{name}" if opts(:raises, false)
|
201
|
+
|
179
202
|
# Do work Here...
|
180
203
|
# And return 0 or more messages as result. Nil is valid response.
|
181
|
-
return { kind:
|
204
|
+
return { kind: "#{name}_result", input: request.to_hash }
|
205
|
+
end
|
206
|
+
|
207
|
+
def inspect
|
208
|
+
"MyOverloadedCommand: #{opts(:name,'notnamed')}, #{request.to_json}"
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
# Create a new builder for this serf app.
|
214
|
+
builder = Serf::Builder.new do
|
215
|
+
# Include some middleware
|
216
|
+
use Serf::Middleware::UuidTagger
|
217
|
+
|
218
|
+
# Create response and error channels for the handler result messages.
|
219
|
+
response_channel MyChannel.new(::Log4r::Logger['resp'])
|
220
|
+
error_channel MyChannel.new(::Log4r::Logger['errr'], true)
|
221
|
+
|
222
|
+
# We pass in a logger to our Serf code: Serfer and Runners.
|
223
|
+
logger ::Log4r::Logger['serf']
|
224
|
+
|
225
|
+
runner :direct
|
226
|
+
|
227
|
+
match 'my_message'
|
228
|
+
run MyOverloadedCommand, name: 'my_message_command'
|
229
|
+
|
230
|
+
match 'raise_error_message'
|
231
|
+
run MyOverloadedCommand, name: 'foreground_raises_error', raises: true
|
232
|
+
run MyOverloadedCommand, name: 'constructor_error', raises_in_new: true
|
233
|
+
|
234
|
+
match 'other_message'
|
235
|
+
run MyOverloadedCommand, name: 'foreground_other_message'
|
236
|
+
|
237
|
+
runner :girl_friday
|
238
|
+
|
239
|
+
# This message kind is handled by multiple handlers.
|
240
|
+
match 'other_message'
|
241
|
+
run MyOverloadedCommand, name: 'background_other_message'
|
242
|
+
run MyOverloadedCommand, name: 'background_raises error', raises: true
|
243
|
+
|
244
|
+
match /^events\/.*$/
|
245
|
+
run MyOverloadedCommand, name: 'regexp_matched_command'
|
246
|
+
|
247
|
+
# Optionally define a not found handler...
|
248
|
+
# Defaults to raising an ArgumentError, 'Not Found'.
|
249
|
+
#not_found lambda {|x| puts x}
|
250
|
+
end
|
251
|
+
app = builder.to_app
|
252
|
+
|
253
|
+
# Start event machine loop.
|
254
|
+
logger = ::Log4r::Logger['tick']
|
255
|
+
|
256
|
+
logger.info "Start Tick #{Thread.current.object_id}"
|
257
|
+
|
258
|
+
# This will submit a 'my_message' message (as a hash) to the Serf App.
|
259
|
+
# NOTE: We should get an error message pushed to the error channel
|
260
|
+
# because no 'data' field was put in my_message as required
|
261
|
+
# And the Result should have a CaughtExceptionEvent.
|
262
|
+
logger.info "BEG MyMessage w/o Data"
|
263
|
+
results = app.call 'kind' => 'my_message'
|
264
|
+
logger.info "END MyMessage w/o Data: #{results.size} #{results.to_json}"
|
265
|
+
|
266
|
+
# Here is good result
|
267
|
+
logger.info "BEG MyMessage w/ Data"
|
268
|
+
results = app.call 'kind' => 'my_message', 'data' => '1'
|
269
|
+
logger.info "END MyMessage w/ Data: #{results.size} #{results.to_json}"
|
270
|
+
|
271
|
+
# Here is a result that will raise an error in foreground
|
272
|
+
# We should get two event messages in the results because we
|
273
|
+
# mounted two commands to the raise_error_message kind.
|
274
|
+
# Each shows errors being raised in two separate stages.
|
275
|
+
# 1. Error in creating the instance of the command.
|
276
|
+
# 2. Error when the command was executed by the foreground runner.
|
277
|
+
logger.info "BEG RaisesErrorMessage"
|
278
|
+
results = app.call 'kind' => 'raise_error_message', 'data' => '2'
|
279
|
+
logger.info "END RaisesErrorMessage: #{results.size} #{results.to_json}"
|
280
|
+
|
281
|
+
# This submission will be executed by THREE commands.
|
282
|
+
# One in the foreground, two in the background.
|
283
|
+
#
|
284
|
+
# The foreground results should be:
|
285
|
+
# * MessageAcceptedEvent
|
286
|
+
# * And return result of one command call
|
287
|
+
#
|
288
|
+
# The error channel should output an error from one background command.
|
289
|
+
logger.info "BEG OtherMessage"
|
290
|
+
results = app.call 'kind' => 'other_message', 'data' => '3'
|
291
|
+
logger.info "END OtherMessage: #{results.size} #{results.to_json}"
|
292
|
+
|
293
|
+
# This will match a regexp call
|
294
|
+
logger.info "BEG Regexp"
|
295
|
+
results = app.call 'kind' => 'events/my_event', 'data' => '4'
|
296
|
+
logger.info "END Regexp Results: #{results.size} #{results.to_json}"
|
297
|
+
|
298
|
+
begin
|
299
|
+
# Here, we're going to submit a message that we don't handle.
|
300
|
+
# By default, an exception will be raised.
|
301
|
+
app.call 'kind' => 'unhandled_message_kind'
|
302
|
+
rescue => e
|
303
|
+
logger.warn "Caught in Tick: #{e.inspect}"
|
304
|
+
end
|
305
|
+
logger.info "End Tick #{Thread.current.object_id}"
|
306
|
+
|
307
|
+
Example With EventMachine
|
308
|
+
=========================
|
309
|
+
|
310
|
+
# Require our libraries
|
311
|
+
require 'log4r'
|
312
|
+
require 'json'
|
313
|
+
|
314
|
+
require 'serf/builder'
|
315
|
+
require 'serf/command'
|
316
|
+
require 'serf/message'
|
317
|
+
require 'serf/middleware/uuid_tagger'
|
318
|
+
require 'serf/util/options_extraction'
|
319
|
+
|
320
|
+
# create a simple logger for this example
|
321
|
+
outputter = Log4r::FileOutputter.new(
|
322
|
+
'fileOutputter',
|
323
|
+
filename: 'log.txt')
|
324
|
+
['tick',
|
325
|
+
'serf',
|
326
|
+
'hndl',
|
327
|
+
'resp',
|
328
|
+
'errr'].each do |name|
|
329
|
+
logger = Log4r::Logger.new name
|
330
|
+
logger.outputters = outputter
|
331
|
+
end
|
332
|
+
|
333
|
+
# Helper class for this example to receive result or error messages
|
334
|
+
# and pipe it into our logger.
|
335
|
+
class MyChannel
|
336
|
+
def initialize(logger, error=false)
|
337
|
+
@logger = logger
|
338
|
+
@error = error
|
339
|
+
end
|
340
|
+
def push(message)
|
341
|
+
if @error
|
342
|
+
@logger.fatal "#{message}"
|
343
|
+
else
|
344
|
+
@logger.debug "#{message}"
|
345
|
+
end
|
182
346
|
end
|
347
|
+
end
|
183
348
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
349
|
+
# my_lib/my_message.rb
|
350
|
+
# This class is stripped down minimal functionality that
|
351
|
+
# ActiveModel or Aequitas/Virtus implements.
|
352
|
+
class MyMessage
|
353
|
+
include Serf::Util::OptionsExtraction
|
354
|
+
include Serf::Message
|
355
|
+
|
356
|
+
attr_accessor :data
|
357
|
+
|
358
|
+
def initialize(*args)
|
359
|
+
extract_options! args
|
360
|
+
self.data = opts :data
|
192
361
|
end
|
193
362
|
|
194
|
-
def
|
195
|
-
|
196
|
-
raise 'My Handler Runtime Error'
|
363
|
+
def attributes
|
364
|
+
{ 'data' => data }
|
197
365
|
end
|
198
366
|
|
199
|
-
def
|
200
|
-
|
201
|
-
nil
|
367
|
+
def valid?
|
368
|
+
!data.nil?
|
202
369
|
end
|
203
370
|
|
204
|
-
def
|
205
|
-
|
206
|
-
nil
|
371
|
+
def full_error_messages
|
372
|
+
'Data is blank' if data.nil?
|
207
373
|
end
|
208
374
|
end
|
209
375
|
|
210
|
-
# my_lib/
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
#
|
223
|
-
|
224
|
-
|
225
|
-
#
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
#
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
/^events\/.*$/ => 'my_handler#regexp_matched'
|
242
|
-
}
|
376
|
+
# my_lib/my_overloaded_command.rb
|
377
|
+
class MyOverloadedCommand
|
378
|
+
include Serf::Command
|
379
|
+
|
380
|
+
self.request_factory = MyMessage
|
381
|
+
|
382
|
+
def initialize(*args)
|
383
|
+
super
|
384
|
+
raise "Constructor Error: #{opts(:name)}" if opts :raises_in_new
|
385
|
+
end
|
386
|
+
|
387
|
+
def call
|
388
|
+
# Set our logger
|
389
|
+
logger = ::Log4r::Logger['hndl']
|
390
|
+
|
391
|
+
# Just our name to sort things out
|
392
|
+
name = opts! :name
|
393
|
+
logger.info "#{name}: #{request.to_json}"
|
394
|
+
|
395
|
+
raise "Forcing Error in #{name}" if opts(:raises, false)
|
396
|
+
|
397
|
+
# Do work Here...
|
398
|
+
# And return 0 or more messages as result. Nil is valid response.
|
399
|
+
return { kind: "#{name}_result", input: request.to_hash }
|
400
|
+
end
|
401
|
+
|
402
|
+
def inspect
|
403
|
+
"MyOverloadedCommand: #{opts(:name,'notnamed')}, #{request.to_json}"
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
243
407
|
|
244
408
|
# Create a new builder for this serf app.
|
245
409
|
builder = Serf::Builder.new do
|
246
410
|
# Include some middleware
|
247
411
|
use Serf::Middleware::UuidTagger
|
248
412
|
|
249
|
-
#
|
250
|
-
|
251
|
-
|
252
|
-
# Can define arguments to pass to the 'my_handler' initialize method.
|
253
|
-
handler 'my_handler', MyHandler.new(logger: ::Log4r::Logger['handler'])
|
254
|
-
message_parser 'my_parser', MyMessage
|
255
|
-
|
256
|
-
# Create result and error channels for the handler result messages.
|
257
|
-
error_channel MyChannel.new(::Log4r::Logger['error_channel'])
|
258
|
-
results_channel MyChannel.new(::Log4r::Logger['results_channel'])
|
413
|
+
# Create response and error channels for the handler result messages.
|
414
|
+
response_channel MyChannel.new(::Log4r::Logger['resp'])
|
415
|
+
error_channel MyChannel.new(::Log4r::Logger['errr'], true)
|
259
416
|
|
260
417
|
# We pass in a logger to our Serf code: Serfer and Runners.
|
261
418
|
logger ::Log4r::Logger['serf']
|
262
419
|
|
420
|
+
runner :direct
|
421
|
+
|
422
|
+
match 'my_message'
|
423
|
+
run MyOverloadedCommand, name: 'my_message_command'
|
424
|
+
|
425
|
+
match 'raise_error_message'
|
426
|
+
run MyOverloadedCommand, name: 'foreground_raises_error', raises: true
|
427
|
+
run MyOverloadedCommand, name: 'constructor_error', raises_in_new: true
|
428
|
+
|
429
|
+
match 'other_message'
|
430
|
+
run MyOverloadedCommand, name: 'foreground_other_message'
|
431
|
+
|
432
|
+
runner :event_machine
|
433
|
+
|
434
|
+
# This message kind is handled by multiple handlers.
|
435
|
+
match 'other_message'
|
436
|
+
run MyOverloadedCommand, name: 'background_other_message'
|
437
|
+
run MyOverloadedCommand, name: 'background_raises error', raises: true
|
438
|
+
|
439
|
+
match /^events\/.*$/
|
440
|
+
run MyOverloadedCommand, name: 'regexp_matched_command'
|
441
|
+
|
263
442
|
# Optionally define a not found handler...
|
264
443
|
# Defaults to raising an ArgumentError, 'Not Found'.
|
265
444
|
#not_found lambda {|x| puts x}
|
@@ -267,7 +446,7 @@ Example
|
|
267
446
|
app = builder.to_app
|
268
447
|
|
269
448
|
# Start event machine loop.
|
270
|
-
logger = ::Log4r::Logger['
|
449
|
+
logger = ::Log4r::Logger['tick']
|
271
450
|
EM.run do
|
272
451
|
# On the next tick
|
273
452
|
EM.next_tick do
|
@@ -277,28 +456,46 @@ Example
|
|
277
456
|
# NOTE: We should get an error message pushed to the error channel
|
278
457
|
# because no 'data' field was put in my_message as required
|
279
458
|
# And the Result should have a CaughtExceptionEvent.
|
280
|
-
|
281
|
-
|
459
|
+
logger.info "BEG MyMessage w/o Data"
|
460
|
+
results = app.call 'kind' => 'my_message'
|
461
|
+
logger.info "END MyMessage w/o Data: #{results.size} #{results.to_json}"
|
282
462
|
|
283
463
|
# Here is good result
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
#
|
289
|
-
#
|
290
|
-
# the
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
results = app.call
|
296
|
-
logger.info "
|
464
|
+
logger.info "BEG MyMessage w/ Data"
|
465
|
+
results = app.call 'kind' => 'my_message', 'data' => '1'
|
466
|
+
logger.info "END MyMessage w/ Data: #{results.size} #{results.to_json}"
|
467
|
+
|
468
|
+
# Here is a result that will raise an error in foreground
|
469
|
+
# We should get two event messages in the results because we
|
470
|
+
# mounted two commands to the raise_error_message kind.
|
471
|
+
# Each shows errors being raised in two separate stages.
|
472
|
+
# 1. Error in creating the instance of the command.
|
473
|
+
# 2. Error when the command was executed by the foreground runner.
|
474
|
+
logger.info "BEG RaisesErrorMessage"
|
475
|
+
results = app.call 'kind' => 'raise_error_message', 'data' => '2'
|
476
|
+
logger.info "END RaisesErrorMessage: #{results.size} #{results.to_json}"
|
477
|
+
|
478
|
+
# This submission will be executed by THREE commands.
|
479
|
+
# One in the foreground, two in the background.
|
480
|
+
#
|
481
|
+
# The foreground results should be:
|
482
|
+
# * MessageAcceptedEvent
|
483
|
+
# * And return result of one command call
|
484
|
+
#
|
485
|
+
# The error channel should output an error from one background command.
|
486
|
+
logger.info "BEG OtherMessage"
|
487
|
+
results = app.call 'kind' => 'other_message', 'data' => '3'
|
488
|
+
logger.info "END OtherMessage: #{results.size} #{results.to_json}"
|
489
|
+
|
490
|
+
# This will match a regexp call
|
491
|
+
logger.info "BEG Regexp"
|
492
|
+
results = app.call 'kind' => 'events/my_event', 'data' => '4'
|
493
|
+
logger.info "END Regexp Results: #{results.size} #{results.to_json}"
|
297
494
|
|
298
495
|
begin
|
299
496
|
# Here, we're going to submit a message that we don't handle.
|
300
497
|
# By default, an exception will be raised.
|
301
|
-
app.call
|
498
|
+
app.call 'kind' => 'unhandled_message_kind'
|
302
499
|
rescue => e
|
303
500
|
logger.warn "Caught in Tick: #{e.inspect}"
|
304
501
|
end
|