bi-frost 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61a3243740e3d642efe0184d5c64d4ad11e72bff
4
+ data.tar.gz: 3834b3af2f9541dd7222a2ebf3e266f9430db2ea
5
+ SHA512:
6
+ metadata.gz: 764a14af9175bf5ffd9f25d8e1d84aa6fd31988db87da0cca2db0bc262715d669b66e591892b6a7e7f017899ad6e870813781ac25dd98860e7c519faee77ecfa
7
+ data.tar.gz: 1319f4d4eb92d423485943ecdbdd941f68c78b9360b584213c442f1f8f5ea735e22e1f044e0a8e40e02d37c1f160ada2df9b96e89685ffdb2b22354a8a76b0a7
@@ -0,0 +1,144 @@
1
+ # Bifrost
2
+
3
+ Bifrost is a component which abstracts access to the Azure messaging bus. This is a lower level library which
4
+ can be used to commuicate with Azure via the Azure SDK.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'bi-frost'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ ## Usage
19
+
20
+ This component consists of 4 primary entities; Topic, Subscriber, Message and Listener.
21
+
22
+ Message's are what we deliver across various applications via an asynchronous pub/sub architecture. Messages are
23
+ posted to Topics. Topics are a collection of unique names which exist in a given Azure namespace. Each instance
24
+ of the Bifrost can only operate in a single Azure namespace. Topic names support alpha numeric characters only.
25
+
26
+ The architecture of this component is based on a fan out pub/sub model, i.e. a topic can have 1 or more subscribers,
27
+ each subscriber registers their interest in receiving messages posted to the topic. For more information on service
28
+ bus and how it works please refer to this [article](https://azure.microsoft.com/en-us/documentation/articles/service-bus-fundamentals-hybrid-solutions/#service-bus-fundamentals).
29
+
30
+ ### Configuration
31
+
32
+ To begin using this gem we need to start by setting some environment variables. The gem in development and
33
+ test environments supports .env files (i.e. .env.test and .env.development etc). The environment variables that
34
+ require to be set prior to using the gem are; NAMESPACE, KEY_NAME and KEY_SECRET. The values for these variables
35
+ can be obtained from your Azure management portal. For more information on how to obtain these values for your
36
+ service bus please refer to this [article](https://azure.microsoft.com/en-us/documentation/articles/service-bus-authentication-and-authorization/).
37
+
38
+ ### Examples
39
+
40
+ To define a topic to which messages can be delivered;
41
+
42
+ ```ruby
43
+ topic = Bifrost::Topic.new('topic_name')
44
+ topic.save
45
+ ```
46
+
47
+ The `save` method returns a `true` or `false` to indicate if the topic was successfully saved. A topic may not save
48
+ if it already exists.
49
+
50
+ To add a subscriber to a particular topic;
51
+
52
+ ```ruby
53
+ topic = Bifrost::Topic.new('topic_name')
54
+ topic.save
55
+ subscriber = Bifrost::Subscriber.new('new_subscriber')
56
+ topic.add_subscriber(subscriber)
57
+ ```
58
+
59
+ Each topic can only have a unique list of subscribers, a subscriber cannot be added to a topic more than once. When a subscriber is added to
60
+ a topic this function will return a true or false indicating success of the add.
61
+
62
+ To post a message to a topic;
63
+
64
+ ```ruby
65
+ topic = Bifrost::Topic.new('topic_name')
66
+ message = Bifrost::Message.new(content: 'some data')
67
+ message.post_to(topic)
68
+ ```
69
+
70
+ This function returns a `true` or `false` indicating the success of the message delivery. This method is synchronous. Each message has an
71
+ identifier which gets sets upon successful delivery only.
72
+
73
+ A message can also be optionally published with a subject;
74
+
75
+ ```ruby
76
+ topic = Bifrost::Topic.new('topic_name')
77
+ message = Bifrost::Message.new(content: 'some data', 'message subject')
78
+ message.post_to(topic)
79
+ ```
80
+
81
+ Subscribers in the Bifrost are [actors](http://http://doc.akka.io/docs/akka/2.4/general/actors.html), these actors run in
82
+ their own threads. At present the Bifrost does not support thread pools, this is something we are investigating and are
83
+ hoping to add at some point. In the Bifrost each actor is referred to as a `worker`. A worker is designed to receive
84
+ messages published to a particular topic with a specific subscriber in mind (refer to the fan-out comment earlier).
85
+
86
+ Workers are added to the Bifrost via the manager. The manager is what activates the workers in the Bifrost environment.
87
+ You can use multiple managers if you like at any given point in time to orchestrate the workers. These managers and workers
88
+ are only allowed to work in a single namespace at a time.
89
+
90
+ To setup a single worker to receive messages sent to a particular topic and subscriber;
91
+
92
+ ```ruby
93
+ manager = Bifrost::Manager.new
94
+ manager.add('topic_name', 'subscriber_name', proc { |m| puts "Received: message #{m}" })
95
+ manager.run
96
+ ```
97
+
98
+ To setup multiple workers to receive messages;
99
+
100
+ ```ruby
101
+ manager = Bifrost::Manager.new
102
+ manager.add('topic_name', 'subscriber_name', proc { |m| puts "Received: message #{m}" })
103
+ manager.add('another_topic_name', 'another_subscriber_name', proc { |m| puts "Received: message #{m}" })
104
+ manager.run
105
+ ```
106
+
107
+ Workers in the Bifrost are self healing, when an actor dies the manager receives a message alerting the manager
108
+ to the failure, the worker then restarts after the failure.
109
+
110
+ **We hope to introduce custom lambda functions in the future to support custom actions when a worker dies**
111
+
112
+ #Is it any good?
113
+
114
+ We are currently [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) this product in our production
115
+ environment. We will no doubt find issues and rectify this over time.
116
+
117
+ #Contributing
118
+
119
+ We'd love to have you involved. Please read our [contributing guide]() for information on how to get stuck in.
120
+
121
+ Contributors
122
+
123
+ This project is managed by the Filmpond team.
124
+
125
+ These individuals have come up with the ideas and written the code that made this possible:
126
+
127
+ 1. Shirren Premaratne
128
+ 2. Leslie Fung
129
+
130
+ #Licence
131
+
132
+ This program is free software: you can redistribute it and/or modify it under the terms of the MIT License.
133
+
134
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT License for more details.
135
+
136
+ The MIT License (MIT)
137
+
138
+ Copyright (C) 2016 Filmpond Pty Ltd
139
+
140
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
141
+
142
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
143
+
144
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ require 'bifrost/message'
2
+ require 'bifrost/topic'
3
+ require 'bifrost/subscriber'
4
+ require 'bifrost/manager'
5
+ require 'bifrost/worker'
6
+
7
+ # Bifrost is a pub/sub gem built on top of the Azure MessageBus system
8
+ module Bifrost
9
+ # Simple utlity that creates a topic and a single subscriber for the given
10
+ # topic. The topic is returned
11
+ def self.create_topic_with_subscriber(topic_name, sub_name)
12
+ topic = Bifrost::Topic.new(topic_name)
13
+ topic.save
14
+ subscriber = Bifrost::Subscriber.new(sub_name)
15
+ topic.add_subscriber(subscriber)
16
+ topic
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'Azure'
2
+
3
+ module Bifrost
4
+ # Types that require access to the message bus should inherit this type. We've chosen
5
+ # inheritance at this point, perhaps a mixin might be a better approach
6
+ class Entity
7
+ attr_reader :bus
8
+
9
+ def initialize
10
+ Azure.sb_namespace = ENV['NAMESPACE']
11
+ host = "https://#{Azure.sb_namespace}.servicebus.windows.net"
12
+ signer = Azure::ServiceBus::Auth::SharedAccessSigner.new(ENV['KEY_NAME'], ENV['KEY_SECRET'])
13
+ @bus ||= Azure::ServiceBus::ServiceBusService.new(host, signer: signer)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ module Bifrost
2
+ module Exceptions
3
+ class DuplicateSubscriberError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Bifrost
2
+ module Exceptions
3
+ class InvalidWorkerDefinitionError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Bifrost
2
+ module Exceptions
3
+ class UnsupportedLambdaError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,49 @@
1
+ require 'azure'
2
+ require 'celluloid/current'
3
+ require_relative 'exceptions/invalid_worker_definition_error'
4
+
5
+ module Bifrost
6
+ # This class is used to handle the setup and execution of multiple listeners
7
+ class Manager
8
+ include Celluloid
9
+
10
+ # The supervisor needs to be notified when a worker dies, it also needs to
11
+ # protect itself from harm
12
+ trap_exit :worker_died
13
+
14
+ # A supervised worker can be added to the current collection of supervised workers
15
+ # this also starts the actor
16
+ def add(topic_name, subscriber_name, proc)
17
+ if topic_name.nil? || subscriber_name.nil? || proc.nil?
18
+ raise InvalidWorkerDefinitionError, 'Invalid worker'
19
+ else
20
+ @supervisor = Worker.supervise(
21
+ as: Worker.supervisor_handle(topic_name, subscriber_name),
22
+ args: [topic_name, subscriber_name, proc]
23
+ )
24
+ # Link the worker to the supervisor so if the worker misbehaves the supervisor is alerted
25
+ # to this poor behaviour, the supervisor decides how to handle recovery
26
+ link(@supervisor.actors.last)
27
+ end
28
+ end
29
+
30
+ # When we run all the workers as actors in their own threads. This run also blocks to make sure
31
+ # the spawned threads remain operational indefinitely
32
+ def run
33
+ @supervisor.actors.each { |w| w.async.run }
34
+ # Put the supervisor thread to sleep indefinitely # Better way?
35
+ loop do
36
+ # TODO: Better way?
37
+ sleep(5)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # This callback function fires when an worker dies
44
+ def worker_died(worker, reason)
45
+ # TODO: Log this instead
46
+ puts "#{worker.inspect} has died: #{reason.class}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,46 @@
1
+ require 'azure'
2
+ require_relative 'entity'
3
+
4
+ module Bifrost
5
+ # A message is a letter that we send to a topic. It must contain a subject and body
6
+ # The receiver can process both fields on receipt of the message
7
+ class Message < Bifrost::Entity
8
+ attr_reader :subject, :body, :status, :message_id
9
+
10
+ # A message must have a valid subject and body. The service
11
+ # bus is initialised in the Entity class
12
+ def initialize(body, subject = nil)
13
+ @subject = subject || SecureRandom.base64
14
+ @body ||= body
15
+ @status ||= :undelivered
16
+ super()
17
+ end
18
+
19
+ # A message can be posted to a particular topic
20
+ def post_to(topic)
21
+ if topic.exists?
22
+ message = create_brokered_message
23
+ bus.send_topic_message(topic.name, message)
24
+ update_message_state_to_delivered(message)
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Create the brokered message
34
+ def create_brokered_message
35
+ message = Azure::ServiceBus::BrokeredMessage.new(subject, message: body)
36
+ message.correlation_id = SecureRandom.hex
37
+ message
38
+ end
39
+
40
+ # Update the status of the message post delivery
41
+ def update_message_state_to_delivered(message)
42
+ @status = :delivered
43
+ @message_id ||= message.correlation_id
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ require 'azure'
2
+
3
+ module Bifrost
4
+ # A subscriber is a type that registers interest in receiving messages posted to certain topics
5
+ class Subscriber
6
+ attr_reader :name, :options
7
+
8
+ def initialize(name, options = {})
9
+ @name ||= name
10
+ @options ||= options
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,75 @@
1
+ require 'azure'
2
+ require_relative 'entity'
3
+ require_relative 'exceptions/duplicate_subscriber_error'
4
+
5
+ module Bifrost
6
+ # Topics are central to the pub/sub system in the Bifrost. All messages must be delivered
7
+ # to a topic. The topic is responsible for forwarding the message to registered subscribers
8
+ class Topic < Entity
9
+ attr_reader :name, :options
10
+
11
+ def initialize(name, options = {})
12
+ @name ||= name
13
+ @options ||= options
14
+ super()
15
+ end
16
+
17
+ # If the topic has been defined this method returns true, if not
18
+ # it returns false
19
+ def exists?
20
+ bus.list_topics.each do |topic|
21
+ return true if topic.name == name
22
+ end
23
+ false
24
+ end
25
+
26
+ # If a topic has not been defined we can save it, so it becomes defined
27
+ def save
28
+ if exists?
29
+ false
30
+ else
31
+ bus.create_topic(name)
32
+ true
33
+ end
34
+ end
35
+
36
+ # If a topic is defined, we can remove the definition
37
+ def delete
38
+ if exists?
39
+ bus.delete_topic(name)
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ # A new subscriber can be added to a topic
47
+ def add_subscriber(subscriber)
48
+ if exists?
49
+ begin
50
+ bus.create_subscription(name, subscriber.name)
51
+ rescue Azure::Core::Http::HTTPError => e
52
+ raise Bifrost::Exceptions::DuplicateSubscriberError, "Duplicate subscriber for topic #{name}" if e.status_code == 409
53
+ end
54
+ true
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ # A topic subscriber can be removed from the topic if it exists
61
+ def remove_subscriber(subscriber)
62
+ if exists?
63
+ begin
64
+ bus.delete_subscription(name, subscriber.name)
65
+ rescue Azure::Core::Http::HTTPError => e
66
+ return false if e.status_code == 404
67
+ raise e
68
+ end
69
+ true
70
+ else
71
+ false
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ # Major.Minor.Patch version numbering
2
+ module Bifrost
3
+ # The major version of Sif, updated only for major changes that are
4
+ # likely to require modification to Filmpond apps.
5
+ MAJOR_VERSION = 0
6
+
7
+ # The minor version of Sif, updated for new feature releases.
8
+ MINOR_VERSION = 1
9
+
10
+ # The patch version of Sif, updated only for bug fixes from the last
11
+ # feature release.
12
+ PATCH_VERSION = 0
13
+
14
+ # The full version as a string.
15
+ VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}.#{PATCH_VERSION}".freeze
16
+ end
@@ -0,0 +1,59 @@
1
+ require 'azure'
2
+ require 'celluloid/current'
3
+ require_relative 'entity'
4
+ require_relative 'exceptions/unsupported_lambda_error'
5
+
6
+ module Bifrost
7
+ # This class is used to read messages from the subscriber, and process the messages one
8
+ # by one. This class is a worker/actor which focusses on processes a single topic/subscriber
9
+ # combination one at a time
10
+ class Worker < Bifrost::Entity
11
+ include Celluloid
12
+
13
+ attr_reader :topic_name, :subscriber_name, :callback
14
+
15
+ def initialize(topic_name, subscriber_name, callback)
16
+ raise Bifrost::Exceptions::UnsupportedLambdaError, 'not a proc' unless callback.respond_to?(:call)
17
+ @topic_name ||= topic_name
18
+ @subscriber_name ||= subscriber_name
19
+ @callback ||= callback
20
+ super()
21
+ end
22
+
23
+ # This method starts the actor, which runs in an infinite loop. This means the worker should
24
+ # not terminate, but if it does, the supervisor will make sure it restarts
25
+ def run
26
+ loop do
27
+ read_message
28
+ sleep(ENV['QUEUE_DELAY'] || 10)
29
+ end
30
+ end
31
+
32
+ # Workers have a friendly name which is a combination of the topic and subscriber name
33
+ # which in the operational environment should be unique
34
+ def to_s
35
+ Worker.supervisor_handle(topic_name, subscriber_name)
36
+ end
37
+
38
+ # Utlity method to get the name of the worker as a symbol
39
+ def to_sym
40
+ to_s.to_sym
41
+ end
42
+
43
+ # A worker can tell you what it's friendly name will be, this is in order for supervision
44
+ def self.supervisor_handle(topic_name, subscriber_name)
45
+ "#{topic_name}-#{subscriber_name}"
46
+ end
47
+
48
+ private
49
+
50
+ # Actual processing of the message
51
+ def read_message
52
+ message = bus.receive_subscription_message(topic_name, subscriber_name, timeout: ENV['TIMEOUT'] || 10)
53
+ if message
54
+ callback.call(message.properties['message'])
55
+ bus.delete_subscription_message(message)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'bifrost'
3
+
4
+ describe Bifrost::Manager do
5
+ let(:cb ) { proc { |m| puts "Principle Received: message #{m}" } }
6
+ let(:worker) { Bifrost::Worker.new('topic', 'subscriber', proc) }
7
+ let(:manager) { Bifrost::Manager.new }
8
+
9
+ skip 'should be able to help a faulty actor heal' do
10
+ manager.add('topicX', 'blah', proc { |m| puts "Secondary Received: message #{m}" })
11
+ manager.run
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'bifrost/message'
3
+ require 'bifrost/topic'
4
+ require 'bifrost/subscriber'
5
+
6
+ describe Bifrost::Message do
7
+ subject(:message) { Bifrost::Message.new({ content: 'some data' }, 'subscriber_name') }
8
+
9
+ it { is_expected.to respond_to(:subject) }
10
+ it { is_expected.to respond_to(:status) }
11
+ it { is_expected.to respond_to(:message_id) }
12
+ it { is_expected.to respond_to(:body) }
13
+ it { is_expected.to respond_to(:post_to) }
14
+
15
+ context 'which is initialized' do
16
+ it 'should be in an undelivered status' do
17
+ expect(message.status).to eq(:undelivered)
18
+ end
19
+ end
20
+
21
+ it 'should be able to auto generate a subject' do
22
+ new_message = Bifrost::Message.new(content: 'some data')
23
+ expect(new_message).to_not be_nil
24
+ end
25
+
26
+ it 'should be postable to a valid topic' do
27
+ topic = Bifrost::Topic.new('valid-topic')
28
+ topic.save
29
+ topic.add_subscriber(Bifrost::Subscriber.new('new_subscriber'))
30
+ expect(message.post_to(topic)).to be_truthy
31
+ expect(message.status).to eq(:delivered)
32
+ expect(message.message_id).not_to be_nil
33
+ topic.delete
34
+ end
35
+
36
+ it 'should not be postable to an invalid topic' do
37
+ topic = Bifrost::Topic.new('invalid-topic') # A topic that is neither saved, nor with no subscribers is semantically invalid
38
+ expect(message.post_to(topic)).to be_falsey
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'bifrost/subscriber'
3
+
4
+ describe Bifrost::Subscriber do
5
+ subject(:subscriber) { Bifrost::Subscriber.new('subscriber_name') }
6
+
7
+ it { is_expected.to respond_to(:name) }
8
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'bifrost/topic'
3
+ require 'bifrost/subscriber'
4
+
5
+ describe Bifrost::Topic do
6
+ subject(:topic) { Bifrost::Topic.new('topic_name') }
7
+
8
+ it { is_expected.to respond_to(:name) }
9
+ it { is_expected.to respond_to(:exists?) }
10
+ it { is_expected.to respond_to(:save) }
11
+ it { is_expected.to respond_to(:delete) }
12
+ it { is_expected.to respond_to(:add_subscriber) }
13
+ it { is_expected.to respond_to(:remove_subscriber) }
14
+
15
+ it 'should not support blank topic names' do
16
+ invalid_topic = Bifrost::Topic.new(nil)
17
+ expect { invalid_topic.save }.to raise_error(TypeError)
18
+ end
19
+
20
+ context 'for an undefined topic' do
21
+ it 'should return false for defined?' do
22
+ expect(topic.exists?).to be_falsey
23
+ end
24
+
25
+ it 'should persist the topic' do
26
+ new_topic = Bifrost::Topic.new('new_topic_name')
27
+ expect(new_topic.save).to be_truthy
28
+ expect(new_topic.delete).to be_truthy
29
+ end
30
+
31
+ it 'should return false for delete' do
32
+ expect(topic.delete).to be_falsey
33
+ end
34
+
35
+ it 'should not be able to add a new subscriber' do
36
+ subscriber = Bifrost::Subscriber.new('new_subscriber')
37
+ expect(topic.add_subscriber(subscriber)).to be_falsey
38
+ end
39
+
40
+ it 'should not be able to remove a subscriber' do
41
+ subscriber = Bifrost::Subscriber.new('new_subscriber')
42
+ expect(topic.remove_subscriber(subscriber)).to be_falsey
43
+ end
44
+ end
45
+
46
+ context 'for a defined topic' do
47
+ let(:new_topic) { Bifrost::Topic.new('test_donotdelete') }
48
+
49
+ before(:all) do
50
+ tp = Bifrost::Topic.new('test_donotdelete')
51
+ tp.save unless tp.exists?
52
+ end
53
+
54
+ after(:all) do
55
+ tp = Bifrost::Topic.new('test_donotdelete')
56
+ tp.delete if tp.exists?
57
+ end
58
+
59
+ it 'should return true for defined?' do
60
+ expect(new_topic.exists?).to be_truthy
61
+ end
62
+
63
+ it 'should return false for save' do
64
+ expect(new_topic.save).to be_falsey
65
+ end
66
+
67
+ it 'should be able to add and remove a new subscriber' do
68
+ subscriber = Bifrost::Subscriber.new('new_subscriber')
69
+ expect(new_topic.add_subscriber(subscriber)).to be_truthy
70
+ expect(new_topic.remove_subscriber(subscriber)).to be_truthy
71
+ end
72
+
73
+ it 'should not be able to remove a non existent subscriber' do
74
+ subscriber = Bifrost::Subscriber.new('non_existent_subscriber')
75
+ expect(new_topic.remove_subscriber(subscriber)).to be_falsey
76
+ end
77
+
78
+ it 'should not be able to add a duplicate subscriber' do
79
+ subscriber = Bifrost::Subscriber.new('another_new_subscriber')
80
+ expect(new_topic.add_subscriber(subscriber)).to be_truthy
81
+ expect { new_topic.add_subscriber(subscriber) }.to raise_error(Bifrost::Exceptions::DuplicateSubscriberError)
82
+ end
83
+ end
84
+
85
+ it 'should not accept names with spaces in them' do
86
+ invalid_topic_name = Bifrost::Topic.new('topic name')
87
+ expect { invalid_topic_name.save }.to raise_error(URI::InvalidURIError)
88
+ end
89
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bifrost do
4
+ it 'should have a version number' do
5
+ expect(Bifrost::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'bifrost/exceptions/unsupported_lambda_error'
3
+ require 'bifrost/message'
4
+ require 'bifrost/topic'
5
+ require 'bifrost/subscriber'
6
+ require 'bifrost/worker'
7
+
8
+ describe Bifrost::Worker do
9
+ let(:cb) { proc { |m| puts "Received: message #{m}" } }
10
+ subject(:worker) { Bifrost::Worker.new('topic', 'subscriber', cb) }
11
+
12
+ it { is_expected.to respond_to(:topic_name) }
13
+ it { is_expected.to respond_to(:subscriber_name) }
14
+
15
+ it 'should not except non procs in last argument' do
16
+ expect { Bifrost::Worker.new('topic', 'subscriber', 'x') }
17
+ .to raise_error(Bifrost::Exceptions::UnsupportedLambdaError)
18
+ end
19
+
20
+ it 'should have a friendly string name' do
21
+ expect(worker.to_s).to eq("#{worker.topic_name}-#{worker.subscriber_name}")
22
+ end
23
+
24
+ it 'should have a friendly symbol name' do
25
+ expect(worker.to_sym).to eq(worker.to_s.to_sym)
26
+ end
27
+
28
+ skip 'should be able to listen for messages' do
29
+ topic = Bifrost::Topic.new('topic')
30
+ topic.save
31
+ subscriber = Bifrost::Subscriber.new('subscriber')
32
+ topic.add_subscriber(subscriber)
33
+ msg = Bifrost::Message.new([item1: { data: 2 }, item2: { more_data: 3 }])
34
+ msg.post_to(topic)
35
+ expect(msg.status).to eq(:delivered)
36
+ expect(msg.message_id).not_to be_nil
37
+ worker.async.run
38
+ sleep
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ require 'azure'
2
+ require 'byebug'
3
+ require 'dotenv'
4
+ require 'simplecov'
5
+
6
+ SimpleCov.start do
7
+ add_filter '/spec/'
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ # Load environment variables for Azure
12
+ Dotenv.load('.env.test')
13
+
14
+ # All specs use this pre-defined namespace in Azure
15
+ Azure.sb_namespace = ENV['NAMESPACE']
16
+
17
+ # rspec-expectations config goes here. You can use an alternate
18
+ # assertion/expectation library such as wrong or the stdlib/minitest
19
+ # assertions if you prefer.
20
+ config.expect_with :rspec do |expectations|
21
+ # This option will default to `true` in RSpec 4. It makes the `description`
22
+ # and `failure_message` of custom matchers include text for helper methods
23
+ # defined using `chain`, e.g.:
24
+ # be_bigger_than(2).and_smaller_than(4).description
25
+ # # => "be bigger than 2 and smaller than 4"
26
+ # ...rather than:
27
+ # # => "be bigger than 2"
28
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
+ end
30
+
31
+ # rspec-mocks config goes here. You can use an alternate test double
32
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41
+ # have no way to turn it off -- the option exists only for backwards
42
+ # compatibility in RSpec 3). It causes shared context metadata to be
43
+ # inherited by the metadata hash of host groups and examples, rather than
44
+ # triggering implicit auto-inclusion in groups with matching metadata.
45
+ config.shared_context_metadata_behavior = :apply_to_host_groups
46
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bi-frost
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shirren Premaratne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: azure
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: celluloid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.17.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.17.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '9.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '9.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: dotenv
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: factory_girl_rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-mocks
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: shoulda-matchers
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.4'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.4'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.7'
153
+ description: Topic definitions should occur via the Bifrost. Subscriptions should
154
+ take place via the Bifrost. Therefore all messages need to transit through the Bifrost
155
+ email:
156
+ - shirren@filmpond.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - README.md
162
+ - lib/bifrost.rb
163
+ - lib/bifrost/entity.rb
164
+ - lib/bifrost/exceptions/duplicate_subscriber_error.rb
165
+ - lib/bifrost/exceptions/invalid_worker_definition_error.rb
166
+ - lib/bifrost/exceptions/unsupported_lambda_error.rb
167
+ - lib/bifrost/manager.rb
168
+ - lib/bifrost/message.rb
169
+ - lib/bifrost/subscriber.rb
170
+ - lib/bifrost/topic.rb
171
+ - lib/bifrost/version.rb
172
+ - lib/bifrost/worker.rb
173
+ - spec/bifrost/manager_spec.rb
174
+ - spec/bifrost/message_spec.rb
175
+ - spec/bifrost/subscriber_spec.rb
176
+ - spec/bifrost/topic_spec.rb
177
+ - spec/bifrost/version_spec.rb
178
+ - spec/bifrost/worker_spec.rb
179
+ - spec/spec_helper.rb
180
+ homepage: https://github.com/filmpond/bifrost
181
+ licenses: []
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 2.5.1
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: Bifrost is a simple pub/sub messaging wrapper component.
203
+ test_files:
204
+ - spec/bifrost/manager_spec.rb
205
+ - spec/bifrost/message_spec.rb
206
+ - spec/bifrost/subscriber_spec.rb
207
+ - spec/bifrost/topic_spec.rb
208
+ - spec/bifrost/version_spec.rb
209
+ - spec/bifrost/worker_spec.rb
210
+ - spec/spec_helper.rb