combi 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +100 -0
- data/combi.gemspec +24 -0
- data/lib/combi.rb +2 -0
- data/lib/combi/buses/bus.rb +82 -0
- data/lib/combi/buses/http.rb +88 -0
- data/lib/combi/buses/in_process.rb +45 -0
- data/lib/combi/buses/queue.rb +86 -0
- data/lib/combi/buses/web_socket.rb +227 -0
- data/lib/combi/helpers.rb +18 -0
- data/lib/combi/queue_service.rb +102 -0
- data/lib/combi/reactor.rb +46 -0
- data/lib/combi/response_store.rb +36 -0
- data/lib/combi/service.rb +59 -0
- data/lib/combi/service_bus.rb +31 -0
- data/lib/combi/version.rb +3 -0
- data/spec/integration/multi_bus_spec.rb +140 -0
- data/spec/lib/combi/buses/bus_spec.rb +22 -0
- data/spec/lib/combi/buses/http_spec.rb +25 -0
- data/spec/lib/combi/buses/in_process_spec.rb +24 -0
- data/spec/lib/combi/buses/queue_spec.rb +41 -0
- data/spec/lib/combi/buses/web_socket_spec.rb +34 -0
- data/spec/lib/combi/service_spec.rb +40 -0
- data/spec/shared_examples/standard_bus.rb +62 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/rabbitmq_server.rb +81 -0
- data/spec/support/web_server.rb +50 -0
- data/spec/support/websocket_server.rb +13 -0
- metadata +203 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Combi
|
2
|
+
class ServiceBus
|
3
|
+
class << self
|
4
|
+
@@buses = {}
|
5
|
+
|
6
|
+
def for(kind, options = {})
|
7
|
+
@@buses[kind] ||= init_for(kind, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def init_for(kind, options)
|
11
|
+
require 'combi/buses/bus'
|
12
|
+
|
13
|
+
case kind
|
14
|
+
when :in_process
|
15
|
+
require 'combi/buses/in_process'
|
16
|
+
Combi::InProcess.new(options)
|
17
|
+
when :queue
|
18
|
+
require 'combi/buses/queue'
|
19
|
+
Combi::Queue.new(options)
|
20
|
+
when :web_socket
|
21
|
+
require 'combi/buses/web_socket'
|
22
|
+
Combi::WebSocket.new(options)
|
23
|
+
when :http
|
24
|
+
require 'combi/buses/http'
|
25
|
+
Combi::Http.new(options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combi/service_bus'
|
4
|
+
require 'combi/buses/in_process'
|
5
|
+
|
6
|
+
describe "in a multi bus environment" do
|
7
|
+
include EventedSpec::SpecHelper
|
8
|
+
|
9
|
+
Given(:boring_salutation_service) do
|
10
|
+
Module.new do
|
11
|
+
def actions; [:say_hello]; end
|
12
|
+
def do_it(params); "Hello #{params['who']}"; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Given(:composed_service_class) do
|
17
|
+
Class.new do
|
18
|
+
include Combi::Service
|
19
|
+
|
20
|
+
def initialize(other_client)
|
21
|
+
@other_client = other_client
|
22
|
+
end
|
23
|
+
|
24
|
+
def actions; [:repeat_with_me]; end
|
25
|
+
|
26
|
+
def do_it(params)
|
27
|
+
defer = EventMachine::DefaultDeferrable.new
|
28
|
+
EM.synchrony do
|
29
|
+
req = @other_client.request(:say_hello, :do_it, params, timeout: 3)
|
30
|
+
service_result = EM::Synchrony.sync req
|
31
|
+
defer.succeed(service_result*2)
|
32
|
+
end
|
33
|
+
defer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Given(:amqp_config) do
|
39
|
+
{
|
40
|
+
:host => "127.0.0.1",
|
41
|
+
:port => RabbitmqServer.instance.port,
|
42
|
+
:user => "admin",
|
43
|
+
:pass => RabbitmqServer::PASSWORD,
|
44
|
+
:vhost => "/",
|
45
|
+
:ssl => false,
|
46
|
+
:heartbeat => 0,
|
47
|
+
:frame_max => 131072
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
Given(:handler) { double('handler', on_open: nil) }
|
52
|
+
Given(:server_port_socket) { 9292 + rand(30000) }
|
53
|
+
Given(:socket_client_options) do
|
54
|
+
{ remote_api: "ws://localhost:#{server_port_socket}/",
|
55
|
+
handler: handler }
|
56
|
+
end
|
57
|
+
Given(:server_port_http) { 9292 + rand(30000) }
|
58
|
+
Given(:http_client_options) do
|
59
|
+
{ remote_api: "http://localhost:#{server_port_http}/" }
|
60
|
+
end
|
61
|
+
|
62
|
+
before(:all) {
|
63
|
+
RabbitmqServer.instance.stop! if ENV['CLEAN']
|
64
|
+
RabbitmqServer.instance.start!
|
65
|
+
}
|
66
|
+
|
67
|
+
after(:all) {
|
68
|
+
RabbitmqServer.instance.stop! if ENV['CLEAN']
|
69
|
+
}
|
70
|
+
|
71
|
+
Given(:options) { {timeout: 3} }
|
72
|
+
|
73
|
+
Given(:service_for_main_bus) { main_bus_provider.add_service composed_service_class.new(internal_bus_consumer) }
|
74
|
+
Given(:service_for_internal_bus) { internal_bus_provider.add_service boring_salutation_service }
|
75
|
+
When(:params) { { who: 'world' } }
|
76
|
+
When(:expected_result) { "Hello worldHello world" }
|
77
|
+
|
78
|
+
Given(:in_process_provider) { Combi::ServiceBus.init_for(:in_process, {}) }
|
79
|
+
Given(:in_process_consumer) { in_process_provider }
|
80
|
+
Given(:http_provider) { Combi::ServiceBus.init_for(:http, {} ) }
|
81
|
+
Given(:http_consumer) { Combi::ServiceBus.init_for(:http, http_client_options) }
|
82
|
+
Given(:queue_provider) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
|
83
|
+
Given(:queue_consumer) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
|
84
|
+
Given(:socket_provider) { Combi::ServiceBus.init_for(:web_socket, {} ) }
|
85
|
+
Given(:socket_consumer) { Combi::ServiceBus.init_for(:web_socket, socket_client_options) }
|
86
|
+
|
87
|
+
BUSES = %w{in_process socket http queue}
|
88
|
+
|
89
|
+
BUSES.each do |main|
|
90
|
+
BUSES.each do |internal|
|
91
|
+
context "#{internal} inside #{main}" do
|
92
|
+
Given(:main_bus_provider) { send "#{main}_provider" }
|
93
|
+
Given(:main_bus_consumer) { send "#{main}_consumer" }
|
94
|
+
Given(:internal_bus_provider) { send "#{internal}_provider" }
|
95
|
+
Given(:internal_bus_consumer) { send "#{internal}_consumer" }
|
96
|
+
|
97
|
+
Given("#{main}_server".to_sym) do
|
98
|
+
if main == 'http'
|
99
|
+
start_web_server main_bus_provider, server_port_http
|
100
|
+
end
|
101
|
+
if main == 'socket'
|
102
|
+
start_em_websocket_server main_bus_provider, server_port_socket
|
103
|
+
end
|
104
|
+
end
|
105
|
+
Given("#{internal}_server".to_sym) do
|
106
|
+
if internal == 'http'
|
107
|
+
start_web_server internal_bus_provider, server_port_http
|
108
|
+
end
|
109
|
+
if internal == 'socket'
|
110
|
+
start_em_websocket_server internal_bus_provider, server_port_socket
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
Then do
|
115
|
+
em do
|
116
|
+
service_for_main_bus
|
117
|
+
service_for_internal_bus
|
118
|
+
main_bus_provider.start!
|
119
|
+
internal_bus_provider.start!
|
120
|
+
send("#{main}_server")
|
121
|
+
send("#{internal}_server")
|
122
|
+
main_bus_consumer.start!
|
123
|
+
internal_bus_consumer.start!
|
124
|
+
|
125
|
+
EM.synchrony do
|
126
|
+
service_result = EM::Synchrony.sync main_bus_consumer.request(:repeat_with_me, :do_it, params, options)
|
127
|
+
service_result.should eq expected_result
|
128
|
+
done
|
129
|
+
main_bus_provider.stop!
|
130
|
+
internal_bus_provider.stop!
|
131
|
+
main_bus_consumer.stop!
|
132
|
+
internal_bus_consumer.stop!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Combi::Bus' do
|
4
|
+
context 'registers services' do
|
5
|
+
Given(:subject) { Combi::Bus.new({}) }
|
6
|
+
|
7
|
+
context 'via instances' do
|
8
|
+
Given(:service_class) { Class.new{include Combi::Service} }
|
9
|
+
Given(:service_definition) { service_class.new }
|
10
|
+
When { subject.add_service service_definition }
|
11
|
+
Then { subject.services == [service_definition] }
|
12
|
+
end
|
13
|
+
|
14
|
+
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] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combi/service_bus'
|
4
|
+
require 'combi/buses/http'
|
5
|
+
|
6
|
+
describe 'Combi::Http' do
|
7
|
+
include EventedSpec::SpecHelper
|
8
|
+
|
9
|
+
context 'can be instantiated via ServiceBus' do
|
10
|
+
When(:bus) { Combi::ServiceBus.init_for(:http, {}) }
|
11
|
+
Then { Combi::Http === bus }
|
12
|
+
end
|
13
|
+
Given(:server_port) { 9292 + rand(30000) }
|
14
|
+
Given(:client_options) { { remote_api: "http://localhost:#{server_port}/" } }
|
15
|
+
Given(:provider) { Combi::ServiceBus.init_for(:http, {} ) }
|
16
|
+
Given(:consumer) { Combi::ServiceBus.init_for(:http, client_options) }
|
17
|
+
Given(:prepare) do
|
18
|
+
provider.start!
|
19
|
+
start_web_server provider, server_port
|
20
|
+
consumer.start!
|
21
|
+
end
|
22
|
+
|
23
|
+
it_behaves_like 'standard_bus'
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combi/service_bus'
|
4
|
+
require 'combi/buses/in_process'
|
5
|
+
|
6
|
+
describe 'Combi::InProcess' do
|
7
|
+
include EventedSpec::SpecHelper
|
8
|
+
|
9
|
+
context 'can be instanitated via ServiceBus' do
|
10
|
+
When(:bus) { Combi::ServiceBus.init_for(:in_process, {}) }
|
11
|
+
Then { Combi::InProcess === bus }
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(:subject) { Combi::ServiceBus.init_for(:in_process, {}) }
|
15
|
+
Given(:provider) { subject }
|
16
|
+
Given(:consumer) { subject }
|
17
|
+
Given(:prepare) do
|
18
|
+
provider.start!
|
19
|
+
consumer.start!
|
20
|
+
end
|
21
|
+
|
22
|
+
it_behaves_like 'standard_bus'
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combi/service_bus'
|
4
|
+
require 'combi/buses/queue'
|
5
|
+
|
6
|
+
describe 'Combi::Queue' do
|
7
|
+
include EventedSpec::SpecHelper
|
8
|
+
|
9
|
+
context 'can be instanitated via ServiceBus' do
|
10
|
+
When(:bus) { Combi::ServiceBus.init_for(:queue, {init_queue: false}) }
|
11
|
+
Then { Combi::Queue === bus }
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(:amqp_config) do
|
15
|
+
{
|
16
|
+
:host => "127.0.0.1",
|
17
|
+
:port => RabbitmqServer.instance.port,
|
18
|
+
:user => "admin",
|
19
|
+
:pass => RabbitmqServer::PASSWORD,
|
20
|
+
:vhost => "/",
|
21
|
+
:ssl => false,
|
22
|
+
:heartbeat => 0,
|
23
|
+
:frame_max => 131072
|
24
|
+
}
|
25
|
+
end
|
26
|
+
Given(:provider) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
|
27
|
+
Given(:consumer) { Combi::ServiceBus.init_for(:queue, { amqp_config: amqp_config } ) }
|
28
|
+
Given(:prepare) do
|
29
|
+
provider.start!
|
30
|
+
consumer.start!
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
it_behaves_like 'standard_bus' do
|
35
|
+
before(:all) do
|
36
|
+
RabbitmqServer.instance.stop! if ENV['CLEAN']
|
37
|
+
RabbitmqServer.instance.start!
|
38
|
+
end
|
39
|
+
after(:all) { RabbitmqServer.instance.stop! if ENV['CLEAN'] }
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'combi/service_bus'
|
4
|
+
require 'combi/buses/web_socket'
|
5
|
+
|
6
|
+
describe 'Combi::WebSocket' do
|
7
|
+
include EventedSpec::SpecHelper
|
8
|
+
|
9
|
+
context 'can be instanitated via ServiceBus' do
|
10
|
+
When(:bus) { Combi::ServiceBus.init_for(:web_socket, {}) }
|
11
|
+
Then { Combi::WebSocket === bus }
|
12
|
+
end
|
13
|
+
Given(:handler) do
|
14
|
+
Class.new do
|
15
|
+
def on_open; end
|
16
|
+
end.new
|
17
|
+
end
|
18
|
+
|
19
|
+
Given(:server_port) { 9292 + rand(30000) }
|
20
|
+
Given(:client_options) do
|
21
|
+
{ remote_api: "ws://localhost:#{server_port}/",
|
22
|
+
handler: handler }
|
23
|
+
end
|
24
|
+
Given(:provider) { Combi::ServiceBus.init_for(:web_socket, {} )}
|
25
|
+
Given(:consumer) { Combi::ServiceBus.init_for(:web_socket, client_options) }
|
26
|
+
Given(:prepare) do
|
27
|
+
provider.start!
|
28
|
+
start_em_websocket_server provider, server_port
|
29
|
+
consumer.start!
|
30
|
+
end
|
31
|
+
|
32
|
+
it_behaves_like 'standard_bus'
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Combi::Service' do
|
4
|
+
Given(:subject_class) { Class.new{include Combi::Service} }
|
5
|
+
Given(:subject) { subject_class.new }
|
6
|
+
|
7
|
+
describe '#setup' do
|
8
|
+
Given(:context) { {foo: 'bar', one: 1} }
|
9
|
+
Given!("setup methods are stubbed") do
|
10
|
+
subject.stub(:setup_context).and_call_original
|
11
|
+
subject.stub(:setup_services).and_call_original
|
12
|
+
subject.stub(:register_actions).and_call_original
|
13
|
+
end
|
14
|
+
Given(:the_bus) { double "the bus" }
|
15
|
+
When { subject.setup the_bus, context }
|
16
|
+
context 'sets up the context' do
|
17
|
+
Then { subject.should have_received :setup_context }
|
18
|
+
end
|
19
|
+
context 'allows the service to setup itself' do
|
20
|
+
Then { subject.should have_received :setup_services }
|
21
|
+
end
|
22
|
+
context 'registers the advertised actions' do
|
23
|
+
Then { subject.should have_received :register_actions }
|
24
|
+
end
|
25
|
+
context 'always includes the service_bus in the context' do
|
26
|
+
Then { subject.service_bus == the_bus }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#setup_context' do
|
31
|
+
context 'creates accessors for keys in the context' do
|
32
|
+
Given(:context) { {foo: 'bar', one: 1} }
|
33
|
+
When { subject.setup_context context }
|
34
|
+
Then { subject.respond_to? :foo }
|
35
|
+
And { subject.respond_to? :one}
|
36
|
+
And { subject.foo == 'bar' }
|
37
|
+
And { subject.one == 1 }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "em-synchrony"
|
2
|
+
|
3
|
+
shared_examples_for "standard_bus" do
|
4
|
+
|
5
|
+
Given(:slow_service) do
|
6
|
+
Module.new do
|
7
|
+
def actions; [:sleep]; end
|
8
|
+
def do_it(params)
|
9
|
+
sleep params['time']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Given(:boring_salutation_service) do
|
15
|
+
Module.new do
|
16
|
+
def actions; [:say_hello]; end
|
17
|
+
def do_it(params)
|
18
|
+
"Hello #{params['who']}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Given(:finalize) do
|
24
|
+
provider.stop!
|
25
|
+
consumer.stop!
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'can invoke services' do
|
29
|
+
When(:service) { provider.add_service boring_salutation_service }
|
30
|
+
When(:params) { { who: 'world' } }
|
31
|
+
Then do
|
32
|
+
em do
|
33
|
+
prepare
|
34
|
+
EM.synchrony do
|
35
|
+
service_result = EM::Synchrony.sync consumer.request(:say_hello, :do_it, params, { timeout: 2 })
|
36
|
+
service_result.should eq "Hello world"
|
37
|
+
done
|
38
|
+
finalize
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'raises Timeout::Error when the response is slow' do
|
45
|
+
Given(:time_base) { 0.01 }
|
46
|
+
When(:params) { { time: time_base*4 } }
|
47
|
+
When(:service) { provider.add_service slow_service }
|
48
|
+
Then do
|
49
|
+
em do
|
50
|
+
prepare
|
51
|
+
EM.synchrony do
|
52
|
+
expect do
|
53
|
+
service_result = EM::Synchrony.sync consumer.request(:sleep, :do_it, params, { timeout: time_base/2.0 })
|
54
|
+
raise Kernel.const_get(service_result.message) if service_result.is_a? Exception
|
55
|
+
end.to raise_error Timeout::Error
|
56
|
+
done(time_base) #timeout response must came before this timeout
|
57
|
+
end
|
58
|
+
finalize
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rspec-given'
|
20
|
+
require 'evented-spec'
|
21
|
+
Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
|
22
|
+
Dir["./spec/shared_examples/**/*.rb"].sort.each {|f| require f}
|