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 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 'rspec', '~> 2.3.0'
16
- gem 'yard', '~> 0.6.0'
17
- gem 'bundler', '~> 1.0.0'
18
- gem 'jeweler', '~> 1.6.4'
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.0)
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.6.4)
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.4)
22
+ multi_json (1.1.0)
17
23
  rake (0.9.2.2)
18
- rspec (2.3.0)
19
- rspec-core (~> 2.3.0)
20
- rspec-expectations (~> 2.3.0)
21
- rspec-mocks (~> 2.3.0)
22
- rspec-core (2.3.1)
23
- rspec-expectations (2.3.0)
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.3.0)
26
- simplecov (0.5.4)
27
- multi_json (~> 1.0.3)
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.6.8)
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.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.6.4)
50
+ jeweler (~> 1.8.3)
51
+ log4r (~> 1.1.10)
42
52
  msgpack (>= 0.4.6)
43
- rspec (~> 2.3.0)
53
+ rspec (~> 2.8.0)
44
54
  simplecov
45
55
  uuidtools (>= 2.1.2)
46
- yard (~> 0.6.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
- The Serf App, as configured by registered manifests, will:
41
- 1. route the ENV to the proper handler
42
- 2. run the handler in blocking or non-blocking mode.
43
- a. The handler's serf call method will parse the ENV into a message object
44
- if the handler registered a Message class.
45
- 3. publish the handler's results to results or error channels.
46
- a. These channels are normally message queuing channels.
47
- b. We only require the channel instance to respond to the 'publish'
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. Handler methods MUST receive a message as either as an options hash
69
- (with symbolized keys) or an instance of a declared Message.
70
- 4. Handler methods MUST return zero or more messages.
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 published as
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
- ['top_level',
129
+ ['tick',
127
130
  'serf',
128
- 'handler',
129
- 'results_channel',
130
- 'error_channel'].each do |name|
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 publish(message)
142
- @logger.info "#{message}"
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
- validates_presence_of :data
154
-
155
- def initialize(options={})
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/my_handler.rb
167
- class MyHandler
181
+ # my_lib/my_overloaded_command.rb
182
+ class MyOverloadedCommand
183
+ include Serf::Command
168
184
 
169
- def initialize(options={})
170
- @logger = options[:logger]
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 submit_my_message(message)
174
- @logger.info "In Submit My Message: #{message.to_json}"
175
- # Validate message because we have implement my_message with it.
176
- unless message.valid?
177
- raise ArgumentError, message.errors.full_messages.join(',')
178
- end
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: 'my_message_results' }
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
- def submit_other_message(message={})
185
- # The message here is the ENV hash because we didn't declare
186
- # an :as option with `receives`.
187
- @logger.info "In Submit OtherMessage: #{message.inspect.to_s}"
188
- return [
189
- { kind: 'other_message_result1' },
190
- { kind: 'other_message_result2' }
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 raises_error(message={})
195
- @logger.info 'In Raises Error, about to raise error'
196
- raise 'My Handler Runtime Error'
363
+ def attributes
364
+ { 'data' => data }
197
365
  end
198
366
 
199
- def regexp_matched(message={})
200
- @logger.info "RegExp Matched #{message.inspect}"
201
- nil
367
+ def valid?
368
+ !data.nil?
202
369
  end
203
370
 
204
- def call(message={})
205
- @logger.info "A message matched an empty action part in the target"
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/routes.rb
211
- ROUTES = {
212
- # Declare a matcher and a list of routes to endpoints.
213
- # We can declare a single route.
214
- 'my_message' => {
215
- # Declares which handler and action (method) of the handler
216
- # to call. The handler part is the name of the handler used
217
- # to register an actual handler object.
218
- target: 'my_handler#submit_my_message',
219
-
220
- # Define a parser that will build up a message object.
221
- # Default: nil, no parsing done.
222
- # Or name of registered parser to use.
223
- message_parser: 'my_parser',
224
-
225
- # Default is process in foreground.
226
- #background: false
227
- },
228
- # This message kind is handled by multiple handlers.
229
- 'other_message' => [{
230
- target: 'my_handler#submit_other_message',
231
- background: true
232
- }, {
233
- target: 'my_handler#raises_error',
234
- background: true
235
- },
236
- # This is just a string route defining the target, nothing else.
237
- # The handler is 'my_handler' and an empty (or missing) action
238
- # part defaults to the 'call' method of the handler.
239
- 'my_handler'
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
- # Registers routes from different service libary manifests.
250
- routes ROUTES
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['top_level']
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
- results = app.call('kind' => 'my_message')
281
- logger.info "In Tick, MyMessage Results: #{results.inspect}"
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
- results = app.call('kind' => 'my_message', 'data' => '1234')
285
- logger.info "In Tick, MyMessage Results: #{results.inspect}"
286
-
287
- # This will submit 'other_message' to be handled in foreground
288
- # Because we declared the 'other_message' kind to be handled async
289
- # in each route config, we should get a MessageAcceptedEvent as
290
- # the results.
291
- results = app.call('kind' => 'other_message')
292
- logger.info "In Tick, OtherMessage Results: #{results.inspect}"
293
-
294
- # This will match a regexp
295
- results = app.call('kind' => 'events/my_event')
296
- logger.info "In Tick, Regexp Results: #{results.inspect}"
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('kind' => 'unhandled_message_kind')
498
+ app.call 'kind' => 'unhandled_message_kind'
302
499
  rescue => e
303
500
  logger.warn "Caught in Tick: #{e.inspect}"
304
501
  end