fare 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/README.md +470 -0
- data/Rakefile +28 -0
- data/bin/fare +48 -0
- data/fare.gemspec +35 -0
- data/features/multiqueue.feature +60 -0
- data/features/multistack.feature +65 -0
- data/features/step_definitions/aruba.rb +1 -0
- data/features/step_definitions/fare_steps.rb +40 -0
- data/features/subscriber.feature +95 -0
- data/features/support/env.rb +34 -0
- data/lib/fare.rb +96 -0
- data/lib/fare/configuration.rb +57 -0
- data/lib/fare/configuration_dsl.rb +134 -0
- data/lib/fare/configuration_when_locked.rb +82 -0
- data/lib/fare/event.rb +26 -0
- data/lib/fare/generate_lock_file.rb +131 -0
- data/lib/fare/load_configuration_file.rb +45 -0
- data/lib/fare/middleware/logging.rb +46 -0
- data/lib/fare/middleware/newrelic.rb +35 -0
- data/lib/fare/middleware/raven.rb +47 -0
- data/lib/fare/publisher.rb +65 -0
- data/lib/fare/queue_adapter.rb +30 -0
- data/lib/fare/rspec.rb +85 -0
- data/lib/fare/subscriber.rb +35 -0
- data/lib/fare/subscriber_cli.rb +270 -0
- data/lib/fare/subscriber_stack.rb +39 -0
- data/lib/fare/test_mode.rb +204 -0
- data/lib/fare/topic.rb +25 -0
- data/lib/fare/topic_adapter.rb +13 -0
- data/lib/fare/update_cli.rb +41 -0
- data/lib/fare/version.rb +3 -0
- data/spec/logger_spec.rb +45 -0
- data/spec/raven_spec.rb +52 -0
- data/spec/rspec_integration_spec.rb +45 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/stubbed_subscribing_spec.rb +66 -0
- metadata +264 -0
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'cucumber'
|
5
|
+
require 'cucumber/rake/task'
|
6
|
+
|
7
|
+
Cucumber::Rake::Task.new(:features)
|
8
|
+
|
9
|
+
rescue LoadError
|
10
|
+
task :features do
|
11
|
+
puts "Cucumber not installed."
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
require 'rspec/core/rake_task'
|
18
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
19
|
+
t.pattern = "spec"
|
20
|
+
end
|
21
|
+
rescue LoadError
|
22
|
+
task :spec do
|
23
|
+
puts "RSpec not installed"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => [:spec, :features]
|
data/bin/fare
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path("../../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require "fare"
|
7
|
+
require "optparse"
|
8
|
+
|
9
|
+
command = ARGV.shift
|
10
|
+
|
11
|
+
def show_help
|
12
|
+
puts <<-HELP.gsub(/^ +/, '')
|
13
|
+
Usage: fare COMMAND [OPTIONS]
|
14
|
+
|
15
|
+
Available commands:
|
16
|
+
|
17
|
+
fare update Parses your configuration file, creates topics, queus, subscriptions and caches it.
|
18
|
+
fare subscriber Controls the subscribers.
|
19
|
+
|
20
|
+
You can run any command with the --help option.
|
21
|
+
|
22
|
+
Example: fare subscriber --help
|
23
|
+
HELP
|
24
|
+
end
|
25
|
+
|
26
|
+
case command
|
27
|
+
when "update"
|
28
|
+
require "fare/update_cli"
|
29
|
+
Fare::UpdateCLI.new(ARGV).call
|
30
|
+
when "subscriber"
|
31
|
+
require "fare/subscriber_cli"
|
32
|
+
Fare::SubscriberCLI.new(ARGV).call
|
33
|
+
when "-v", "--version"
|
34
|
+
puts "Fare version #{Fare::VERSION}"
|
35
|
+
exit 0
|
36
|
+
when "help", "--help", "-h"
|
37
|
+
show_help
|
38
|
+
exit 0
|
39
|
+
when "", nil
|
40
|
+
show_help
|
41
|
+
exit 1
|
42
|
+
else
|
43
|
+
puts "Unkown command: #{command}"
|
44
|
+
puts
|
45
|
+
show_help
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
data/fare.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fare/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fare"
|
8
|
+
spec.version = Fare::VERSION
|
9
|
+
spec.authors = ["iain"]
|
10
|
+
spec.email = ["iain@iain.nl"]
|
11
|
+
spec.description = %q{An event system built on Amazon SNS and SQS}
|
12
|
+
spec.summary = %q{An event system built on Amazon SNS and SQS}
|
13
|
+
spec.homepage = "https://github.com/yourkarma/fare"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "aws-sdk"
|
21
|
+
spec.add_dependency "daemonic", "~> 0.1.2"
|
22
|
+
spec.add_dependency "virtus"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "thin"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "fake_sqs", "~> 0.0.10"
|
29
|
+
spec.add_development_dependency "fake_sns", "~> 0.0.2"
|
30
|
+
|
31
|
+
spec.add_development_dependency "cucumber"
|
32
|
+
spec.add_development_dependency "aruba"
|
33
|
+
spec.add_development_dependency "json_expressions"
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: Multiqueue
|
2
|
+
|
3
|
+
Scenario: Different subscribers for different queues
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
publishes subject: "user", action: "signup", version: "0.1"
|
11
|
+
|
12
|
+
subscriber do
|
13
|
+
|
14
|
+
setup do
|
15
|
+
require File.expand_path("../../middleware", __FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
stack do
|
19
|
+
listen_to subject: "user", action: "login"
|
20
|
+
run do
|
21
|
+
use RegisterEvent, name: "default-queue"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
subscriber :other_queue do
|
28
|
+
|
29
|
+
setup do
|
30
|
+
require File.expand_path("../../middleware", __FILE__)
|
31
|
+
end
|
32
|
+
|
33
|
+
stack do
|
34
|
+
listen_to subject: "user", action: "login"
|
35
|
+
run do
|
36
|
+
use RegisterEvent, name: "other-queue"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
"""
|
42
|
+
|
43
|
+
And a file named "middleware.rb" with:
|
44
|
+
"""
|
45
|
+
class RegisterEvent
|
46
|
+
def initialize(app, options = {})
|
47
|
+
@app = app
|
48
|
+
@options = options
|
49
|
+
end
|
50
|
+
def call(env)
|
51
|
+
puts "Registered event by #{@options[:name]}"
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
"""
|
56
|
+
|
57
|
+
When I publish an event with the subject "user" and action "login"
|
58
|
+
And I run `fare subscriber start --name other_queue`
|
59
|
+
Then the output should contain "Registered event by other-queue"
|
60
|
+
But the output should not contain "Registered event by default-queue"
|
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Multistack
|
2
|
+
|
3
|
+
Scenario: Different stacks for different messages
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
publishes subject: "user", action: "signup", version: "0.1"
|
11
|
+
|
12
|
+
subscriber do
|
13
|
+
|
14
|
+
setup do
|
15
|
+
require File.expand_path("../../middleware", __FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
stack do
|
19
|
+
listen_to subject: "user", action: "login"
|
20
|
+
run do
|
21
|
+
use TestForLogin
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
stack do
|
26
|
+
listen_to subject: "user", action: "signup"
|
27
|
+
run do
|
28
|
+
use TestForSignup
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
"""
|
34
|
+
|
35
|
+
And a file named "middleware.rb" with:
|
36
|
+
"""
|
37
|
+
class TestForLogin
|
38
|
+
def initialize(app)
|
39
|
+
@app = app
|
40
|
+
end
|
41
|
+
def call(env)
|
42
|
+
puts "Received Login"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class TestForSignup
|
48
|
+
def initialize(app)
|
49
|
+
@app = app
|
50
|
+
end
|
51
|
+
def call(env)
|
52
|
+
puts "Received Signup"
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
"""
|
57
|
+
|
58
|
+
When I publish an event with the subject "user" and action "login"
|
59
|
+
And I run `fare subscriber start`
|
60
|
+
Then the output should contain "Received Login"
|
61
|
+
But the output should not contain "Received Signup"
|
62
|
+
|
63
|
+
When I publish an event with the subject "user" and action "signup"
|
64
|
+
And I run `fare subscriber start`
|
65
|
+
Then the output should contain "Received Signup"
|
@@ -0,0 +1 @@
|
|
1
|
+
require "aruba/cucumber"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Given(/^the Fare config:$/) do |string|
|
2
|
+
|
3
|
+
extra = %Q|require File.expand_path("../../helper.rb", __FILE__)|
|
4
|
+
|
5
|
+
write_file "config/fare.rb", "#{extra}\n#{string}"
|
6
|
+
|
7
|
+
write_file "helper.rb", <<-HELPER
|
8
|
+
|
9
|
+
AWS.config(
|
10
|
+
use_ssl: false,
|
11
|
+
sqs_endpoint: "localhost",
|
12
|
+
sqs_port: 4568,
|
13
|
+
sns_endpoint: "localhost",
|
14
|
+
sns_port: 9293,
|
15
|
+
access_key_id: "fake access key",
|
16
|
+
secret_access_key: "fake secret key",
|
17
|
+
)
|
18
|
+
|
19
|
+
HELPER
|
20
|
+
|
21
|
+
run_simple("fare update")
|
22
|
+
end
|
23
|
+
|
24
|
+
When(/^I give SNS time to propagate the messages$/) do
|
25
|
+
$fake_sns.drain
|
26
|
+
end
|
27
|
+
|
28
|
+
When(/^I publish an event with the subject "(.*?)" and action "(.*?)"$/) do |subject, action|
|
29
|
+
write_file "publisher.rb", <<-PUBLISHER
|
30
|
+
require "bundler/setup"
|
31
|
+
Bundler.require(:default, :test)
|
32
|
+
Fare.publish(subject: #{subject.inspect}, action: #{action.inspect}, payload: "payload")
|
33
|
+
PUBLISHER
|
34
|
+
|
35
|
+
run_simple "ruby publisher.rb"
|
36
|
+
|
37
|
+
$fake_sns.drain
|
38
|
+
$fake_sns.reset
|
39
|
+
run_simple("fare update --force")
|
40
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
Feature: Subscriber
|
2
|
+
|
3
|
+
Scenario: Running in the foreground
|
4
|
+
|
5
|
+
Given the Fare config:
|
6
|
+
"""
|
7
|
+
app_name "my_app"
|
8
|
+
|
9
|
+
publishes subject: "user", action: "login", version: "0.1"
|
10
|
+
|
11
|
+
subscriber do
|
12
|
+
|
13
|
+
stack do
|
14
|
+
listen_to subject: "user", action: "login"
|
15
|
+
|
16
|
+
run do
|
17
|
+
use Class.new {
|
18
|
+
|
19
|
+
def initialize(app, options = {})
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
event = env.fetch(:event)
|
25
|
+
puts "Event received: #{event.subject} - #{event.action}"
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
"""
|
35
|
+
|
36
|
+
When I publish an event with the subject "user" and action "login"
|
37
|
+
|
38
|
+
And I run `fare subscriber start`
|
39
|
+
|
40
|
+
Then the output should contain "Event received: user - login"
|
41
|
+
|
42
|
+
Scenario: Multiple Middleware
|
43
|
+
|
44
|
+
Given the Fare config:
|
45
|
+
"""
|
46
|
+
app_name "my_app"
|
47
|
+
|
48
|
+
publishes subject: "user", action: "login", version: "0.1"
|
49
|
+
|
50
|
+
subscriber do
|
51
|
+
|
52
|
+
setup do
|
53
|
+
require File.expand_path("../../middleware", __FILE__)
|
54
|
+
end
|
55
|
+
|
56
|
+
stack do
|
57
|
+
listen_to subject: "user", action: "login"
|
58
|
+
|
59
|
+
run do
|
60
|
+
use TestMiddlewareOne
|
61
|
+
use TestMiddlewareTwo
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
"""
|
67
|
+
|
68
|
+
And a file named "middleware.rb" with:
|
69
|
+
"""
|
70
|
+
class TestMiddlewareOne
|
71
|
+
def initialize(app)
|
72
|
+
@app = app
|
73
|
+
end
|
74
|
+
def call(env)
|
75
|
+
env[:one] = "Extra Data"
|
76
|
+
@app.call(env)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class TestMiddlewareTwo
|
81
|
+
def initialize(app)
|
82
|
+
@app = app
|
83
|
+
end
|
84
|
+
def call(env)
|
85
|
+
puts "Received data: #{env[:one]}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end
|
89
|
+
"""
|
90
|
+
|
91
|
+
When I publish an event with the subject "user" and action "login"
|
92
|
+
|
93
|
+
And I run `fare subscriber start`
|
94
|
+
|
95
|
+
Then the output should contain "Received data: Extra Data"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
ENV["RACK_ENV"] = "test"
|
2
|
+
require "bundler/setup"
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
|
5
|
+
require "fare"
|
6
|
+
|
7
|
+
require "fake_sns/test_integration"
|
8
|
+
require "fake_sqs/test_integration"
|
9
|
+
|
10
|
+
AWS.config(
|
11
|
+
use_ssl: false,
|
12
|
+
sqs_endpoint: "localhost",
|
13
|
+
sqs_port: 4568,
|
14
|
+
sns_endpoint: "localhost",
|
15
|
+
sns_port: 9293,
|
16
|
+
access_key_id: "fake access key",
|
17
|
+
secret_access_key: "fake secret key",
|
18
|
+
)
|
19
|
+
|
20
|
+
$fake_sns = FakeSNS::TestIntegration.new(database: ":memory:")
|
21
|
+
$fake_sqs = FakeSQS::TestIntegration.new(database: ":memory:")
|
22
|
+
|
23
|
+
$fake_sns.start
|
24
|
+
$fake_sqs.start
|
25
|
+
|
26
|
+
at_exit {
|
27
|
+
$fake_sns.stop
|
28
|
+
$fake_sqs.stop
|
29
|
+
}
|
30
|
+
|
31
|
+
Before do
|
32
|
+
$fake_sns.reset
|
33
|
+
$fake_sqs.reset
|
34
|
+
end
|
data/lib/fare.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
require "verbose_hash_fetch"
|
3
|
+
require "base64"
|
4
|
+
require "json"
|
5
|
+
require "yaml"
|
6
|
+
require "digest/md5"
|
7
|
+
require "virtus"
|
8
|
+
require "forwardable"
|
9
|
+
|
10
|
+
require "fare/version"
|
11
|
+
|
12
|
+
require "fare/load_configuration_file"
|
13
|
+
require "fare/configuration_dsl"
|
14
|
+
require "fare/configuration"
|
15
|
+
require "fare/subscriber_stack"
|
16
|
+
require "fare/generate_lock_file"
|
17
|
+
require "fare/topic"
|
18
|
+
|
19
|
+
require "fare/queue_adapter"
|
20
|
+
require "fare/topic_adapter"
|
21
|
+
|
22
|
+
require "fare/configuration_when_locked"
|
23
|
+
require "fare/publisher"
|
24
|
+
|
25
|
+
require "fare/subscriber"
|
26
|
+
require "fare/event"
|
27
|
+
|
28
|
+
require "fare/middleware/logging"
|
29
|
+
require "fare/middleware/raven"
|
30
|
+
|
31
|
+
module Fare
|
32
|
+
ConfigurationNotFoundError = Class.new(RuntimeError)
|
33
|
+
LockFileNotFoundError = Class.new(RuntimeError)
|
34
|
+
ChecksumNotMatchingError = Class.new(RuntimeError)
|
35
|
+
NoEnvironmentFoundError = Class.new(RuntimeError)
|
36
|
+
|
37
|
+
class << self
|
38
|
+
attr_accessor :queue_adapter, :topic_adapter
|
39
|
+
end
|
40
|
+
self.queue_adapter = QueueAdapter
|
41
|
+
self.topic_adapter = TopicAdapter
|
42
|
+
|
43
|
+
def self.test_mode!
|
44
|
+
require "fare/test_mode"
|
45
|
+
@message_list = TestMode::MessageList.new
|
46
|
+
self.queue_adapter = @message_list.queue_adapter
|
47
|
+
self.topic_adapter = @message_list.topic_adapter
|
48
|
+
update(force: true)
|
49
|
+
@configuration = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.stubbed_messages
|
53
|
+
if @message_list
|
54
|
+
@message_list
|
55
|
+
else
|
56
|
+
fail "Not in test mode. Enable it with: Fare.test_mode!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.given_event(*args)
|
61
|
+
stubbed_messages.given_event(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.run(*args)
|
65
|
+
stubbed_messages.run(*args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.default_config_file
|
69
|
+
File.expand_path("config/fare.rb", Dir.pwd)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.default_environment
|
73
|
+
env = ENV["RACK_ENV"] || ENV["RAILS_ENV"]
|
74
|
+
if env.nil? && defined?(::Rails)
|
75
|
+
env = Rails.env
|
76
|
+
end
|
77
|
+
env
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.update(options = {})
|
81
|
+
GenerateLockFile.new(options).call
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.publish(options)
|
85
|
+
Publisher.new(configuration, options).call
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.configuration
|
89
|
+
@configuration ||= config({})
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.config(options)
|
93
|
+
@configuration = ConfigurationWhenLocked.new(options)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|