rcom 0.0.1 → 0.0.2
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 +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
|