dispatch-rider 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/LICENSE.txt +22 -0
  2. data/README.md +270 -0
  3. data/bin/dispatch_rider +17 -0
  4. data/lib/dispatch-rider.rb +21 -0
  5. data/lib/dispatch-rider/demultiplexer.rb +36 -0
  6. data/lib/dispatch-rider/dispatcher.rb +21 -0
  7. data/lib/dispatch-rider/errors.rb +34 -0
  8. data/lib/dispatch-rider/message.rb +33 -0
  9. data/lib/dispatch-rider/notification_services.rb +9 -0
  10. data/lib/dispatch-rider/notification_services/aws_sns.rb +23 -0
  11. data/lib/dispatch-rider/notification_services/base.rb +57 -0
  12. data/lib/dispatch-rider/notification_services/file_system.rb +22 -0
  13. data/lib/dispatch-rider/notification_services/file_system/channel.rb +16 -0
  14. data/lib/dispatch-rider/notification_services/file_system/notifier.rb +17 -0
  15. data/lib/dispatch-rider/publisher.rb +65 -0
  16. data/lib/dispatch-rider/queue_services.rb +10 -0
  17. data/lib/dispatch-rider/queue_services/aws_sqs.rb +39 -0
  18. data/lib/dispatch-rider/queue_services/aws_sqs/message_body_extractor.rb +17 -0
  19. data/lib/dispatch-rider/queue_services/base.rb +74 -0
  20. data/lib/dispatch-rider/queue_services/file_system.rb +39 -0
  21. data/lib/dispatch-rider/queue_services/file_system/queue.rb +42 -0
  22. data/lib/dispatch-rider/queue_services/simple.rb +30 -0
  23. data/lib/dispatch-rider/registrars.rb +13 -0
  24. data/lib/dispatch-rider/registrars/base.rb +39 -0
  25. data/lib/dispatch-rider/registrars/file_system_channel.rb +11 -0
  26. data/lib/dispatch-rider/registrars/handler.rb +10 -0
  27. data/lib/dispatch-rider/registrars/notification_service.rb +10 -0
  28. data/lib/dispatch-rider/registrars/publishing_destination.rb +9 -0
  29. data/lib/dispatch-rider/registrars/queue_service.rb +10 -0
  30. data/lib/dispatch-rider/registrars/sns_channel.rb +10 -0
  31. data/lib/dispatch-rider/subscriber.rb +41 -0
  32. data/lib/dispatch-rider/version.rb +4 -0
  33. metadata +236 -0
@@ -0,0 +1,9 @@
1
+ # The namespace that holds the notification services services
2
+ module DispatchRider
3
+ module NotificationServices
4
+ end
5
+ end
6
+
7
+ require "dispatch-rider/notification_services/base"
8
+ require "dispatch-rider/notification_services/aws_sns"
9
+ require "dispatch-rider/notification_services/file_system"
@@ -0,0 +1,23 @@
1
+ # This is a basic implementation of the Notification service using Amazon SNS.
2
+ # The expected usage is as follows :
3
+ # notification_service = DispatchRider::NotificationServices::AwsSns.new
4
+ # notification_service.publish(:to => [:foo, :oof], :message => {:subject => "bar", :body => "baz"})
5
+ module DispatchRider
6
+ module NotificationServices
7
+ class AwsSns < Base
8
+ def notifier_builder
9
+ AWS::SNS
10
+ rescue NameError
11
+ raise AdapterNotFoundError.new(self.class.name, 'aws-sdk')
12
+ end
13
+
14
+ def channel_registrar_builder
15
+ Registrars::SnsChannel
16
+ end
17
+
18
+ def channel(name)
19
+ notifier.topics[self.fetch(name)]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ # This base class provides an interface that we can implement
2
+ # to generate a wrapper around a notification service.
3
+ # The expected usage is as follows :
4
+ # notification_service = DispatchRider::NotificationServices::Base.new
5
+ # notification_service.publish(:to => [:foo, :oof], :message => {:subject => "bar", :body => "baz"})
6
+ module DispatchRider
7
+ module NotificationServices
8
+ class Base
9
+ extend Forwardable
10
+
11
+ attr_reader :notifier, :channel_registrar
12
+
13
+ def_delegators :channel_registrar, :register, :fetch, :unregister
14
+
15
+ def initialize(options = {})
16
+ @notifier = notifier_builder.new(options)
17
+ @channel_registrar = channel_registrar_builder.new
18
+ end
19
+
20
+ def notifier_builder
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def channel_registrar_builder
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def publish(options)
29
+ channels(options[:to]).each do |channel|
30
+ channel.publish(serialize(message(options[:message])))
31
+ end
32
+ end
33
+
34
+ def channels(names)
35
+ Array(names).map { |name| channel(name) }
36
+ end
37
+
38
+ def channel(name)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def message_builder
43
+ DispatchRider::Message
44
+ end
45
+
46
+ private
47
+
48
+ def message(attrs)
49
+ message_builder.new(attrs)
50
+ end
51
+
52
+ def serialize(item)
53
+ item.to_json
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,22 @@
1
+ # this is a basic notification service which uses a filesystem folder to handle notifications
2
+
3
+ module DispatchRider
4
+ module NotificationServices
5
+ class FileSystem < Base
6
+ def notifier_builder
7
+ Notifier
8
+ end
9
+
10
+ def channel_registrar_builder
11
+ DispatchRider::Registrars::FileSystemChannel
12
+ end
13
+
14
+ def channel(name)
15
+ notifier.channel(self.fetch(name))
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'dispatch-rider/notification_services/file_system/channel'
22
+ require 'dispatch-rider/notification_services/file_system/notifier'
@@ -0,0 +1,16 @@
1
+ # this represents a FileSystem queue channel (or basically a folder)
2
+
3
+ module DispatchRider
4
+ module NotificationServices
5
+ class FileSystem::Channel
6
+
7
+ def initialize(path)
8
+ @file_system_queue = DispatchRider::QueueServices::FileSystem::Queue.new(path)
9
+ end
10
+
11
+ def publish(message)
12
+ @file_system_queue.add(message.to_json)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # This is abstraction around a notifier service for FileSystem based queue services
2
+
3
+ module DispatchRider
4
+ module NotificationServices
5
+ class FileSystem::Notifier
6
+
7
+ def initialize(options)
8
+ # nothing to do here
9
+ end
10
+
11
+ def channel(path)
12
+ FileSystem::Channel.new(path)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+ # This class takes care of the publishing side of the messaging system.
2
+ module DispatchRider
3
+ class Publisher
4
+ attr_reader :service_channel_mapper, :notification_service_registrar, :publishing_destination_registrar, :sns_channel_registrar
5
+
6
+ def initialize
7
+ @notification_service_registrar = DispatchRider::Registrars::NotificationService.new
8
+ @publishing_destination_registrar = DispatchRider::Registrars::PublishingDestination.new
9
+ @service_channel_mapper = ServiceChannelMapper.new(publishing_destination_registrar)
10
+ end
11
+
12
+ def register_notification_service(name, options = {})
13
+ notification_service_registrar.register(name, options)
14
+ self
15
+ end
16
+
17
+ def register_destination(name, service, channel, options = {})
18
+ register_channel(service, channel, options)
19
+ publishing_destination_registrar.register(name, :service => service, :channel => channel)
20
+ self
21
+ end
22
+
23
+ def register_channel(service, name, options = {})
24
+ notification_service = notification_service_registrar.fetch(service)
25
+ notification_service.register(name, options)
26
+ self
27
+ end
28
+
29
+ def publish(opts = {})
30
+ options = opts.dup
31
+ service_channel_mapper.map(options.delete(:destinations)).each do |(service, channels)|
32
+ notification_service_registrar.fetch(service).publish(options.merge(:to => channels))
33
+ end
34
+ end
35
+
36
+ class ServiceChannelMapper
37
+ attr_reader :destination_registrar
38
+
39
+ def initialize(destination_registrar)
40
+ @destination_registrar = destination_registrar
41
+ end
42
+
43
+ def map(names)
44
+ services_and_channels_map(publishing_destinations(names))
45
+ end
46
+
47
+ private
48
+
49
+ def publishing_destinations(names)
50
+ Array(names).map { |name| destination_registrar.fetch(name) }
51
+ end
52
+
53
+ def services_and_channels_map(destinations)
54
+ destinations.reduce({}) do |result, destination|
55
+ if result.has_key?(destination.service)
56
+ result[destination.service] << destination.channel
57
+ else
58
+ result[destination.service] = [destination.channel]
59
+ end
60
+ result
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,10 @@
1
+ # The namespace that holds the queue services
2
+ module DispatchRider
3
+ module QueueServices
4
+ end
5
+ end
6
+
7
+ require "dispatch-rider/queue_services/base"
8
+ require "dispatch-rider/queue_services/simple"
9
+ require "dispatch-rider/queue_services/aws_sqs"
10
+ require "dispatch-rider/queue_services/file_system"
@@ -0,0 +1,39 @@
1
+ # This queue service is based on aws sqs.
2
+ # To make this queue service work, one would need the aws sqs gem to be installed.
3
+ module DispatchRider
4
+ module QueueServices
5
+ class AwsSqs < Base
6
+ require "dispatch-rider/queue_services/aws_sqs/message_body_extractor"
7
+
8
+ def assign_storage(attrs)
9
+ begin
10
+ AWS::SQS.new.queues.named(attrs.fetch(:name))
11
+ rescue NameError
12
+ raise AdapterNotFoundError.new(self.class.name, 'aws-sdk')
13
+ rescue IndexError
14
+ raise RecordInvalid.new(self, ["Name can not be blank"])
15
+ end
16
+ end
17
+
18
+ def insert(item)
19
+ queue.send_message(item)
20
+ end
21
+
22
+ def raw_head
23
+ queue.receive_message
24
+ end
25
+
26
+ def construct_message_from(item)
27
+ deserialize(MessageBodyExtractor.new(item).extract)
28
+ end
29
+
30
+ def delete(item)
31
+ item.delete
32
+ end
33
+
34
+ def size
35
+ queue.approximate_number_of_messages
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ module DispatchRider
2
+ module QueueServices
3
+ class AwsSqs < Base
4
+ class MessageBodyExtractor
5
+ attr_reader :parsed_message
6
+
7
+ def initialize(raw_message)
8
+ @parsed_message = JSON.parse(raw_message.body)
9
+ end
10
+
11
+ def extract
12
+ parsed_message.has_key?("Message") ? parsed_message["Message"] : parsed_message.to_json
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ # This is the base class that provides the template for all queue services.
2
+ # The child classes must implement the following methods to become a concrete class :
3
+ # assign_storage, insert, raw_head, construct_message_from, delete and size.
4
+ # The instances of this class or it's child classes are supposed to perform the following actions on the queue service :
5
+ # initialize, push, pop and empty?
6
+ module DispatchRider
7
+ module QueueServices
8
+ class Base
9
+ attr_accessor :queue
10
+
11
+ def initialize(options = {})
12
+ @queue = assign_storage(options.symbolize_keys)
13
+ end
14
+
15
+ def assign_storage(attrs)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def push(item)
20
+ message = serialize(item)
21
+ insert(message)
22
+ message
23
+ end
24
+
25
+ def insert(item)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def pop(&block)
30
+ obj = head
31
+ if obj
32
+ block.call(obj.message) && delete(obj.item)
33
+ obj.message
34
+ end
35
+ end
36
+
37
+ def head
38
+ raw_item = raw_head
39
+ raw_item && OpenStruct.new(:item => raw_item, :message => construct_message_from(raw_item))
40
+ end
41
+
42
+ def raw_head
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def construct_message_from(item)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def delete(item)
51
+ raise NotImplementedError
52
+ end
53
+
54
+ def empty?
55
+ size.zero?
56
+ end
57
+
58
+ def size
59
+ raise NotImplementedError
60
+ end
61
+
62
+ protected
63
+
64
+ def serialize(item)
65
+ item.to_json
66
+ end
67
+
68
+ def deserialize(item)
69
+ attrs = JSON.parse(item).symbolize_keys
70
+ DispatchRider::Message.new(attrs)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ # This is a rudementary queue service that uses file system instead of
2
+ # AWS::SQS or SimpleQueue. It addresses SimpleQueue's inability to be used
3
+ # by only one application instance while avoiding the cost of setting up AWS::SQS.
4
+ # This is ideal to be used in development mode between multiple applications.
5
+ module DispatchRider
6
+ module QueueServices
7
+ require "dispatch-rider/queue_services/file_system/queue"
8
+ class FileSystem < Base
9
+ def assign_storage(attrs)
10
+ begin
11
+ path = attrs.fetch(:path)
12
+ Queue.new(path)
13
+ rescue IndexError
14
+ raise RecordInvalid.new(self, ["Path can not be blank"])
15
+ end
16
+ end
17
+
18
+ def insert(item)
19
+ queue.add item
20
+ end
21
+
22
+ def raw_head
23
+ queue.pop
24
+ end
25
+
26
+ def construct_message_from(item)
27
+ deserialize(item.read)
28
+ end
29
+
30
+ def delete(item)
31
+ queue.remove item
32
+ end
33
+
34
+ def size
35
+ queue.size
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ # This is a queue implementation for the queue service based on file systems
2
+ module DispatchRider
3
+ module QueueServices
4
+ class FileSystem < Base
5
+ class Queue
6
+ def initialize(path)
7
+ FileUtils.mkdir_p(path)
8
+ @path = path
9
+ end
10
+
11
+ def add(item)
12
+ name_base = "#{@path}/#{Time.now.to_f}"
13
+ File.open("#{name_base}.inprogress", "w"){ |f| f.write(item) }
14
+ FileUtils.mv("#{name_base}.inprogress", "#{name_base}.ready")
15
+ end
16
+
17
+ def pop
18
+ file_path = file_paths.first
19
+ return nil unless file_path
20
+ file_path_inflight = file_path.gsub(/\.ready$/, '.inflight')
21
+ FileUtils.mv(file_path, file_path_inflight)
22
+ File.new(file_path_inflight)
23
+ end
24
+
25
+ def remove(item)
26
+ item.close
27
+ File.unlink(item.path)
28
+ end
29
+
30
+ def size
31
+ file_paths.size
32
+ end
33
+
34
+ private
35
+
36
+ def file_paths
37
+ Dir["#{@path}/*.ready"]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end