dispatch-rider 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +22 -0
- data/README.md +270 -0
- data/bin/dispatch_rider +17 -0
- data/lib/dispatch-rider.rb +21 -0
- data/lib/dispatch-rider/demultiplexer.rb +36 -0
- data/lib/dispatch-rider/dispatcher.rb +21 -0
- data/lib/dispatch-rider/errors.rb +34 -0
- data/lib/dispatch-rider/message.rb +33 -0
- data/lib/dispatch-rider/notification_services.rb +9 -0
- data/lib/dispatch-rider/notification_services/aws_sns.rb +23 -0
- data/lib/dispatch-rider/notification_services/base.rb +57 -0
- data/lib/dispatch-rider/notification_services/file_system.rb +22 -0
- data/lib/dispatch-rider/notification_services/file_system/channel.rb +16 -0
- data/lib/dispatch-rider/notification_services/file_system/notifier.rb +17 -0
- data/lib/dispatch-rider/publisher.rb +65 -0
- data/lib/dispatch-rider/queue_services.rb +10 -0
- data/lib/dispatch-rider/queue_services/aws_sqs.rb +39 -0
- data/lib/dispatch-rider/queue_services/aws_sqs/message_body_extractor.rb +17 -0
- data/lib/dispatch-rider/queue_services/base.rb +74 -0
- data/lib/dispatch-rider/queue_services/file_system.rb +39 -0
- data/lib/dispatch-rider/queue_services/file_system/queue.rb +42 -0
- data/lib/dispatch-rider/queue_services/simple.rb +30 -0
- data/lib/dispatch-rider/registrars.rb +13 -0
- data/lib/dispatch-rider/registrars/base.rb +39 -0
- data/lib/dispatch-rider/registrars/file_system_channel.rb +11 -0
- data/lib/dispatch-rider/registrars/handler.rb +10 -0
- data/lib/dispatch-rider/registrars/notification_service.rb +10 -0
- data/lib/dispatch-rider/registrars/publishing_destination.rb +9 -0
- data/lib/dispatch-rider/registrars/queue_service.rb +10 -0
- data/lib/dispatch-rider/registrars/sns_channel.rb +10 -0
- data/lib/dispatch-rider/subscriber.rb +41 -0
- data/lib/dispatch-rider/version.rb +4 -0
- 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
|