serf 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +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
|