rservicebus 0.0.9 → 0.0.10

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/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