dispatch-rider 0.0.3

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