combi 0.1.0 → 0.2.0.pre

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 28565637d9bdb76e4bd9f256be6af0c89900ec25
4
- data.tar.gz: 411ec39864499243853f42677ec76f3708c8c630
3
+ metadata.gz: ac4f813928579df4ccf43c961482b6291268d32f
4
+ data.tar.gz: b27cb7008850b414d42d45b1b8f7d177d59e32f9
5
5
  SHA512:
6
- metadata.gz: 8a353643e81e81912a88a8b756cf29a12ba19982e6db5e09c34fd88d153041bf14434a6d609196c202336d99671f08f3bf49b9c1fde43b1f656c04b2b66b9fa2
7
- data.tar.gz: 98151bf6b13683a2992a3e0078bfea21f136377fd33971bb5190893aaac1e3e63b1ff9940516ae2558959229eb36e2d668890630b728850424e1467cf0e27d1a
6
+ metadata.gz: e0e6d420d29afb6f115273e2c17d9ef24b2ddec8084e53df4b3e10c9ab58c221ae966e87b495ba93c22095b188bf78f3ae0247d4d071141801ede2f56a3de795
7
+ data.tar.gz: 8699fe145b26e9d58ae734fc8d1b9c44d911ce46df719d61036e01cf60db7d402986befd22723fe91689ba0c780cfa83e162cd7966512de8a3065cfe08d509ac
@@ -1,17 +1,15 @@
1
1
  require "combi/service"
2
2
  require_relative 'correlation'
3
3
  require 'yajl'
4
- require 'yajl/json_gem' # for object.to_json, JSON.parse, etc...
5
4
 
6
5
  module Combi
7
6
  class Bus
8
- attr_reader :services
9
-
10
7
  RPC_DEFAULT_TIMEOUT = 1
8
+ attr_reader :routes
11
9
 
12
10
  def initialize(options)
13
11
  @options = options
14
- @services = []
12
+ @routes = {}
15
13
  post_initialize
16
14
  end
17
15
 
@@ -19,9 +17,14 @@ module Combi
19
17
  end
20
18
 
21
19
  def add_service(service_definition, options = {})
22
- service = make_service_instance(service_definition)
23
- service.setup(self, options[:context])
24
- @services << service
20
+ service_instance = make_service_instance(service_definition)
21
+ service_instance.actions.each do |service_name|
22
+ self.add_routes_for(service_name, service_instance)
23
+ end
24
+ service_instance.fast_actions.each do |service_name|
25
+ self.add_routes_for(service_name, service_instance, fast: true)
26
+ end
27
+ service_instance.setup(self, options[:context])
25
28
  end
26
29
 
27
30
  def start!
@@ -74,23 +77,94 @@ module Combi
74
77
  service_class = Class.new do
75
78
  include Combi::Service
76
79
  include a_module
80
+
81
+ define_method :service_module do
82
+ a_module
83
+ end
84
+ protected :service_module
85
+
86
+ def remote_methods
87
+ @_REMOTE_METHODS ||= service_module.public_instance_methods(false) - [:actions]
88
+ end
89
+ def to_s
90
+ @_TO_S ||= "#{service_module.name || 'Annonymous'}"
91
+ end
92
+ end
93
+ service_class.new
94
+ end
95
+
96
+ def add_routes_for(service_name, service_instance, options = {})
97
+ service_instance.remote_methods.each do |method|
98
+ add_route_for(service_name, method, service_instance, options)
99
+ end
100
+ end
101
+
102
+ def add_route_for(service_name, action_name, service_instance, options = {})
103
+ path = [service_name, action_name].join('/')
104
+ puts "New route: #{path} :: #{service_instance}"
105
+ @routes[path] = {service_instance: service_instance, options: options}
106
+ end
107
+
108
+ # Funny name of a exception used to signal that the requested
109
+ # combination of service and method can not be found.
110
+ #
111
+ # Looked much funnier the first time... (@amuino)
112
+ class UnknownStop < RuntimeError
113
+ end
114
+
115
+ def resolve_route(service_name, kind)
116
+ path = [service_name, kind].join('/')
117
+ handler = @routes[path]
118
+ if handler
119
+ return service_instance = handler[:service_instance]
120
+ else
121
+ log "[WARNING] Service Path #{path} not found"
122
+ log "[WARNING] routes: #{@routes.keys.inspect}"
123
+ raise UnknownStop.new(path)
77
124
  end
78
- service = service_class.new
79
125
  end
80
126
 
81
- def invoke_service(service_instance, action, params)
127
+ def invoke_service(service_name, action, params)
128
+ t0 = Time.now
129
+ path = "#{service_name}/#{action}?#{params.inspect}"
130
+ service_instance = resolve_route(service_name.to_s, action)
82
131
  # convert keys to symbols in-place
83
132
  params.keys.each {|key| params[key.to_sym] = params.delete(key) }
84
- service_instance.send(action, params)
85
- rescue => e
133
+ deferrable = sync_to_async service_instance.send(action, params)
134
+ deferrable.callback &log_service_invocation(true, t0, path)
135
+ deferrable.errback &log_service_invocation(false, t0, path)
136
+ deferrable
137
+ rescue StandardError => e
86
138
  # TODO: report in a more effective way (I will not read server logs to find this)
87
139
  require 'yaml'
88
140
  puts " *** ERROR INVOKING SERVICE ***"
89
141
  puts " - #{e.inspect}"
90
- puts " - #{service_instance.class.ancestors.join ' > '}"
142
+ puts " - #{service_name} #{service_instance.class.ancestors.join ' > '}"
91
143
  puts " - #{action}"
92
- puts " - #{params.to_yaml}"
93
- raise e
144
+ puts " - #{params.to_yaml.split("\n").join("\n\t")}"
145
+ # FIXME: strings because is what in_process tests expects
146
+ return sync_to_async error: { klass: e.class.name, message: e.message, backtrace: e.backtrace }
147
+ end
148
+
149
+ def sync_to_async(response)
150
+ return response if response.respond_to? :succeed # already a deferrable
151
+ deferrable = EM::DefaultDeferrable.new
152
+ if response.respond_to?(:keys) and response[:error]
153
+ deferrable.fail response[:error]
154
+ else
155
+ deferrable.succeed response
156
+ end
157
+ return deferrable
158
+ end
159
+
160
+ def log_service_invocation(success, t0, path)
161
+ Proc.new do |response|
162
+ result = success ? 'OK' : 'KO'
163
+ time = '%10.6fs' % (Time.now - t0)
164
+ base_msg = "#{result}\t#{time}\t#{path}"
165
+ base_msg += "\t#{response.inspect}" if success == false
166
+ puts base_msg
167
+ end
94
168
  end
95
169
 
96
170
  end
@@ -14,9 +14,9 @@ module Combi
14
14
  def on_message(request)
15
15
  path = request.path.split('/')
16
16
  message = {
17
- "service" => path[1],
18
- "kind" => path[2],
19
- "payload" => JSON.parse(request.body)
17
+ service_name: path[1],
18
+ kind: path[2],
19
+ payload: Yajl::Parser.parse(request.body, symbolize_keys: true)
20
20
  }
21
21
  @bus.on_message(message)
22
22
  end
@@ -46,46 +46,21 @@ module Combi
46
46
  @machine.on_message Rack::Request.new(env)
47
47
  end
48
48
 
49
- def on_message(message)
50
- service_name = message['service']
51
- handler = handlers[service_name.to_s]
52
- if handler
53
- service_instance = handler[:service_instance]
54
- kind = message['kind']
55
- if service_instance.respond_to? kind
56
- message['payload'] ||= {}
57
- begin
58
- response = invoke_service(service_instance, kind, message['payload'])
59
- rescue RuntimeError => e
60
- response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
61
- end
62
- {result: 'ok', response: response}
63
- else
64
- {result: 'error', response: {error: { klass: 'unknown action', message: kind } } }
65
- end
66
- else
67
- {result: 'error', response: {error: 'unknown service'}}
68
- end
69
- end
70
-
71
- def respond_to(service_instance, action, options = {})
72
- handlers[action.to_s] = {service_instance: service_instance, options: options}
73
- end
74
-
75
- def handlers
76
- @handlers ||= {}
49
+ def on_message(service_name:, kind:, payload: {})
50
+ invoke_service(service_name, kind, payload)
77
51
  end
78
52
 
79
53
  def request(name, kind, message, options = {})
80
54
  options[:timeout] ||= RPC_DEFAULT_TIMEOUT
81
55
 
82
56
  correlation_id = Combi::Correlation.generate
83
- waiter = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
57
+ waiter = @response_store.wait_for(correlation_id, options[:timeout])
84
58
  url = "#{@options[:remote_api]}#{name}/#{kind}"
85
- request_async = EventMachine::HttpRequest.new(url, connection_timeout: options[:timeout]).post(body: message.to_json)
59
+ message_json = Yajl::Encoder.encode(message)
60
+ request_async = EventMachine::HttpRequest.new(url, connection_timeout: options[:timeout]).post(body: message_json)
86
61
  request_async.callback do |r|
87
- parsed = JSON.parse(r.response)
88
- waiter.succeed(parsed['response'])
62
+ parsed = Yajl::Parser.parse(r.response, symbolize_keys: true)
63
+ waiter.succeed(parsed)
89
64
  end
90
65
  request_async.errback do |x|
91
66
  waiter.fail(Timeout::Error.new)
@@ -3,53 +3,24 @@ require 'combi/buses/bus'
3
3
  module Combi
4
4
  class InProcess < Bus
5
5
 
6
- def request(handler_name, kind, message, options = {})
6
+ def request(service_name, kind, message, options = {})
7
7
  options[:timeout] ||= RPC_DEFAULT_TIMEOUT
8
- handler = memory_handlers[handler_name.to_s]
9
8
  waiter = EventMachine::DefaultDeferrable.new
10
- if handler.nil?
11
- waiter.fail('error' => 'unknown service')
12
- else
13
- service_instance = handler[:service_instance]
14
- message = JSON.parse(message.to_json)
15
- if service_instance.respond_to?(kind)
16
- waiter.timeout(options[:timeout], 'error' => 'Timeout::Error')
17
- begin
18
- Timeout.timeout(options[:timeout]) do
19
- response = invoke_service(service_instance, kind, message)
20
- if response.respond_to? :succeed
21
- response.callback do |service_response|
22
- log "responding with deferred response: #{service_response.inspect[0..500]}"
23
- waiter.succeed service_response
24
- end
25
- response.errback do |service_response|
26
- failure_response = { 'error' => service_response }
27
- log "responding with deferred failure: #{service_response.inspect[0..500]}"
28
- waiter.fail(failure_response)
29
- end
30
- else
31
- waiter.succeed response
32
- end
33
- end
34
- rescue Timeout::Error => e
35
- waiter.fail 'error' => 'Timeout::Error'
36
- rescue RuntimeError => e
37
- waiter.fail 'error' => {'klass' => e.class.name, 'message' => e.message, 'backtrace' => e.backtrace}
9
+ begin
10
+ Timeout.timeout(options[:timeout]) do
11
+ message = Yajl::Parser.parse(Yajl::Encoder.encode(message), symbolize_keys: true)
12
+ response = invoke_service(service_name, kind, message)
13
+ response.callback do |service_response|
14
+ waiter.succeed service_response
15
+ end
16
+ response.errback do |service_response|
17
+ waiter.fail error: service_response
38
18
  end
39
- else
40
- waiter.fail('error' => { 'klass' => 'unknown action', 'message' => kind.to_s })
41
19
  end
20
+ rescue Timeout::Error => e
21
+ waiter.fail error: 'Timeout::Error'
42
22
  end
43
23
  waiter
44
24
  end
45
-
46
- def respond_to(service_instance, action, options = {})
47
- memory_handlers[action.to_s] = {service_instance: service_instance, options: options}
48
- end
49
-
50
- def memory_handlers
51
- @memory_handlers ||= {}
52
- end
53
-
54
25
  end
55
26
  end
@@ -25,8 +25,13 @@ module Combi
25
25
  end
26
26
  end
27
27
 
28
- def respond_to(service_instance, action, options = {})
29
- log "registering #{action}"
28
+ def add_routes_for(service_name, service_instance, options = {})
29
+ create_queue_for_service(service_name, options)
30
+ super
31
+ end
32
+
33
+ def create_queue_for_service(service_name, options = {})
34
+ log "creating queue #{service_name}"
30
35
  queue_options = {}
31
36
  subscription_options = {}
32
37
  if options[:fast] == true
@@ -35,10 +40,10 @@ module Combi
35
40
  subscription_options[:ack] = true
36
41
  end
37
42
  queue_service.ready do
38
- queue_service.queue(action.to_s, queue_options) do |queue|
39
- log "subscribing to queue #{action.to_s} with options #{queue_options}"
43
+ queue_service.queue(service_name.to_s, queue_options) do |queue|
44
+ log "subscribing to queue #{service_name.to_s} with options #{queue_options}"
40
45
  queue.subscribe(subscription_options) do |delivery_info, payload|
41
- respond service_instance, payload, delivery_info
46
+ respond service_name, payload, delivery_info
42
47
  queue_service.acknowledge delivery_info unless options[:fast] == true
43
48
  end
44
49
  end
@@ -51,7 +56,7 @@ module Combi
51
56
  options[:routing_key] = name.to_s
52
57
  correlation_id = Combi::Correlation.generate
53
58
  options[:correlation_id] = correlation_id
54
- waiter = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
59
+ waiter = @response_store.wait_for(correlation_id, options[:timeout])
55
60
  queue_service.next_ready_only do
56
61
  log "Making request: #{name}.#{kind} #{message.inspect[0..500]}\t|| #{options.inspect[0..500]}"
57
62
  queue_service.call(kind, message, options)
@@ -59,36 +64,18 @@ module Combi
59
64
  waiter
60
65
  end
61
66
 
62
- def respond(service_instance, request, delivery_info)
63
- message = JSON.parse request
64
- kind = message['kind']
65
- payload = message['payload']
66
- options = message['options']
67
- if service_instance.respond_to?(kind)
68
- log "generating response for #{service_instance.class}#{service_instance.actions.inspect}.#{kind} #{payload.inspect[0..500]}"
69
- begin
70
- response = invoke_service(service_instance, kind, payload)
71
- rescue RuntimeError => e
72
- response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
73
- end
74
- else
75
- log "Service instance does not respond to #{kind}: #{service_instance.inspect}"
76
- response = {error: { klass: 'unknown action', message: kind } }
67
+ def respond(service_name, request, delivery_info)
68
+ message = Yajl::Parser.parse request, symbolize_keys: true
69
+ kind = message[:kind]
70
+ payload = message[:payload]
71
+ options = message[:options]
72
+ response = invoke_service(service_name, kind, payload)
73
+ response.callback do |service_response|
74
+ queue_service.respond service_response, delivery_info
77
75
  end
78
- if response.respond_to? :succeed
79
- log "response is deferred"
80
- response.callback do |service_response|
81
- log "responding with deferred answer: #{service_response.inspect[0..500]}"
82
- queue_service.respond(service_response.to_json, delivery_info)
83
- end
84
- response.errback do |service_response|
85
- failure_response = { error: service_response }
86
- log "responding with deferred failure: #{service_response.inspect[0..500]}"
87
- queue_service.respond(failure_response.to_json, delivery_info)
88
- end
89
- else
90
- log "responding with inmediate answer: #{response.inspect[0..500]}"
91
- queue_service.respond(response.to_json, delivery_info)
76
+ response.errback do |service_response|
77
+ failure_response = { error: service_response }
78
+ queue_service.respond failure_response, delivery_info
92
79
  end
93
80
  end
94
81
 
@@ -23,8 +23,8 @@ module Combi
23
23
  end
24
24
 
25
25
  def on_message(ws, session, raw_message)
26
- @bus.log "ON MESSAGE #{raw_message[0..500]}"
27
- message = JSON.parse(raw_message)
26
+ @bus.log "WS SERVER ON MESSAGE #{raw_message[0..500]}"
27
+ message = Yajl::Parser.parse raw_message, symbolize_keys: true
28
28
  @bus.on_message(ws, message, session)
29
29
  end
30
30
 
@@ -73,7 +73,7 @@ module Combi
73
73
 
74
74
  ws.on :message do |event|
75
75
  @bus.log "ON MESSAGE: #{event.data[0..500]}"
76
- message = JSON.parse(event.data)
76
+ message = Yajl::Parser.parse event.data, symbolize_keys: true
77
77
  @bus.on_message(ws, message)
78
78
  end
79
79
 
@@ -96,11 +96,10 @@ module Combi
96
96
 
97
97
  end
98
98
 
99
- attr_reader :handlers, :ready
99
+ attr_reader :ready
100
100
 
101
101
  def initialize(options)
102
102
  super
103
- @handlers = {}
104
103
  end
105
104
 
106
105
  def post_initialize
@@ -161,80 +160,52 @@ module Combi
161
160
  end
162
161
 
163
162
  def on_message(ws, message, session = nil)
164
- if message['correlation_id'] && message.has_key?('result')
163
+ if message[:correlation_id] && message.has_key?(:result)
165
164
  @response_store.handle_rpc_response(message)
166
- log "Stored message with correlation_id #{message['correlation_id']} - #{message.inspect[0..500]}"
165
+ log "Handled response with correlation_id #{message[:correlation_id]} - #{message.inspect[0..500]}"
167
166
  return
168
167
  end
169
- service_name = message['service']
170
- kind = message['kind']
171
- payload = message['payload'] || {}
172
- payload['session'] = session
168
+ service_name = message[:service]
169
+ kind = message[:kind]
170
+ payload = message[:payload] || {}
171
+ payload[:session] = session
173
172
  begin
174
- response = lookup_and_invoke_service(service_name, kind, payload)
173
+ response = invoke_service(service_name, kind, payload)
175
174
  rescue RuntimeError => e
176
175
  response = {error: {klass: e.class.name, message: e.message, backtrace: e.backtrace } }
177
176
  end
178
177
 
179
- msg = {result: 'ok', correlation_id: message['correlation_id']}
178
+ msg = {result: 'ok', correlation_id: message[:correlation_id]}
180
179
 
181
- if response.respond_to? :succeed
182
- log "response is deferred"
183
- response.callback do |service_response|
184
- log "responding with deferred answer: #{service_response.inspect[0..500]}"
185
- msg[:response] = service_response.to_json
186
- ws.send(msg.to_json)
187
- end
188
- response.errback do |service_response|
189
- log "responding with deferred error: #{service_response.inspect[0..500]}"
190
- error_response = { error: service_response }
191
- msg[:response] = error_response.to_json
192
- ws.send(msg.to_json)
193
- end
194
- else
195
- log "responding with inmediate answer: #{response.inspect[0..500]}"
196
- msg[:response] = response.to_json
197
- ws.send(msg.to_json)
180
+ response.callback do |service_response|
181
+ msg[:response] = service_response
182
+ serialized = Yajl::Encoder.encode msg
183
+ ws.send serialized
198
184
  end
199
- end
200
-
201
- def lookup_and_invoke_service(service_name, kind, payload)
202
- handler = handlers[service_name.to_s]
203
- if handler
204
- service_instance = handler[:service_instance]
205
- if service_instance.respond_to? kind
206
- response = invoke_service(service_instance, kind, payload)
207
- else
208
- log "[WARNING] Service #{service_name}(#{service_instance.class.name}) does not respond to message #{kind}"
209
- response = {error: { klass: 'unknown action', message: kind } }
210
- end
211
- else
212
- log "[WARNING] Service #{service_name} not found"
213
- log "[WARNING] handlers: #{handlers.keys.inspect}"
214
- response = {error: 'unknown service'}
185
+ response.errback do |service_response|
186
+ msg[:response] = {error: service_response}
187
+ serialized = Yajl::Encoder.encode msg
188
+ ws.send serialized
215
189
  end
216
190
  end
217
191
 
218
- def respond_to(service_instance, action, options = {})
219
- log "registering #{action}"
220
- handlers[action.to_s] = {service_instance: service_instance, options: options}
221
- log "handlers: #{handlers.keys.inspect}"
222
- end
223
-
224
192
  def request(name, kind, message, options = {})
225
193
  options[:timeout] ||= RPC_DEFAULT_TIMEOUT
194
+ correlation_id = Combi::Correlation.generate
226
195
  msg = {
227
196
  service: name,
228
197
  kind: kind,
229
- payload: message
198
+ payload: message,
199
+ correlation_id: correlation_id
230
200
  }
231
- correlation_id = Combi::Correlation.generate
232
- msg[:correlation_id] = correlation_id
233
- waiter = EventedWaiter.wait_for(correlation_id, @response_store, options[:timeout])
201
+ waiter = @response_store.wait_for(correlation_id, options[:timeout])
234
202
  @ready.callback do |r|
235
203
  web_socket = @machine.ws || options[:ws]
236
- log "sending request #{msg.inspect}"
237
- web_socket.send msg.to_json unless web_socket.nil?
204
+ unless web_socket.nil?
205
+ serialized = Yajl::Encoder.encode msg
206
+ log "sending request #{serialized}"
207
+ web_socket.send serialized
208
+ end
238
209
  end
239
210
  waiter
240
211
  end
@@ -47,7 +47,7 @@ module Combi
47
47
  end
48
48
 
49
49
  def connect(config, &after_connect)
50
- puts "[INFO] trying to connect to queue"
50
+ puts "[INFO] trying to connect to queue server"
51
51
  @amqp_conn = AMQP.connect(config) do |connection, open_ok|
52
52
  @channel = AMQP::Channel.new @amqp_conn
53
53
  @channel.auto_recovery = true
@@ -94,8 +94,8 @@ module Combi
94
94
  end
95
95
 
96
96
  def respond(response, delivery_info)
97
- response = response.call if response.respond_to? :call
98
- publish response, routing_key: delivery_info.reply_to, correlation_id: delivery_info.correlation_id
97
+ serialized = Yajl::Encoder.encode response
98
+ publish serialized, routing_key: delivery_info.reply_to, correlation_id: delivery_info.correlation_id
99
99
  end
100
100
 
101
101
  def create_rpc_queue
@@ -103,9 +103,10 @@ module Combi
103
103
  @rpc_queue = queue('', exclusive: true, auto_delete: true) do |rpc_queue|
104
104
  log "\tRPC QUEUE: #{@rpc_queue.name}"
105
105
  rpc_queue.subscribe do |metadata, response|
106
+ parsed_response = Yajl::Parser.parse response, symbolize_keys: true
106
107
  message = {
107
- 'correlation_id' => metadata.correlation_id,
108
- 'response' => response
108
+ correlation_id: metadata.correlation_id,
109
+ response: parsed_response
109
110
  }
110
111
  rpc_callback.call(message) unless rpc_callback.nil?
111
112
  end
@@ -119,12 +120,8 @@ module Combi
119
120
  options[:expiration] = ((options[:timeout] || RPC_DEFAULT_TIMEOUT) * 1000).to_i
120
121
  options[:routing_key] ||= 'rcalls_queue'
121
122
  options[:reply_to] ||= @rpc_queue.name
122
- request = {
123
- kind: kind,
124
- payload: message,
125
- options: {}
126
- }
127
- publish(request.to_json, options)
123
+ request = Yajl::Encoder.encode kind: kind, payload: message, options: {}
124
+ publish request, options
128
125
  end
129
126
 
130
127
  end
@@ -6,49 +6,36 @@ module Combi
6
6
  @waiters = {}
7
7
  end
8
8
 
9
- def add_waiter(key, waiter)
10
- @waiters[key] = waiter
11
- waiter.callback { |r| finish key }
12
- waiter.errback { |r| finish key }
9
+ # Returns an EM::Deferrable
10
+ def wait_for(correlation_id, timeout)
11
+ waiter = EM::DefaultDeferrable.new
12
+ add_waiter correlation_id, waiter
13
+ waiter.timeout timeout, error: 'Timeout::Error'
13
14
  end
14
15
 
15
16
  def handle_rpc_response(response)
16
- correlation_id = response['correlation_id']
17
+ correlation_id = response[:correlation_id]
17
18
  waiter = @waiters[correlation_id]
18
19
  return unless waiter
19
- response = JSON.parse response['response']
20
- if response.respond_to?(:keys) and response['error']
21
- waiter.fail(response)
20
+ response = response[:response] #Yajl::Parser.parse response[:response], sybolize_keys: true
21
+ if response.respond_to?(:keys) and response[:error]
22
+ waiter.fail response
22
23
  else
23
- waiter.succeed(response)
24
+ waiter.succeed response
24
25
  end
25
26
  end
26
27
 
27
- def finish(key)
28
- @waiters.delete key
29
- end
30
- end
28
+ protected
31
29
 
32
- class EventedWaiter
33
- include EM::Deferrable
34
- def self.log(message)
35
- return unless @debug_mode ||= ENV['DEBUG'] == 'true'
36
- puts "#{Time.now.to_f} #{self.name} #{message}"
37
- end
38
-
39
- def self.wait_for(key, response_store, timeout)
40
- t1 = Time.now
41
- log "started waiting for #{key}"
42
- waiter = new(key, response_store, timeout)
43
- response_store.add_waiter(key, waiter)
44
- waiter.callback {|r| log "success waiting for #{key}: #{Time.now.to_f - t1.to_f}s" }
45
- waiter.errback {|r| log "failed waiting for #{key}: #{Time.now.to_f - t1.to_f}s, #{r.inspect[0..500]}" }
30
+ def add_waiter(correlation_id, waiter)
31
+ @waiters[correlation_id] = waiter
32
+ waiter.callback { |r| remove_waiter correlation_id }
33
+ waiter.errback { |r| remove_waiter correlation_id }
46
34
  waiter
47
35
  end
48
36
 
49
- def initialize(key, response_store, timeout)
50
- self.timeout(timeout, 'error' => 'Timeout::Error')
37
+ def remove_waiter(correlation_id)
38
+ @waiters.delete correlation_id
51
39
  end
52
-
53
40
  end
54
41
  end
@@ -6,7 +6,6 @@ module Combi
6
6
  context[:service_bus] = service_bus
7
7
  setup_context(context)
8
8
  setup_services
9
- register_actions
10
9
  end
11
10
 
12
11
  def setup_context(context)
@@ -21,15 +20,6 @@ module Combi
21
20
  def setup_services
22
21
  end
23
22
 
24
- def register_actions
25
- actions.each do |handler|
26
- service_bus.respond_to(self, handler)
27
- end
28
- fast_actions.each do |handler|
29
- service_bus.respond_to(self, handler, fast: true)
30
- end
31
- end
32
-
33
23
  def no_response
34
24
  nil
35
25
  end
@@ -46,6 +36,10 @@ module Combi
46
36
  []
47
37
  end
48
38
 
39
+ def remote_methods
40
+ @_REMOTE_METHODS ||= public_methods(false) - Combi::Service.public_instance_methods
41
+ end
42
+
49
43
  def service_bus
50
44
  @service_bus
51
45
  end
@@ -55,5 +49,9 @@ module Combi
55
49
  yield block if block_given?
56
50
  end
57
51
 
52
+ def to_s
53
+ @_TO_S ||= "#{self.class.name}"
54
+ end
55
+
58
56
  end
59
57
  end
@@ -1,3 +1,3 @@
1
1
  module Combi
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0.pre'
3
3
  end
@@ -36,7 +36,7 @@ describe "in a multi bus environment" do
36
36
  defer.fail('unknown error')
37
37
  end
38
38
  else
39
- if service_result.respond_to?(:keys) && service_result['error']
39
+ if service_result.respond_to?(:keys) && service_result[:error]
40
40
  defer.fail('other service failed')
41
41
  else
42
42
  defer.succeed(service_result*2)
@@ -173,7 +173,7 @@ describe "in a multi bus environment" do
173
173
  prepare
174
174
  EM.synchrony do
175
175
  service_result = EM::Synchrony.sync main_bus_consumer.request(:say_hello_if_you_can, :do_it, params, options)
176
- service_result['error']['message'].should eq "I can't talk"
176
+ service_result[:error][:message].should eq "I can't talk"
177
177
  done
178
178
  finalize
179
179
  end
@@ -191,7 +191,7 @@ describe "in a multi bus environment" do
191
191
  EM.synchrony do
192
192
  params.merge!('service_name' => 'say_hello_if_you_can')
193
193
  service_result = EM::Synchrony.sync main_bus_consumer.request(:repeat_with_me, :do_it, params, options)
194
- service_result['error'].should eq 'other service failed'
194
+ service_result[:error].should eq 'other service failed'
195
195
  done
196
196
  finalize
197
197
  end
@@ -1,22 +1,40 @@
1
1
  require 'spec_helper'
2
+ require 'combi/buses/bus'
2
3
 
3
4
  describe 'Combi::Bus' do
4
5
  context 'registers services' do
5
- Given(:subject) { Combi::Bus.new({}) }
6
+ Given(:bus) { Combi::Bus.new({}) }
6
7
 
7
8
  context 'via instances' do
8
- Given(:service_class) { Class.new{include Combi::Service} }
9
+ Given(:service_class) do
10
+ Class.new do
11
+ include Combi::Service
12
+ def actions; ['class']; end
13
+ def remote; end
14
+ end
15
+ end
9
16
  Given(:service_definition) { service_class.new }
10
- When { subject.add_service service_definition }
11
- Then { subject.services == [service_definition] }
17
+ Given(:path) { 'class/remote' }
18
+ When { bus.add_service service_definition }
19
+ Then { bus.routes.keys.length == 1 }
20
+ And { bus.routes[path][:service_instance] == service_definition }
12
21
  end
13
22
 
14
23
  context 'via modules' do
15
- Given(:service_definition) { Module.new }
16
- When { subject.add_service service_definition }
17
- Then { subject.services.length == 1 }
18
- And { Combi::Service === subject.services[0] }
19
- And { service_definition === subject.services[0] }
24
+ Given(:service_definition) do
25
+ Module.new do
26
+ def actions; ['module']; end
27
+ def remote; end
28
+ end
29
+ end
30
+ Given(:path) { 'module/remote' }
31
+ When { bus.add_service service_definition
32
+ puts bus.routes.inspect
33
+ puts bus.routes[path].inspect
34
+ puts bus.routes.keys.inspect
35
+ }
36
+ Then { bus.routes.keys.length == 1 }
37
+ And { bus.routes[path][:service_instance].is_a? service_definition }
20
38
  end
21
39
  end
22
40
  end
@@ -94,8 +94,8 @@ describe 'Combi::WebSocket' do
94
94
  provider.stop!
95
95
  service_result = EM::Synchrony.sync consumer.request(:null, :do_it, {}, { timeout: 0.3 })
96
96
  service_result.should be_a Hash
97
- service_result.should have_key 'error'
98
- service_result['error'].should eq 'Timeout::Error'
97
+ service_result.should have_key :error
98
+ service_result[:error].should eq 'Timeout::Error'
99
99
  done(0.3) #timeout response must came before this timeout
100
100
  end
101
101
  provider.stop!
@@ -9,19 +9,15 @@ describe 'Combi::Service' do
9
9
  Given!("setup methods are stubbed") do
10
10
  subject.stub(:setup_context).and_call_original
11
11
  subject.stub(:setup_services).and_call_original
12
- subject.stub(:register_actions).and_call_original
13
12
  end
14
13
  Given(:the_bus) { double "the bus" }
15
14
  When { subject.setup the_bus, context }
16
15
  context 'sets up the context' do
17
- Then { subject.should have_received :setup_context }
16
+ Then { subject.should have_received(:setup_context).with context }
18
17
  end
19
18
  context 'allows the service to setup itself' do
20
19
  Then { subject.should have_received :setup_services }
21
20
  end
22
- context 'registers the advertised actions' do
23
- Then { subject.should have_received :register_actions }
24
- end
25
21
  context 'always includes the service_bus in the context' do
26
22
  Then { subject.service_bus == the_bus }
27
23
  end
@@ -69,8 +69,8 @@ shared_examples_for "standard_bus" do
69
69
  EM.synchrony do
70
70
  service_result = EM::Synchrony.sync consumer.request(:sleep, :do_it, params, { timeout: time_base/2.0 })
71
71
  service_result.should be_a Hash
72
- service_result.should have_key 'error'
73
- service_result['error'].should eq 'Timeout::Error'
72
+ service_result.should have_key :error
73
+ service_result[:error].should eq 'Timeout::Error'
74
74
  done(time_base) #timeout response must came before this timeout
75
75
  end
76
76
  finalize
@@ -114,8 +114,15 @@ shared_examples_for "standard_bus" do
114
114
  end
115
115
 
116
116
  context 'for maps' do
117
- Given(:data) { {'a' => 1, 'b' => 'dos'} }
118
- Then { result_container[:result].should eq data}
117
+ context 'with string keys' do
118
+ Given(:data) { {'a' => 1, 'b' => 'dos'} }
119
+ Then { result_container[:result].keys.should eq data.keys.map(&:to_sym)}
120
+ And { result_container[:result].values.should eq data.values }
121
+ end
122
+ context 'with symbol keys' do
123
+ Given(:data) { {a: 1, b: 'dos'} }
124
+ Then { result_container[:result].should eq data}
125
+ end
119
126
  end
120
127
 
121
128
  context 'for objects returns their json version' do
@@ -131,7 +138,7 @@ shared_examples_for "standard_bus" do
131
138
  end
132
139
  end
133
140
  Given(:data) { custom_class.new(custom_json).to_json}
134
- Then { result_container[:result].should eq JSON.parse(custom_json.to_json)}
141
+ Then { result_container[:result].should eq custom_json}
135
142
  end
136
143
  end
137
144
 
@@ -143,10 +150,10 @@ shared_examples_for "standard_bus" do
143
150
  prepare
144
151
  EM.synchrony do
145
152
  service_result = EM::Synchrony.sync consumer.request(:shout_error, :do_it, {message: error_message}, { timeout: 0.1 })
146
- service_result['error'].should be_a Hash
147
- service_result['error']['message'].should eq error_message
148
- service_result['error']['backtrace'].should_not be_nil
149
- service_result['error']['backtrace'].should be_an Array
153
+ service_result[:error].should be_a Hash
154
+ service_result[:error][:message].should eq error_message
155
+ service_result[:error][:backtrace].should_not be_nil
156
+ service_result[:error][:backtrace].should be_an Array
150
157
  done
151
158
  finalize
152
159
  end
@@ -155,7 +162,7 @@ shared_examples_for "standard_bus" do
155
162
  end
156
163
 
157
164
  context 'return an error when requesting an unknown service' do
158
- Given(:error_message) { 'unknown service' }
165
+ Given(:error_message) { Combi::Bus::UnknownStop.name }
159
166
  When(:service) { provider.add_service broken_service }
160
167
  Then do
161
168
  em do
@@ -163,10 +170,11 @@ shared_examples_for "standard_bus" do
163
170
  EM.synchrony do
164
171
  begin
165
172
  service_result = EM::Synchrony.sync consumer.request(:some_not_service, :do_it, {}, { timeout: 0.1 })
166
- if consumer.class == Combi::Queue
167
- service_result['error'].should eq "Timeout::Error"
173
+ if defined?(Combi::Queue) and consumer.class == Combi::Queue
174
+ service_result[:error].should eq "Timeout::Error"
168
175
  else
169
- service_result['error'].should eq error_message
176
+ service_result[:error][:klass].should eq error_message
177
+ service_result[:error][:message].should eq 'some_not_service/do_it'
170
178
  end
171
179
  done
172
180
  finalize
@@ -177,7 +185,7 @@ shared_examples_for "standard_bus" do
177
185
  end
178
186
 
179
187
  context 'return an error when requesting an unknown action for the service' do
180
- Given(:error_message) { 'unknown action' }
188
+ Given(:error_message) { Combi::Bus::UnknownStop.name }
181
189
  When(:service) { provider.add_service echo_service }
182
190
  Then do
183
191
  em do
@@ -185,8 +193,8 @@ shared_examples_for "standard_bus" do
185
193
  EM.synchrony do
186
194
  begin
187
195
  service_result = EM::Synchrony.sync consumer.request(:echo_this, :do_other, {}, { timeout: 0.1 })
188
- service_result['error']['klass'].should eq error_message
189
- service_result['error']['message'].should eq 'do_other'
196
+ service_result[:error][:klass].should eq error_message
197
+ service_result[:error][:message].should eq 'echo_this/do_other'
190
198
  done
191
199
  finalize
192
200
  end
@@ -8,15 +8,17 @@ class DeferrableBody
8
8
  def initialize(defer)
9
9
  @defer = defer
10
10
  @defer.callback do |service_response|
11
+ json_response = Yajl::Encoder.encode service_response
11
12
  EM::next_tick do
12
- self.call [{result: 'ok', response: service_response}.to_json]
13
+ self.call [json_response]
13
14
  self.succeed
14
15
  end
15
16
  end
16
17
  @defer.errback do |service_response|
17
18
  error_response = { error: service_response }
19
+ json_response = Yajl::Encoder.encode error_response
18
20
  EM::next_tick do
19
- self.call [{result: 'ok', response: error_response}.to_json]
21
+ self.call [json_response]
20
22
  self.fail
21
23
  end
22
24
  end
@@ -38,16 +40,8 @@ def start_web_server(http_bus, port, webserver = 'thin')
38
40
  require webserver
39
41
  app = lambda do |env|
40
42
  response_message = http_bus.manage_request(env)
41
- if response_message[:response].respond_to? :succeed
42
- env['async.callback'].call [200, {}, DeferrableBody.new(response_message[:response])]
43
- AsyncResponse
44
- else
45
- response_rack = Rack::Response.new
46
- response_rack.status = response_message.nil? ? 201 : 200
47
- response_rack.body = [response_message.to_json]
48
- response_rack.finish
49
- response_rack
50
- end
43
+ env['async.callback'].call [200, {}, DeferrableBody.new(response_message)]
44
+ AsyncResponse
51
45
  end
52
46
  Thin::Logging.silent = true if webserver == 'thin'
53
47
  rack_handler = Rack::Handler.get(webserver)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: combi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - German Del Zotto
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-26 00:00:00.000000000 Z
12
+ date: 2014-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yajl-ruby
@@ -191,9 +191,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
191
  version: '0'
192
192
  required_rubygems_version: !ruby/object:Gem::Requirement
193
193
  requirements:
194
- - - ">="
194
+ - - ">"
195
195
  - !ruby/object:Gem::Version
196
- version: '0'
196
+ version: 1.3.1
197
197
  requirements: []
198
198
  rubyforge_project:
199
199
  rubygems_version: 2.3.0