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