lolitra 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.
- data/.rspec +1 -0
- data/lib/lolitra/handler_base.rb +231 -0
- data/lib/lolitra/version.rb +1 -1
- data/lib/lolitra.rb +1 -4
- data/lolitra.gemspec +2 -0
- data/spec/lolitra_spec.rb +176 -0
- data/spec/spec_helper.rb +30 -0
- metadata +24 -7
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--default_path spec
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Lolitra
|
4
|
+
module MessageHandler
|
5
|
+
module Helpers
|
6
|
+
def self.underscore(word)
|
7
|
+
word.gsub!(/::/, '/')
|
8
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
9
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
10
|
+
word.tr!("-", "_")
|
11
|
+
word.downcase!
|
12
|
+
word
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NoHandlerMessageException < StandardError
|
17
|
+
def initialize(handler, message)
|
18
|
+
@handler_class = handler.name
|
19
|
+
@message_class = message.class.name
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"No handler (or starter if stateful) for message #{@message_class} in class #{@handler_class}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module MessageHandlerClass
|
28
|
+
|
29
|
+
def self.extended(base)
|
30
|
+
class << base
|
31
|
+
attr_accessor :handlers
|
32
|
+
attr_accessor :starters
|
33
|
+
attr_accessor :is_stateful
|
34
|
+
end
|
35
|
+
base.handlers = {}
|
36
|
+
base.starters = []
|
37
|
+
base.is_stateful = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle(message)
|
41
|
+
#puts "#{self.name} try to handle new message #{message.class.name}"
|
42
|
+
begin
|
43
|
+
get_handler(message).handle(message)
|
44
|
+
rescue NoMethodError => e
|
45
|
+
raise NoHandlerMessageException.new(self, message) if e.message == "undefined method `handle' for nil:NilClass"
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def publish(message)
|
51
|
+
#TODO: IoC
|
52
|
+
MessageHandlerManager.publish(message)
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_messages
|
56
|
+
handlers.values.collect { |class_method_pair| class_method_pair[0] }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def message_handler(message_class, id = :id)
|
61
|
+
message_class.new.send(id) #check if id exists for this class
|
62
|
+
handlers.merge!(message_class.name => [message_class, get_method_by_class(message_class), id])
|
63
|
+
end
|
64
|
+
|
65
|
+
def started_by(message_class)
|
66
|
+
starters << message_class.name
|
67
|
+
end
|
68
|
+
|
69
|
+
def search(message)
|
70
|
+
id_method_name = handlers[message.class.name][2]
|
71
|
+
send("find_by_#{id_method_name}", message.send(id_method_name))
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_starter?(message)
|
75
|
+
starters.include? message.class.name
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_handler(message)
|
79
|
+
if is_stateful?
|
80
|
+
is_starter?(message) ? (search(message) || new) : search(message)
|
81
|
+
else
|
82
|
+
new
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_method_by_class(arg_class)
|
87
|
+
MessageHandler::Helpers.underscore(arg_class.name).gsub("/","_").to_sym
|
88
|
+
end
|
89
|
+
|
90
|
+
def stateful(stateful_arg)
|
91
|
+
self.is_stateful = stateful_arg
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_stateful?
|
95
|
+
is_stateful
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.included(base)
|
100
|
+
base.send :extend, MessageHandlerClass
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle(message)
|
104
|
+
handler_method = self.class.handlers[message.class.name][1]
|
105
|
+
raise "Can't handle message #{message.class}" unless handler_method
|
106
|
+
self.send(handler_method, message)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class MessageHandlerManager
|
112
|
+
include Singleton
|
113
|
+
|
114
|
+
attr_accessor :bus
|
115
|
+
|
116
|
+
def self.bus=(new_bus)
|
117
|
+
instance.bus = new_bus
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.bus
|
121
|
+
instance.bus
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.register(handler_class)
|
125
|
+
instance.register(handler_class)
|
126
|
+
end
|
127
|
+
|
128
|
+
def register(handler_class)
|
129
|
+
register_message_handler(handler_class)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.publish(message_instance)
|
133
|
+
instance.publish(message_instance)
|
134
|
+
end
|
135
|
+
|
136
|
+
def publish(message_instance)
|
137
|
+
bus.publish(message_instance)
|
138
|
+
end
|
139
|
+
private
|
140
|
+
def register_message_handler(handler_class)
|
141
|
+
handler_class.handle_messages.each do |message_class|
|
142
|
+
bus.subscribe(message_class, handler_class)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module AmqpMessage
|
148
|
+
|
149
|
+
module AmqpMessageClass
|
150
|
+
def self.extended(base)
|
151
|
+
class << base; attr_accessor :class_message_key; end
|
152
|
+
end
|
153
|
+
|
154
|
+
def message_key(key = nil)
|
155
|
+
if (key)
|
156
|
+
self.class_message_key = key
|
157
|
+
else
|
158
|
+
self.class_message_key
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def unmarshall(message_json)
|
163
|
+
hash = JSON.parse(message_json)
|
164
|
+
self.new(hash)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.included(base)
|
170
|
+
base.send :extend, AmqpMessageClass
|
171
|
+
end
|
172
|
+
|
173
|
+
def initialize(hash={})
|
174
|
+
hash.each { |key, value| self.send("#{MessageHandler::Helper.underscore(key)}=", value) }
|
175
|
+
end
|
176
|
+
|
177
|
+
def marshall
|
178
|
+
JSON.generate(self)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
module AmqpMessageHandler
|
183
|
+
module AmqpMessageHandlerClass
|
184
|
+
|
185
|
+
def self.extended(base)
|
186
|
+
class << base
|
187
|
+
attr_accessor :message_class_by_key
|
188
|
+
alias_method :message_handler_without_class_by_key, :message_handler unless method_defined?(:message_handler_without_class_by_key)
|
189
|
+
alias_method :message_handler, :message_handler_with_class_by_key
|
190
|
+
end
|
191
|
+
base.message_class_by_key = {}
|
192
|
+
end
|
193
|
+
|
194
|
+
def message_handler_with_class_by_key(message_class, id = :id)
|
195
|
+
message_class_by_key.merge!({message_class.message_key => message_class})
|
196
|
+
message_handler_without_class_by_key(message_class, id)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.included(base)
|
202
|
+
base.send :include, MessageHandler
|
203
|
+
base.send :extend, AmqpMessageHandlerClass
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
class AmqpBus
|
209
|
+
attr_accessor :queue_prefix
|
210
|
+
attr_accessor :exchange
|
211
|
+
|
212
|
+
def initialize(hash = {})
|
213
|
+
self.queue_prefix = hash[:queue_prefix]
|
214
|
+
self.exchange = hash[:exchange]
|
215
|
+
end
|
216
|
+
|
217
|
+
def subscribe(message_class, handler_class)
|
218
|
+
EM.next_tick do
|
219
|
+
AmqpNotifier.new(exchange, queue_prefix + MessageHandler::Helper.underscore(handler_class.name).subscribe(message_class.message_key)) do |info, payload|
|
220
|
+
#all message with the same handler goes here to try to maintain the order in the same queue (not ensured)
|
221
|
+
message_class_tmp = handler_class.message_class_by_key[info.routing_key]
|
222
|
+
handler_class.handle(message_class_tmp.unmarshall(payload))
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def publish(message)
|
228
|
+
AmqpNotifier.new(exchange).publish(message.class.message_key, message.marshall)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
data/lib/lolitra/version.rb
CHANGED
data/lib/lolitra.rb
CHANGED
data/lolitra.gemspec
CHANGED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
class TestMessage
|
4
|
+
include Lolitra::AmqpMessage
|
5
|
+
|
6
|
+
message_key "test1"
|
7
|
+
|
8
|
+
def id
|
9
|
+
1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestMessage1
|
14
|
+
include Lolitra::AmqpMessage
|
15
|
+
|
16
|
+
message_key "test2"
|
17
|
+
|
18
|
+
def id
|
19
|
+
2
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestMessageHandler
|
24
|
+
include Lolitra::MessageHandler
|
25
|
+
|
26
|
+
started_by TestMessage
|
27
|
+
message_handler TestMessage
|
28
|
+
stateful true
|
29
|
+
|
30
|
+
def self.find_by_id(id)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_message(message)
|
35
|
+
"handled"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class TestMessageHandler1
|
40
|
+
include Lolitra::MessageHandler
|
41
|
+
|
42
|
+
started_by TestMessage1
|
43
|
+
message_handler TestMessage
|
44
|
+
message_handler TestMessage1
|
45
|
+
|
46
|
+
stateful true
|
47
|
+
|
48
|
+
def self.find_by_id(id)
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_message(message)
|
53
|
+
"handled"
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_message1(message)
|
57
|
+
"handled1"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class TestAmqpMessageHandler
|
63
|
+
include Lolitra::AmqpMessageHandler
|
64
|
+
|
65
|
+
message_handler TestMessage
|
66
|
+
message_handler TestMessage1
|
67
|
+
|
68
|
+
stateful false
|
69
|
+
end
|
70
|
+
|
71
|
+
describe Lolitra::MessageHandler,'#create_handler' do
|
72
|
+
it "returns old instance of the handler for the same message id" do
|
73
|
+
handler = TestMessageHandler.new
|
74
|
+
TestMessageHandler.should_receive(:new).exactly(1).and_return(handler)
|
75
|
+
TestMessageHandler.handle(TestMessage.new)
|
76
|
+
TestMessageHandler.should_receive(:find_by_id).at_least(:once).and_return(handler)
|
77
|
+
TestMessageHandler.handle(TestMessage.new)
|
78
|
+
TestMessageHandler.handle(TestMessage.new)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "create a new handler if the message starter has different id" do
|
82
|
+
handler1 = TestMessageHandler.new
|
83
|
+
message1 = TestMessage.new
|
84
|
+
message2 = TestMessage.new
|
85
|
+
message2.stub(:id => 2)
|
86
|
+
|
87
|
+
TestMessageHandler.should_receive(:new).exactly(2).and_return(handler1)
|
88
|
+
TestMessageHandler.handle(message1)
|
89
|
+
TestMessageHandler.handle(message2)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe Lolitra::MessageHandler, '#handle_message' do
|
94
|
+
it "handle message when message arrives" do
|
95
|
+
message_handler = TestMessageHandler1.new
|
96
|
+
TestMessageHandler1.stub(:find_by_id).and_return(nil, message_handler)
|
97
|
+
TestMessageHandler1.handle(TestMessage1.new).should eq "handled1"
|
98
|
+
TestMessageHandler1.handle(TestMessage.new).should eq "handled"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe Lolitra::MessageHandler, "#handle_message" do
|
103
|
+
it "handle non starter message thow execption" do
|
104
|
+
expect { TestMessageHandler1.handle(TestMessage.new) }.to raise_error(Lolitra::MessageHandler::NoHandlerMessageException)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe Lolitra::MessageHandler, '#publish' do
|
109
|
+
it "can send message to the bus" do
|
110
|
+
message = TestMessage.new
|
111
|
+
bus = TestBus.new
|
112
|
+
Lolitra::MessageHandlerManager.bus = bus
|
113
|
+
Lolitra::MessageHandlerManager.register(TestMessageHandler)
|
114
|
+
|
115
|
+
|
116
|
+
TestMessageHandler.should_receive(:handle).with(message)
|
117
|
+
TestMessageHandler.publish(message)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe Lolitra::MessageHandlerManager, '#publish' do
|
122
|
+
it "can send message to the bus" do
|
123
|
+
bus = TestBus.new
|
124
|
+
Lolitra::MessageHandlerManager.bus = bus
|
125
|
+
Lolitra::MessageHandlerManager.register(TestMessageHandler)
|
126
|
+
|
127
|
+
message = TestMessage.new
|
128
|
+
|
129
|
+
TestMessageHandler.should_receive(:handle).with(message)
|
130
|
+
|
131
|
+
Lolitra::MessageHandlerManager.publish(message)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe Lolitra::MessageHandlerManager, '#handle_message' do
|
136
|
+
it "handle message with the correct handler" do
|
137
|
+
|
138
|
+
bus = TestBus.new
|
139
|
+
|
140
|
+
Lolitra::MessageHandlerManager.bus = bus
|
141
|
+
|
142
|
+
Lolitra::MessageHandlerManager.register(TestMessageHandler)
|
143
|
+
Lolitra::MessageHandlerManager.register(TestMessageHandler1)
|
144
|
+
|
145
|
+
message = TestMessage.new
|
146
|
+
message1 = TestMessage1.new
|
147
|
+
|
148
|
+
TestMessageHandler.should_receive(:handle).with(message)
|
149
|
+
TestMessageHandler.should_not_receive(:handle).with(message1)
|
150
|
+
|
151
|
+
TestMessageHandler1.should_receive(:handle).with(message)
|
152
|
+
TestMessageHandler1.should_receive(:handle).with(message1)
|
153
|
+
|
154
|
+
Lolitra::MessageHandlerManager.publish(message)
|
155
|
+
Lolitra::MessageHandlerManager.publish(message1)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe Lolitra::AmqpMessage, '#message_key' do
|
160
|
+
it "message_key has constant key for a class" do
|
161
|
+
TestMessage.message_key.should eq "test1"
|
162
|
+
TestMessage1.message_key.should eq "test2"
|
163
|
+
TestMessage.message_key.should eq "test1"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe Lolitra::AmqpMessageHandler, '#message_class_by_key' do
|
168
|
+
it "should return the message_class that belongs to key" do
|
169
|
+
TestAmqpMessageHandler.message_class_by_key[TestMessage.message_key].name.should eq "TestMessage"
|
170
|
+
TestAmqpMessageHandler.message_class_by_key[TestMessage1.message_key].name.should eq "TestMessage1"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
#TODO
|
176
|
+
#add test to Lolitra::AmqpMessageHandler and AmqpBus with evented-spec
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:default, :test)
|
5
|
+
|
6
|
+
require "lolitra"
|
7
|
+
|
8
|
+
class TestBus
|
9
|
+
def initialize
|
10
|
+
@handlers = {}
|
11
|
+
@unmarshallers = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish_directly(message_key, message_payload)
|
15
|
+
publish(@unmarshallers[message_key].unmarshall(message_payload))
|
16
|
+
end
|
17
|
+
|
18
|
+
def publish(message)
|
19
|
+
@handlers[message.class.name].each do |handler|
|
20
|
+
handler.handle(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def subscribe(message_class, handler_class)
|
25
|
+
@unmarshallers[message_class.message_key] = message_class
|
26
|
+
@handlers[message_class.name] ||= []
|
27
|
+
@handlers[message_class.name] << handler_class
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Hugo Freire
|
@@ -14,10 +14,22 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-09-
|
17
|
+
date: 2012-09-19 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
21
33
|
description: Lolitra, build Sagas, a kind of Long Live Transaction (LLT), in less lines
|
22
34
|
email:
|
23
35
|
- hfreire@abajar.com
|
@@ -29,13 +41,17 @@ extra_rdoc_files: []
|
|
29
41
|
|
30
42
|
files:
|
31
43
|
- .gitignore
|
44
|
+
- .rspec
|
32
45
|
- Gemfile
|
33
46
|
- LICENSE
|
34
47
|
- README.md
|
35
48
|
- Rakefile
|
36
49
|
- lib/lolitra.rb
|
50
|
+
- lib/lolitra/handler_base.rb
|
37
51
|
- lib/lolitra/version.rb
|
38
52
|
- lolitra.gemspec
|
53
|
+
- spec/lolitra_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
39
55
|
has_rdoc: true
|
40
56
|
homepage: ""
|
41
57
|
licenses: []
|
@@ -68,5 +84,6 @@ rubygems_version: 1.3.7
|
|
68
84
|
signing_key:
|
69
85
|
specification_version: 3
|
70
86
|
summary: Lolitra, build Sagas, a kind of Long Live Transaction (LLT), in less lines
|
71
|
-
test_files:
|
72
|
-
|
87
|
+
test_files:
|
88
|
+
- spec/lolitra_spec.rb
|
89
|
+
- spec/spec_helper.rb
|