rcom 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +114 -1
- data/lib/rcom/rpc.rb +22 -13
- data/lib/rcom/task.rb +6 -1
- data/lib/rcom/topic.rb +6 -1
- data/lib/rcom/version.rb +1 -1
- data/test/bin/rpc_consumer.rb +2 -3
- data/test/bin/rpc_publisher.rb +3 -4
- data/test/bin/task_consumer.rb +0 -1
- data/test/bin/task_publisher.rb +0 -1
- data/test/bin/topic_publisher.rb +2 -3
- data/test/bin/topic_subscriber.rb +2 -3
- data/test/spec/rpc_spec.rb +11 -10
- data/test/spec/task_spec.rb +11 -3
- data/test/spec/topic_spec.rb +12 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57eeb76581f7c4b2e8a370e59c286e47d4a3eeb8
|
4
|
+
data.tar.gz: d417c03fd9471cf183dcbb2216a62ac9d356bbb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5be8b6c88113107329e7a061e04ffbd1785111f57c112df4c6edf5ae0833968595678812a0ff79f9e9688b8827eb08087a603c5055dcb2b517e460d809c649c0
|
7
|
+
data.tar.gz: 8bc4c1f4c67821cf3ea3190e6a8b9eb0430f7c09993e27bc13882363be3147a3263b5e0abc4b95e4e689378af3580c81993fa8989e9d0e85a4a52f8db61c5370
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Rcom
|
2
2
|
|
3
|
-
Redis inter-service messaging: request-response, publish-subscribe and
|
3
|
+
[Redis](http://redis.io) inter-service messaging: request-response, publish-subscribe and task queues. A thin, minimal layer on top of [Redis-rb](https://github.com/redis/redis-rb).
|
4
4
|
|
5
5
|
## Installation.
|
6
6
|
|
@@ -18,8 +18,121 @@ gem 'rcom'
|
|
18
18
|
|
19
19
|
## Usage.
|
20
20
|
|
21
|
+
Rcom supports the request-response, publish-subscribe and task queues patterns for inter-service messaging. Publishers are non-blocking, subscribers/consumers are blocking and should be run as independent processes. Processes communicate using MessagePack internally.
|
22
|
+
|
23
|
+
### Node.
|
24
|
+
|
25
|
+
A node represents a Redis connection to a server address specified with an ENV variable.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Specify this in your .env file.
|
29
|
+
ENV['local'] = 'redis://localhost'
|
30
|
+
node = Rcom::Node.new('local').connect
|
31
|
+
```
|
32
|
+
|
33
|
+
### Topics.
|
34
|
+
|
35
|
+
One service might need to update many different services about an event, following the publish-subscribe pattern. You can publish and subscribe to topics on a node, specifying a key.
|
36
|
+
|
37
|
+
- Publisher.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
message = {
|
41
|
+
id: 1,
|
42
|
+
key: 'xxxccc'
|
43
|
+
}
|
44
|
+
|
45
|
+
node = Rcom::Node.new('local').connect
|
46
|
+
topic = Rcom::Topic.new(node: node, key: 'users')
|
47
|
+
|
48
|
+
topic.publish(message)
|
49
|
+
```
|
50
|
+
|
51
|
+
- Subscriber.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
node = Rcom::Node.new('local').connect
|
55
|
+
topic = Rcom::Topic.new(node: node, key: 'users')
|
56
|
+
|
57
|
+
topic.subscribe do |message|
|
58
|
+
p message
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
## Tasks.
|
63
|
+
|
64
|
+
A service might need to push expensive tasks into a queue and forget about them. Tasks will be processed by consumers listening to the queue.
|
65
|
+
|
66
|
+
- Publisher.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
message = {
|
70
|
+
id: 1,
|
71
|
+
key: 'xxxccc'
|
72
|
+
}
|
73
|
+
|
74
|
+
node = Rcom::Node.new('local').connect
|
75
|
+
messages = Rcom::Task.new(node: node, queue: 'messages')
|
76
|
+
|
77
|
+
messages.publish(message)
|
78
|
+
```
|
79
|
+
|
80
|
+
- Consumer.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
node = Rcom::Node.new('local').connect
|
84
|
+
messages = Rcom::Task.new(node: node, queue: 'messages')
|
85
|
+
|
86
|
+
messages.subscribe do |message|
|
87
|
+
sleep 1
|
88
|
+
p message
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
## RPC, requests and responses.
|
93
|
+
|
94
|
+
In some cases services need real time informations from other services that can't be asynchronously processed. A service can create a request on a route. The other service listening on the same route will reply to the request.
|
95
|
+
|
96
|
+
- Publisher.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
message = {
|
100
|
+
route: 'user.key',
|
101
|
+
args: 1
|
102
|
+
}
|
103
|
+
|
104
|
+
node = Rcom::Node.new('local').connect
|
105
|
+
auth = Rcom::Rpc.new(node: node, service: 'auth')
|
106
|
+
|
107
|
+
auth.request(message)
|
108
|
+
```
|
109
|
+
|
110
|
+
- Consumer.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
node = Rcom::Node.new('local').connect
|
114
|
+
auth = Rcom::Rpc.new(node: node, service: 'auth')
|
115
|
+
|
116
|
+
auth.subscribe do |request|
|
117
|
+
request.on('user.key') do |params|
|
118
|
+
request.reply = 'xxxccc'
|
119
|
+
end
|
120
|
+
|
121
|
+
request.on('user.password') do |params|
|
122
|
+
request.reply = 'not authorized'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
21
127
|
## Test.
|
22
128
|
|
129
|
+
- Be sure you have a local Redis server running.
|
130
|
+
|
131
|
+
- run tests with:
|
132
|
+
```ruby
|
133
|
+
bundle exec rake test:spec
|
134
|
+
```
|
135
|
+
|
23
136
|
## Contributing.
|
24
137
|
|
25
138
|
1. Fork it ( https://github.com/badshark/rcom/fork )
|
data/lib/rcom/rpc.rb
CHANGED
@@ -8,29 +8,38 @@ module Rcom
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def request(params)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
response,
|
20
|
-
|
21
|
-
|
11
|
+
begin
|
12
|
+
request = {
|
13
|
+
id: SecureRandom.hex,
|
14
|
+
route: params[:route],
|
15
|
+
args: params[:args] || ''
|
16
|
+
}
|
17
|
+
|
18
|
+
node.rpush(service, request.to_msgpack)
|
19
|
+
ch, response = node.brpop(request[:id], timeout=10)
|
20
|
+
|
21
|
+
MessagePack.unpack(
|
22
|
+
response,
|
23
|
+
symbolize_keys: true
|
24
|
+
)
|
25
|
+
rescue
|
26
|
+
return nil
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
def subscribe
|
25
31
|
begin
|
26
32
|
loop do
|
27
33
|
ch, request = node.brpop(service)
|
34
|
+
|
28
35
|
message = MessagePack.unpack(
|
29
36
|
request,
|
30
37
|
symbolize_keys: true
|
31
38
|
)
|
32
39
|
router = Rcom::Router.new(message)
|
40
|
+
|
33
41
|
yield router
|
42
|
+
|
34
43
|
node.rpush(message[:id], router.reply.to_msgpack)
|
35
44
|
end
|
36
45
|
rescue Interrupt => _
|
@@ -45,8 +54,8 @@ module Rcom
|
|
45
54
|
@message = message
|
46
55
|
end
|
47
56
|
|
48
|
-
def on(
|
49
|
-
return nil unless message[:
|
57
|
+
def on(route)
|
58
|
+
return nil unless message[:route] == route
|
50
59
|
yield message[:args]
|
51
60
|
end
|
52
61
|
end
|
data/lib/rcom/task.rb
CHANGED
data/lib/rcom/topic.rb
CHANGED
data/lib/rcom/version.rb
CHANGED
data/test/bin/rpc_consumer.rb
CHANGED
@@ -2,12 +2,11 @@
|
|
2
2
|
ENV['LOCAL'] = 'redis://localhost'
|
3
3
|
|
4
4
|
require 'rcom'
|
5
|
-
require 'json'
|
6
5
|
|
7
6
|
node = Rcom::Node.new('local').connect
|
8
|
-
|
7
|
+
auth = Rcom::Rpc.new(node: node, service: 'auth')
|
9
8
|
|
10
|
-
|
9
|
+
auth.subscribe do |request|
|
11
10
|
request.on('user.key') do |params|
|
12
11
|
request.reply = 'xxxccc'
|
13
12
|
end
|
data/test/bin/rpc_publisher.rb
CHANGED
@@ -2,12 +2,11 @@
|
|
2
2
|
ENV['LOCAL'] = 'redis://localhost'
|
3
3
|
|
4
4
|
require 'rcom'
|
5
|
-
require 'json'
|
6
5
|
|
7
6
|
message = {
|
8
|
-
|
7
|
+
route: 'user.key',
|
9
8
|
args: 1
|
10
9
|
}
|
11
10
|
node = Rcom::Node.new('local').connect
|
12
|
-
|
13
|
-
p
|
11
|
+
auth = Rcom::Rpc.new(node: node, service: 'auth')
|
12
|
+
p auth.request(message)
|
data/test/bin/task_consumer.rb
CHANGED
data/test/bin/task_publisher.rb
CHANGED
data/test/bin/topic_publisher.rb
CHANGED
@@ -2,13 +2,12 @@
|
|
2
2
|
ENV['local'] = 'redis://localhost'
|
3
3
|
|
4
4
|
require 'rcom'
|
5
|
-
require 'json'
|
6
5
|
|
7
6
|
message = {
|
8
7
|
id: 1,
|
9
8
|
key: 'xxxccc'
|
10
9
|
}
|
11
10
|
node = Rcom::Node.new('local').connect
|
12
|
-
|
11
|
+
users = Rcom::Topic.new(node: node, key: 'users')
|
13
12
|
|
14
|
-
|
13
|
+
users.publish(message)
|
@@ -2,11 +2,10 @@
|
|
2
2
|
ENV['local'] = 'redis://localhost'
|
3
3
|
|
4
4
|
require 'rcom'
|
5
|
-
require 'json'
|
6
5
|
|
7
6
|
node = Rcom::Node.new('local').connect
|
8
|
-
|
7
|
+
users = Rcom::Topic.new(node: node, key: 'users')
|
9
8
|
|
10
|
-
|
9
|
+
users.subscribe do |message|
|
11
10
|
p message
|
12
11
|
end
|
data/test/spec/rpc_spec.rb
CHANGED
@@ -4,11 +4,15 @@ describe 'Rpc' do
|
|
4
4
|
before do
|
5
5
|
ENV['LOCAL'] = 'redis://localhost'
|
6
6
|
@node = Rcom::Node.new('local').connect
|
7
|
+
@service = Rcom::Rpc.new(node: @node, service: 'auth')
|
7
8
|
end
|
8
9
|
|
9
10
|
it 'represents a remote procedure call' do
|
10
|
-
service
|
11
|
-
|
11
|
+
@service.must_be_instance_of Rcom::Rpc
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns nil if the request cannot be processed' do
|
15
|
+
@service.request(route: 'nonexistent').must_equal nil
|
12
16
|
end
|
13
17
|
|
14
18
|
it 'works in a request/response scenario' do
|
@@ -17,19 +21,16 @@ describe 'Rpc' do
|
|
17
21
|
publisher = 'bundle exec ruby test/bin/rpc_publisher.rb'
|
18
22
|
consumer = 'bundle exec ruby test/bin/rpc_consumer.rb'
|
19
23
|
|
20
|
-
#
|
21
|
-
# start the publisher and wait for the message
|
22
|
-
# on the consumer side. Process, then kill
|
23
|
-
# the long-running consumer.
|
24
|
-
|
24
|
+
# Spawn consumer and wait for requests.
|
25
25
|
consumer_pid = spawn(consumer)
|
26
26
|
sleep 1
|
27
27
|
|
28
|
-
|
28
|
+
# Spawn a request and record stdout,
|
29
|
+
# then kill both consumer and publisher.
|
30
|
+
Open3.popen3(publisher) do |stdin, stdout, stderr, thr|
|
29
31
|
response = stdout.gets
|
30
|
-
Process.kill('INT',
|
32
|
+
Process.kill('INT', thr.pid)
|
31
33
|
end
|
32
|
-
|
33
34
|
Process.kill('INT', consumer_pid)
|
34
35
|
|
35
36
|
eval(response.chomp).must_equal user_key
|
data/test/spec/task_spec.rb
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
require_relative './_init'
|
2
2
|
|
3
3
|
describe 'Task' do
|
4
|
-
# Doesn't stop processes
|
5
4
|
before do
|
6
5
|
ENV['LOCAL'] = 'redis://localhost'
|
7
6
|
@node = Rcom::Node.new('local').connect
|
7
|
+
@task = Rcom::Task.new(node: @node, queue: 'messages')
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'represents a Task' do
|
11
|
-
task
|
12
|
-
|
11
|
+
@task.must_be_instance_of Rcom::Task
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can pubish a task' do
|
15
|
+
message = {
|
16
|
+
id: 1,
|
17
|
+
key: 'xxxccc'
|
18
|
+
}
|
19
|
+
@task.publish(message).must_equal true
|
13
20
|
end
|
14
21
|
|
15
22
|
it 'works in a pub/consumer scenario' do
|
@@ -26,6 +33,7 @@ describe 'Task' do
|
|
26
33
|
# on the consumer side. Process, then kill
|
27
34
|
# the long-running consumer.
|
28
35
|
Open3.popen3(consumer) do |stdin, stdout, stderr, wait_thr|
|
36
|
+
sleep 1
|
29
37
|
spawn(publisher)
|
30
38
|
completed_job = stdout.gets
|
31
39
|
Process.kill('INT', wait_thr.pid)
|
data/test/spec/topic_spec.rb
CHANGED
@@ -4,11 +4,19 @@ describe 'Topic' do
|
|
4
4
|
before do
|
5
5
|
ENV['LOCAL'] = 'redis://localhost'
|
6
6
|
@node = Rcom::Node.new('local').connect
|
7
|
+
@topic = Rcom::Topic.new(node: @node, key: 'users')
|
7
8
|
end
|
8
9
|
|
9
10
|
it 'represents a Topic' do
|
10
|
-
topic
|
11
|
-
|
11
|
+
@topic.must_be_instance_of Rcom::Topic
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can publish a message' do
|
15
|
+
message = {
|
16
|
+
id: 1,
|
17
|
+
key: 'xxxccc'
|
18
|
+
}
|
19
|
+
@topic.publish(message).must_equal true
|
12
20
|
end
|
13
21
|
|
14
22
|
it 'works in a pub/sub scenario' do
|
@@ -25,13 +33,13 @@ describe 'Topic' do
|
|
25
33
|
# on the subscriber side. Then kill
|
26
34
|
# the long-running subscriber.
|
27
35
|
Open3.popen3(subscriber) do |stdin, stdout, stderr, wait_thr|
|
28
|
-
sleep
|
36
|
+
sleep 2
|
29
37
|
spawn(publisher)
|
30
38
|
read_message = stdout.gets
|
31
39
|
Process.kill('INT', wait_thr.pid)
|
32
40
|
end
|
33
41
|
|
34
|
-
output = eval
|
42
|
+
output = eval(read_message.chomp)
|
35
43
|
output.must_equal message
|
36
44
|
end
|
37
45
|
end
|