rservicebus 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,10 +1,6 @@
1
1
  #RServiceBus
2
2
 
3
- A Ruby implementation of NServiceBus
4
-
5
- Where I started this project to increase my knowledge of NServiceBus, it is now
6
- proving to be a workable framework.
7
-
3
+ A Ruby interpretation of NServiceBus
8
4
 
9
5
  ##Principles
10
6
  *Why are you doing it and what can go wrong
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "amqp"
4
+ require "yaml"
5
+
6
+ require "rservicebus"
7
+
8
+ host = 'localhost:11300'
9
+ errorQueueName = "error"
10
+ beanstalk = Beanstalk::Pool.new([host])
11
+
12
+ tubes = beanstalk.list_tubes[host]
13
+ if !tubes.include?(errorQueueName) then
14
+ abort( "Nothing waiting on the error queue" )
15
+ end
16
+
17
+ tubeStats = beanstalk.stats_tube(errorQueueName)
18
+ number_of_messages = tubeStats["current-jobs-ready"]
19
+ puts
20
+ puts "Attempting to return #{number_of_messages} to their source queue"
21
+ puts
22
+
23
+ begin
24
+ beanstalk.watch(errorQueueName)
25
+ 1.upto(number_of_messages) do |request_nbr|
26
+ job = beanstalk.reserve 1
27
+ payload = job.body
28
+
29
+ puts "#" + request_nbr.to_s + ": " + payload
30
+ msg = YAML::load(payload)
31
+ queueName = msg.getLastErrorMsg.sourceQueue
32
+
33
+ beanstalk.use( queueName )
34
+ beanstalk.put( payload )
35
+
36
+ job.delete
37
+ end
38
+ rescue Exception => e
39
+ if e.message == "TIMED_OUT" then
40
+ else
41
+ raise
42
+ end
43
+ end
@@ -1,3 +1,5 @@
1
+ module RServiceBus
2
+
1
3
  require "redis"
2
4
 
3
5
  #Implementation of an AppResource - Redis
@@ -15,3 +17,5 @@ class AppResource_Redis<AppResource
15
17
  end
16
18
 
17
19
  end
20
+
21
+ end
@@ -1,3 +1,5 @@
1
+ module RServiceBus
2
+
1
3
  require "uri"
2
4
 
3
5
  # Wrapper base class for resources used by applications, allowing rservicebus to configure the resource
@@ -20,3 +22,5 @@ class AppResource
20
22
  raise "Method, getResource, needs to be implemented for resource"
21
23
  end
22
24
  end
25
+
26
+ end
@@ -2,7 +2,7 @@ module RServiceBus
2
2
 
3
3
  #Marshals configuration information for an rservicebus host
4
4
  class Config
5
- attr_reader :appName, :messageEndpointMappings, :handlerPathList, :localQueueName, :errorQueueName, :maxRetries, :forwardReceivedMessagesTo, :verbose, :beanstalkHost
5
+ attr_reader :appName, :messageEndpointMappings, :handlerPathList, :localQueueName, :errorQueueName, :maxRetries, :forwardReceivedMessagesTo, :verbose, :beanstalkHost, :queueTimeout
6
6
 
7
7
  @appName
8
8
  @messageEndpointMappings
@@ -17,6 +17,8 @@ class Config
17
17
 
18
18
  @beanstalkHost
19
19
 
20
+ @queueTimeout
21
+
20
22
  def initialize()
21
23
  puts "Cannot instantiate config directly."
22
24
  puts "For production, use ConfigFromEnv."
@@ -85,6 +87,7 @@ class Config
85
87
  @errorQueueName = self.getValue( "ERROR_QUEUE_NAME", "error" )
86
88
  @maxRetries = self.getValue( "MAX_RETRIES", "5" ).to_i
87
89
  @forwardReceivedMessagesTo = self.getValue( "FORWARD_RECEIVED_MESSAGES_TO" )
90
+ @queueTimeout = self.getValue( "QUEUE_TIMEOUT", "5" ).to_i
88
91
 
89
92
  return self
90
93
  end
@@ -1,3 +1,5 @@
1
+ module RServiceBus
2
+
1
3
  require "uri"
2
4
 
3
5
  #Configure AppResources for an rservicebus host
@@ -24,3 +26,5 @@ class ConfigureAppResource
24
26
  end
25
27
 
26
28
  end
29
+
30
+ end
@@ -2,9 +2,11 @@ module RServiceBus
2
2
 
3
3
  class ErrorMessage
4
4
 
5
- attr_reader :sourceQueue, :errorMsg
5
+ attr_reader :occurredAt, :sourceQueue, :errorMsg
6
6
 
7
7
  def initialize( sourceQueue, errorMsg )
8
+ @occurredAt = DateTime.now
9
+
8
10
  @sourceQueue=sourceQueue
9
11
  @errorMsg=errorMsg
10
12
  end
@@ -3,7 +3,6 @@ module RServiceBus
3
3
  #Host process for rservicebus
4
4
  class Host
5
5
 
6
-
7
6
  @handlerList
8
7
 
9
8
  @forwardReceivedMessagesToQueue
@@ -15,6 +14,10 @@ class Host
15
14
  @appResources
16
15
 
17
16
  @config
17
+
18
+ @subscriptionManager
19
+
20
+ @stats
18
21
 
19
22
  def log(string, ver=false)
20
23
  type = ver ? "VERB" : "INFO"
@@ -77,46 +80,20 @@ class Host
77
80
  return self
78
81
  end
79
82
 
80
- #Load an existing subscription - startup function
81
- def loadSubscriptions
82
- log "Load subscriptions"
83
- @subscriptions = Hash.new
83
+ def configureSubscriptions
84
+ subscriptionStorage = SubscriptionStorage_Redis.new( @config.appName, "uri" )
85
+ @subscriptionManager = SubscriptionManager.new( subscriptionStorage )
84
86
 
85
- begin
86
- redis = Redis.new
87
-
88
- prefix = @config.appName + ".Subscriptions."
89
- subscriptions = redis.keys prefix + "*Event"
90
- rescue Exception => e
91
- puts "Error connecting to redis"
92
- # puts "Host string, #{@config.beanstalkHost}"
93
- if e.message == "Redis::CannotConnectError" ||
94
- e.message == "Redis::ECONNREFUSED" then
95
- puts "***Most likely, redis is not running. Start redis, and try running this again."
96
- # puts "***If you still get this error, check redis is running at, " + beanstalkHost
97
- else
98
- puts e.message
99
- puts e.backtrace
100
- end
101
- abort()
102
- end
103
-
104
- subscriptions.each do |subscriptionName|
105
- log "Loading subscription: " + subscriptionName, true
106
- eventName = subscriptionName.sub( prefix, "" )
107
- @subscriptions[eventName] = Array.new
87
+ return self
88
+ end
108
89
 
109
- log "Loading for event: " + eventName, true
110
- subscription = redis.smembers subscriptionName
111
- subscription.each do |subscriber|
112
- log "Loading subscriber, " + subscriber + " for event, " + eventName, true
113
- @subscriptions[eventName] << subscriber
114
- end
115
- end
90
+ def configureStatistics
91
+ @stats = Stats.new
116
92
 
117
93
  return self
118
94
  end
119
95
 
96
+
120
97
  def initialize()
121
98
 
122
99
  @config = ConfigFromEnv.new
@@ -127,28 +104,16 @@ class Host
127
104
  .loadMessageEndpointMappings()
128
105
  .loadHandlerPathList();
129
106
 
130
- self.configureAppResource()
107
+ self.configureStatistics()
108
+ .configureAppResource()
131
109
  .connectToBeanstalk()
132
110
  .loadHandlers()
133
- .loadSubscriptions()
111
+ .configureSubscriptions()
134
112
  .sendSubscriptions()
135
113
 
136
114
  return self
137
115
  end
138
116
 
139
- #Process a subscription request from a subscriber
140
- def addSubscrption( eventName, queueName )
141
- log "Adding subscrption for, " + eventName + ", to, " + queueName
142
- redis = Redis.new
143
- key = @config.appName + ".Subscriptions." + eventName
144
- redis.sadd key, queueName
145
-
146
- if @subscriptions[eventName].nil? then
147
- @subscriptions[eventName] = Array.new
148
- end
149
- @subscriptions[eventName] << queueName
150
- end
151
-
152
117
  def run
153
118
  log "Starting the Host"
154
119
 
@@ -161,59 +126,75 @@ class Host
161
126
  self.StartListeningToEndpoints
162
127
  end
163
128
 
164
-
129
+ #Receive a msg, prep it, and handle any errors that may occur
130
+ # - Most of this should be queue independant
165
131
  def StartListeningToEndpoints
166
132
  log "Waiting for messages. To exit press CTRL+C"
167
133
 
168
134
  loop do
169
135
  retries = @config.maxRetries
136
+ #Popping a msg off the queue should not be in the message handler, as it affects retry
170
137
  begin
171
- job = @beanstalk.reserve
172
- body = job.body
173
-
174
- @msg = YAML::load(body)
175
- if @msg.msg.class.name == "RServiceBus::Subscription" then
176
- self.addSubscrption( @msg.msg.eventName, @msg.returnAddress )
177
- else
178
- self.HandleMessage()
179
- if !@config.forwardReceivedMessagesTo.nil? then
180
- self._SendAlreadyWrappedAndSerialised(body,@config.forwardReceivedMessagesTo)
138
+ log @stats.getForReporting
139
+ job = @beanstalk.reserve @config.queueTimeout
140
+ begin
141
+ body = job.body
142
+ @stats.incTotalProcessed
143
+ @msg = YAML::load(body)
144
+ if @msg.msg.class.name == "RServiceBus::Message_Subscription" then
145
+ @subscriptionManager.add( @msg.msg.eventName, @msg.returnAddress )
146
+
147
+ else
148
+ self.HandleMessage()
149
+ if !@config.forwardReceivedMessagesTo.nil? then
150
+ self._SendAlreadyWrappedAndSerialised(body,@config.forwardReceivedMessagesTo)
151
+ end
152
+ end
153
+ job.delete
154
+ rescue Exception => e
155
+ sleep 0.5
156
+ retry if (retries -= 1) > 0
157
+
158
+ @stats.incTotalErrored
159
+ if e.class.name == "Beanstalk::NotConnected" then
160
+ puts "Lost connection to beanstalkd."
161
+ puts "*** Start or Restart beanstalkd and try again."
162
+ abort();
181
163
  end
182
- end
183
- job.delete
184
- rescue Exception => e
185
- sleep 0.5
186
- retry if (retries -= 1) > 0
187
-
188
- if e.class.name == "Beanstalk::NotConnected" then
189
- puts "Lost connection to beanstalkd."
190
- puts "*** Start or Restart beanstalkd and try again."
191
- abort();
192
- end
193
164
 
194
- if e.class.name == "Redis::CannotConnectError" then
195
- puts "Lost connection to redis."
196
- puts "*** Start or Restart redis and try again."
197
- abort();
198
- end
165
+ if e.class.name == "Redis::CannotConnectError" then
166
+ puts "Lost connection to redis."
167
+ puts "*** Start or Restart redis and try again."
168
+ abort();
169
+ end
199
170
 
200
- errorString = e.message + ". " + e.backtrace[0]
171
+ errorString = e.message + ". " + e.backtrace[0]
201
172
  if e.backtrace.length > 1 then
202
173
  errorString = errorString + ". " + e.backtrace[1]
203
174
  end
204
175
  if e.backtrace.length > 2 then
205
176
  errorString = errorString + ". " + e.backtrace[2]
206
177
  end
207
- log errorString
178
+ log errorString
208
179
 
209
- @msg.addErrorMsg( @config.localQueueName, errorString )
210
- serialized_object = YAML::dump(@msg)
211
- self._SendAlreadyWrappedAndSerialised(serialized_object, @config.errorQueueName)
212
- job.delete
213
- end
180
+ @msg.addErrorMsg( @config.localQueueName, errorString )
181
+ serialized_object = YAML::dump(@msg)
182
+ self._SendAlreadyWrappedAndSerialised(serialized_object, @config.errorQueueName)
183
+ job.delete
184
+ end
185
+ rescue Exception => e
186
+ if e.message == "TIMED_OUT" then
187
+ else
188
+ puts "*** This is really unexpected."
189
+ puts e.message
190
+ puts e.backtrace
191
+ abort()
192
+ end
193
+ end
214
194
  end
215
195
  end
216
196
 
197
+ #Send the current msg to the appropriate handlers
217
198
  def HandleMessage()
218
199
  msgName = @msg.msg.class.name
219
200
  handlerList = @handlerList[msgName]
@@ -228,6 +209,7 @@ class Host
228
209
  handler.Handle( @msg.msg )
229
210
  rescue Exception => e
230
211
  log "An error occured in Handler: " + handler.class.name
212
+ log e.message + ". " + e.backtrace[0]
231
213
  raise e
232
214
  end
233
215
  end
@@ -265,6 +247,7 @@ class Host
265
247
  # @param [RServiceBus::Message] msg msg to be sent
266
248
  def Reply( msg )
267
249
  log "Reply with: " + msg.class.name + " To: " + @msg.returnAddress, true
250
+ @stats.incTotalReply
268
251
 
269
252
  self._SendNeedsWrapping( msg, @msg.returnAddress )
270
253
  end
@@ -276,6 +259,7 @@ class Host
276
259
  # @param [RServiceBus::Message] msg msg to be sent
277
260
  def Send( msg )
278
261
  log "Bus.Send", true
262
+ @stats.incTotalSent
279
263
 
280
264
  msgName = msg.class.name
281
265
  if !@config.messageEndpointMappings.has_key?( msgName ) then
@@ -294,14 +278,10 @@ class Host
294
278
  # @param [RServiceBus::Message] msg msg to be sent
295
279
  def Publish( msg )
296
280
  log "Bus.Publish", true
281
+ @stats.incTotalPublished
297
282
 
298
- subscription = @subscriptions[msg.class.name]
299
- if subscription.nil? then
300
- log "No subscribers for event, " + msg.class.name
301
- return
302
- end
303
-
304
- subscription.each do |subscriber|
283
+ subscriptions = @subscriptionManager.get(msg.class.name)
284
+ subscriptions.each do |subscriber|
305
285
  self._SendNeedsWrapping( msg, subscriber )
306
286
  end
307
287
 
@@ -315,7 +295,7 @@ class Host
315
295
 
316
296
 
317
297
  queueName = @config.messageEndpointMappings[eventName]
318
- subscription = Subscription.new( eventName )
298
+ subscription = Message_Subscription.new( eventName )
319
299
 
320
300
 
321
301
  self._SendNeedsWrapping( subscription, queueName )
@@ -0,0 +1,13 @@
1
+ module RServiceBus
2
+
3
+
4
+ class Message_Subscription
5
+ attr_reader :eventName
6
+
7
+ def initialize( eventName )
8
+ @eventName=eventName
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -13,6 +13,8 @@ class Message
13
13
  @_msg=YAML::dump(msg)
14
14
  @returnAddress=returnAddress
15
15
 
16
+ @createdAt = DateTime.now
17
+
16
18
  @msgId=UUIDTools::UUID.random_create
17
19
  @errorList = Array.new
18
20
  end
@@ -0,0 +1,69 @@
1
+ module RServiceBus
2
+
3
+ #Used to collect various run time stats for runtime reporting
4
+ class Stats
5
+
6
+ def initialize
7
+ @hash = Hash.new
8
+
9
+ @totalProcessed = 0
10
+ @totalErrored = 0
11
+ @totalSent = 0
12
+ @totalPublished = 0
13
+ @totalReply = 0
14
+
15
+ @totalByMessageType = Hash.new
16
+ end
17
+
18
+ def incTotalProcessed
19
+ @totalProcessed = @totalProcessed + 1
20
+ end
21
+
22
+ def incTotalErrored
23
+ @totalErrored = @totalErrored + 1
24
+ end
25
+
26
+ def incTotalSent
27
+ @totalSent = @totalSent + 1
28
+ end
29
+
30
+ def incTotalPublished
31
+ @totalPublished = @totalPublished + 1
32
+ end
33
+
34
+ def incTotalReply
35
+ @totalReply = @totalReply + 1
36
+ end
37
+
38
+ def inc( key )
39
+ if @hash[key].nil? then
40
+ @hash[key] = 0
41
+ end
42
+ @hash[key] = @hash[key] + 1
43
+ end
44
+
45
+ def incMessageType( className )
46
+ if @totalByMessageType[className].nil? then
47
+ @totalByMessageType[className] = 1
48
+ else
49
+ @totalByMessageType[className] = @totalByMessageType[className] + 1
50
+ end
51
+
52
+ end
53
+
54
+ def getForReporting
55
+ string = "T:#{@totalProcessed};E:#{@totalErrored};S:#{@totalSent};P:#{@totalPublished};R:#{@totalReply}"
56
+
57
+ if @hash.length > 0 then
58
+ @hash.each do |k,v|
59
+ string = "#{string};#{k}:#{v}"
60
+ end
61
+ end
62
+
63
+ return string
64
+ end
65
+
66
+ end
67
+
68
+
69
+ end
@@ -0,0 +1,34 @@
1
+ module RServiceBus
2
+
3
+ class SubscriptionManager
4
+
5
+ @subscriptionStorage
6
+ @subscriptions
7
+
8
+ def initialize( subscriptionStorage )
9
+ @subscriptionStorage = subscriptionStorage
10
+ @subscriptions = @subscriptionStorage.getAll
11
+ end
12
+
13
+ #Get subscriptions for given eventName
14
+ def get( eventName )
15
+ subscriptions = @subscriptions[eventName]
16
+ if subscriptions.nil? then
17
+ RServiceBus.log "No subscribers for event, " + eventName
18
+ return Array.new
19
+ end
20
+
21
+ return subscriptions
22
+ end
23
+
24
+ def add( eventName, queueName )
25
+ RServiceBus.log "Adding subscrption for, " + eventName + ", to, " + queueName
26
+ @subscriptions = @subscriptionStorage.add( eventName, queueName )
27
+ end
28
+
29
+ def remove( eventName, queueName )
30
+ raise "Method, remove, needs to be implemented for this subscription storage"
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,65 @@
1
+ module RServiceBus
2
+
3
+ require "redis"
4
+
5
+ #Implementation of Subscription Storage to Redis
6
+ class SubscriptionStorage_Redis<SubscriptionStorage
7
+
8
+ @redis
9
+
10
+ def initialize( appName, uri )
11
+ super(appName, uri)
12
+ @redis = Redis.new
13
+ end
14
+
15
+ def getAll
16
+ RServiceBus.log "Load subscriptions"
17
+ begin
18
+ content = @redis.get( @appName + ".Subscriptions" )
19
+ if content.nil? then
20
+ subscriptions = Hash.new
21
+ else
22
+ subscriptions = YAML::load(content)
23
+ end
24
+ return subscriptions
25
+ rescue Exception => e
26
+ puts "Error connecting to redis"
27
+ if e.message == "Redis::CannotConnectError" ||
28
+ e.message == "Redis::ECONNREFUSED" then
29
+ puts "***Most likely, redis is not running. Start redis, and try running this again."
30
+ else
31
+ puts e.message
32
+ puts e.backtrace
33
+ end
34
+ abort()
35
+ end
36
+ end
37
+
38
+ def add( eventName, queueName )
39
+ # RServiceBus.log "Storing subscrption for, " + eventName + ", to, " + queueName
40
+ content = @redis.get( @appName + ".Subscriptions" )
41
+ if content.nil? then
42
+ subscriptions = Hash.new
43
+ else
44
+ subscriptions = YAML::load(content)
45
+ end
46
+
47
+ if subscriptions[eventName].nil? then
48
+ subscriptions[eventName] = Array.new
49
+ end
50
+
51
+ subscriptions[eventName] << queueName
52
+ subscriptions[eventName] = subscriptions[eventName].uniq
53
+
54
+ @redis.set( @appName + ".Subscriptions", YAML::dump(subscriptions ) )
55
+
56
+ return subscriptions
57
+ end
58
+
59
+ def remove( eventName, queueName )
60
+ raise "Method, remove, needs to be implemented for this subscription storage"
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,30 @@
1
+ module RServiceBus
2
+
3
+ require "uri"
4
+
5
+ # Base class for subscription storage
6
+ #
7
+ class SubscriptionStorage
8
+ @appName
9
+
10
+ # Specified using URI.
11
+ #
12
+ # @param [String] uri a location for the resource to which we will attach, eg redis://127.0.0.1/foo
13
+ def initialize(appName, uri)
14
+ @appName = appName
15
+ end
16
+
17
+ def getAll
18
+ raise "Method, getResource, needs to be implemented for resource"
19
+ end
20
+
21
+ def add( eventName, queueName )
22
+ raise "Method, add, needs to be implemented for this subscription storage"
23
+ end
24
+
25
+ def remove( eventName, queueName )
26
+ raise "Method, remove, needs to be implemented for this subscription storage"
27
+ end
28
+ end
29
+
30
+ end
@@ -13,4 +13,12 @@ module RServiceBus
13
13
  return hash.to_json
14
14
  end
15
15
 
16
+ def RServiceBus.log(string, ver=false)
17
+ type = ver ? "VERB" : "INFO"
18
+ # if @config.verbose || !ver then
19
+ timestamp = Time.new.strftime( "%Y-%m-%d %H:%M:%S" )
20
+ puts "[#{type}] #{timestamp} :: #{string}"
21
+ # end
22
+ end
23
+
16
24
  end
data/lib/rservicebus.rb CHANGED
@@ -8,15 +8,22 @@ require "json"
8
8
  require "rservicebus/helper_functions"
9
9
  require "rservicebus/Agent"
10
10
  require "rservicebus/ErrorMessage"
11
- require "rservicebus/Message"
12
11
  require "rservicebus/HandlerLoader"
13
12
  require "rservicebus/ConfigureAppResource"
14
13
  require "rservicebus/Host"
15
14
  require "rservicebus/Config"
15
+ require "rservicebus/Stats"
16
+
17
+ require "rservicebus/Message"
18
+ require "rservicebus/Message/Subscription"
16
19
 
17
20
  require "rservicebus/AppResource"
18
21
  require "rservicebus/AppResource/Redis"
19
22
 
23
+ require "rservicebus/SubscriptionManager"
24
+ require "rservicebus/SubscriptionStorage"
25
+ require "rservicebus/SubscriptionStorage/Redis"
26
+
20
27
 
21
28
  module RServiceBus
22
29
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rservicebus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,10 +11,11 @@ bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-06-25 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: A ruby implementation of NServiceBus
14
+ description: A Ruby interpretation of NServiceBus
15
15
  email: guy@guyirvine.com
16
16
  executables:
17
17
  - rservicebus
18
+ - ReturnErroredMessagesToSourceQueue
18
19
  extensions: []
19
20
  extra_rdoc_files: []
20
21
  files:
@@ -27,11 +28,17 @@ files:
27
28
  - lib/rservicebus/HandlerLoader.rb
28
29
  - lib/rservicebus/helper_functions.rb
29
30
  - lib/rservicebus/Host.rb
31
+ - lib/rservicebus/Message/Subscription.rb
30
32
  - lib/rservicebus/Message.rb
33
+ - lib/rservicebus/Stats.rb
34
+ - lib/rservicebus/SubscriptionManager.rb
35
+ - lib/rservicebus/SubscriptionStorage/Redis.rb
36
+ - lib/rservicebus/SubscriptionStorage.rb
31
37
  - lib/rservicebus/Test/Bus.rb
32
38
  - lib/rservicebus/Test/Redis.rb
33
39
  - lib/rservicebus/Test.rb
34
40
  - lib/rservicebus.rb
41
+ - bin/ReturnErroredMessagesToSourceQueue
35
42
  - bin/rservicebus
36
43
  - LICENSE
37
44
  - README.md