combi 0.0.3
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 +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}
|