batsir 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +22 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +79 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/batsir.gemspec +104 -0
  10. data/batsir.png +0 -0
  11. data/lib/batsir.rb +73 -0
  12. data/lib/batsir/acceptors/acceptor.rb +45 -0
  13. data/lib/batsir/acceptors/amqp_acceptor.rb +19 -0
  14. data/lib/batsir/amqp.rb +45 -0
  15. data/lib/batsir/chain.rb +33 -0
  16. data/lib/batsir/config.rb +34 -0
  17. data/lib/batsir/dsl/dsl_mappings.rb +96 -0
  18. data/lib/batsir/filter.rb +12 -0
  19. data/lib/batsir/filter_queue.rb +30 -0
  20. data/lib/batsir/logo.rb +36 -0
  21. data/lib/batsir/notifiers/amqp_notifier.rb +16 -0
  22. data/lib/batsir/notifiers/notifier.rb +39 -0
  23. data/lib/batsir/registry.rb +15 -0
  24. data/lib/batsir/stage.rb +94 -0
  25. data/lib/batsir/stage_worker.rb +86 -0
  26. data/lib/batsir/transformers/field_transformer.rb +40 -0
  27. data/lib/batsir/transformers/json_input_transformer.rb +9 -0
  28. data/lib/batsir/transformers/json_output_transformer.rb +9 -0
  29. data/lib/batsir/transformers/transformer.rb +15 -0
  30. data/spec/batsir/acceptors/acceptor_spec.rb +136 -0
  31. data/spec/batsir/acceptors/amqp_acceptor_spec.rb +169 -0
  32. data/spec/batsir/chain_spec.rb +31 -0
  33. data/spec/batsir/dsl/chain_mapping_spec.rb +117 -0
  34. data/spec/batsir/dsl/stage_mapping_spec.rb +435 -0
  35. data/spec/batsir/filter_queue_spec.rb +74 -0
  36. data/spec/batsir/filter_spec.rb +12 -0
  37. data/spec/batsir/notifiers/amqp_notifier_spec.rb +117 -0
  38. data/spec/batsir/notifiers/notifier_spec.rb +73 -0
  39. data/spec/batsir/stage_spec.rb +678 -0
  40. data/spec/batsir/stage_worker_spec.rb +128 -0
  41. data/spec/batsir/support/bunny_mocks.rb +62 -0
  42. data/spec/batsir/support/mock_filters.rb +43 -0
  43. data/spec/batsir/transformers/field_transformer_spec.rb +73 -0
  44. data/spec/batsir/transformers/json_input_transformer_spec.rb +22 -0
  45. data/spec/batsir/transformers/json_output_transformer_spec.rb +18 -0
  46. data/spec/batsir/transformers/transformer_spec.rb +22 -0
  47. data/spec/spec_helper.rb +22 -0
  48. metadata +220 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --backtrace
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+
3
+ jdk:
4
+ - oraclejdk7
5
+
6
+ rvm:
7
+ - 1.9.2
8
+ - 1.9.3
9
+ - jruby-19mode
10
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ gem "bundler", "> 1.0.0"
9
+ gem "jeweler"
10
+ gem "blockenspiel", "~> 0.4.3"
11
+ gem "celluloid"
12
+ gem "sidekiq"
13
+ gem "bunny", :git => "https://github.com/ruby-amqp/bunny.git"
14
+ gem "json"
15
+ #gem "hot_bunnies", :git => "https://github.com/ruby-amqp/hot_bunnies.git"
16
+
17
+ group :development do
18
+ end
19
+
20
+ group :test do
21
+ gem "rspec"
22
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 J.W. Koelewijn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ [![Build Status](https://secure.travis-ci.org/jwkoelewijn/batsir.png?branch=master)](http://travis-ci.org/jwkoelewijn/batsir)
2
+
3
+
4
+ ![Batsir Logo](/jwkoelewijn/batsir/raw/master/batsir.png)
5
+
6
+ # Batsir
7
+ Batsir is an execution platform for stage based filter queue execution.
8
+ It is based on the Pipes and Filters patterns, and makes it possible to create
9
+ multiples Pipes and Filters (called Stages) that can be invoked asynchronously using so called
10
+ inbound Acceptors. Acceptors are started automatically and will send a payload
11
+ into the filter chain, after which the (possibly) transformed message will be
12
+ processed by the so called outbound Notifiers.
13
+ Notifiers can be used to asynchronously send a message to another stage, as long
14
+ as corresponding inbound Acceptors have been configured.
15
+
16
+ This makes it possible to create chains of stages to perform tasks that depend on each
17
+ other, but otherwise have a low coupling.
18
+
19
+ The usage of the Pipes and Filter pattern make it possible to thoroughly test each
20
+ filter in isolation, thus promoting fast test cycles.
21
+
22
+ # Example
23
+
24
+ ```ruby
25
+ Batsir.create_and_start do
26
+ stage "stage 1" do
27
+ inbound do
28
+ acceptor AMQPAcceptor, :queue => 'some_queue', :host => 'localhost'
29
+ end
30
+ filter SumFilter
31
+ filter AverageFilter
32
+ outbound do
33
+ notifier AMQPNotifier, :queue => 'queue_2'
34
+ end
35
+ end
36
+
37
+ stage "stage 2" do
38
+ inbound do
39
+ acceptor AMQPAcceptor, :queue => 'queue_2'
40
+ end
41
+ filter PrintFilter
42
+ end
43
+ end
44
+ ```
45
+
46
+ This example creates 2 stages, 'stage 1' and 'stage 2'. The first stage creates and AMQPAcceptor,
47
+ that will connect to a AMQP Broker on localhost and will listen for messages on the 'some_queue' queue.
48
+ When a message is received, the message will be offered to the SumFilter first. The result of the
49
+ SumFilter is then sent to the #execute method of the AverageFilter. The result of this filter will
50
+ than be sent as an AMQP message on the 'queue_2' queue.
51
+
52
+ The inbound AMQPAcceptor of the second stage will then receive the message and its filters will be
53
+ invoked (the PrintFilter in this example).
54
+
55
+ # Sidekiq & Celluloid
56
+ Batsir acts as both a Sidekiq server and client at the same time. When Batsir#create_and_start is invoked,
57
+ Batsir will create Sidekiq workers on the fly, with instantiated filters on the workers. These workers will
58
+ be deployed in the Sidekiq server, so that they will be available for processing. The workers also register
59
+ themselves in a registry, where they can be requested using the stage name.
60
+
61
+ The inbound acceptors will listen as a Celluloid Actor on the client side of Sidekiq. When a message is
62
+ received, it will look up the corresponding StageWorker in the registry and it will invoke the
63
+ StageWorker asynchronously using Sidekiq.
64
+
65
+ ## Contributing to batsir
66
+
67
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
68
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
69
+ * Fork the project
70
+ * Start a feature/bugfix branch
71
+ * Commit and push until you are happy with your contribution
72
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
73
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
74
+
75
+ ## Copyright
76
+
77
+ Copyright (c) 2012 J.W. Koelewijn. See LICENSE.txt for
78
+ further details.
79
+
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "batsir"
18
+ gem.homepage = "http://github.com/jwkoelewijn/batsir"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Batsir is an execution platform for stage based operation queue execution}
21
+ gem.description = %Q{Batsir uses so called stages to define operation queues. These operation queus
22
+ consist of several operations that will be executed one after the other. Each stage
23
+ is defined by its name and the queue on which it will listen. Once a message is received
24
+ on the queue, it is dispatched to a worker in a seperate thread that will pass the message
25
+ to each operation in the operation queue.
26
+ Operation queues can have 4 different operations, 1 common operation type, and 3 special
27
+ purpose operations: retrieval operations (which are always executed before all other operations),
28
+ persistence operations (which are always executed after the common operations, but before the
29
+ notification operations) and notification operations (which will always be executed last)
30
+ This makes it possible to create chains of stages to perform tasks that depend on each
31
+ other, but otherwise have a low coupling}
32
+ gem.email = "jwkoelewijn@gmail.com"
33
+ gem.authors = ["J.W. Koelewijn"]
34
+ # dependencies defined in Gemfile
35
+ end
36
+ Jeweler::RubygemsDotOrgTasks.new
37
+
38
+ require 'rspec/core/rake_task'
39
+
40
+ desc "Run specs"
41
+ RSpec::Core::RakeTask.new do |t|
42
+ t.pattern = 'spec/**/*_spec.rb'
43
+ end
44
+
45
+ task :default => :spec
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "batsir #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/batsir.gemspec ADDED
@@ -0,0 +1,104 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "batsir"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["J.W. Koelewijn"]
12
+ s.date = "2012-06-28"
13
+ s.description = "Batsir uses so called stages to define operation queues. These operation queus\n consist of several operations that will be executed one after the other. Each stage\n is defined by its name and the queue on which it will listen. Once a message is received\n on the queue, it is dispatched to a worker in a seperate thread that will pass the message\n to each operation in the operation queue.\n Operation queues can have 4 different operations, 1 common operation type, and 3 special \n purpose operations: retrieval operations (which are always executed before all other operations),\n persistence operations (which are always executed after the common operations, but before the\n notification operations) and notification operations (which will always be executed last)\n This makes it possible to create chains of stages to perform tasks that depend on each\n other, but otherwise have a low coupling"
14
+ s.email = "jwkoelewijn@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".travis.yml",
23
+ "Gemfile",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "batsir.gemspec",
29
+ "batsir.png",
30
+ "lib/batsir.rb",
31
+ "lib/batsir/acceptors/acceptor.rb",
32
+ "lib/batsir/acceptors/amqp_acceptor.rb",
33
+ "lib/batsir/amqp.rb",
34
+ "lib/batsir/chain.rb",
35
+ "lib/batsir/config.rb",
36
+ "lib/batsir/dsl/dsl_mappings.rb",
37
+ "lib/batsir/filter.rb",
38
+ "lib/batsir/filter_queue.rb",
39
+ "lib/batsir/logo.rb",
40
+ "lib/batsir/notifiers/amqp_notifier.rb",
41
+ "lib/batsir/notifiers/notifier.rb",
42
+ "lib/batsir/registry.rb",
43
+ "lib/batsir/stage.rb",
44
+ "lib/batsir/stage_worker.rb",
45
+ "lib/batsir/transformers/field_transformer.rb",
46
+ "lib/batsir/transformers/json_input_transformer.rb",
47
+ "lib/batsir/transformers/json_output_transformer.rb",
48
+ "lib/batsir/transformers/transformer.rb",
49
+ "spec/batsir/acceptors/acceptor_spec.rb",
50
+ "spec/batsir/acceptors/amqp_acceptor_spec.rb",
51
+ "spec/batsir/chain_spec.rb",
52
+ "spec/batsir/dsl/chain_mapping_spec.rb",
53
+ "spec/batsir/dsl/stage_mapping_spec.rb",
54
+ "spec/batsir/filter_queue_spec.rb",
55
+ "spec/batsir/filter_spec.rb",
56
+ "spec/batsir/notifiers/amqp_notifier_spec.rb",
57
+ "spec/batsir/notifiers/notifier_spec.rb",
58
+ "spec/batsir/stage_spec.rb",
59
+ "spec/batsir/stage_worker_spec.rb",
60
+ "spec/batsir/support/bunny_mocks.rb",
61
+ "spec/batsir/support/mock_filters.rb",
62
+ "spec/batsir/transformers/field_transformer_spec.rb",
63
+ "spec/batsir/transformers/json_input_transformer_spec.rb",
64
+ "spec/batsir/transformers/json_output_transformer_spec.rb",
65
+ "spec/batsir/transformers/transformer_spec.rb",
66
+ "spec/spec_helper.rb"
67
+ ]
68
+ s.homepage = "http://github.com/jwkoelewijn/batsir"
69
+ s.licenses = ["MIT"]
70
+ s.require_paths = ["lib"]
71
+ s.rubygems_version = "1.8.22"
72
+ s.summary = "Batsir is an execution platform for stage based operation queue execution"
73
+
74
+ if s.respond_to? :specification_version then
75
+ s.specification_version = 3
76
+
77
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
78
+ s.add_runtime_dependency(%q<bundler>, ["> 1.0.0"])
79
+ s.add_runtime_dependency(%q<jeweler>, [">= 0"])
80
+ s.add_runtime_dependency(%q<blockenspiel>, ["~> 0.4.3"])
81
+ s.add_runtime_dependency(%q<celluloid>, [">= 0"])
82
+ s.add_runtime_dependency(%q<sidekiq>, [">= 0"])
83
+ s.add_runtime_dependency(%q<bunny>, [">= 0"])
84
+ s.add_runtime_dependency(%q<json>, [">= 0"])
85
+ else
86
+ s.add_dependency(%q<bundler>, ["> 1.0.0"])
87
+ s.add_dependency(%q<jeweler>, [">= 0"])
88
+ s.add_dependency(%q<blockenspiel>, ["~> 0.4.3"])
89
+ s.add_dependency(%q<celluloid>, [">= 0"])
90
+ s.add_dependency(%q<sidekiq>, [">= 0"])
91
+ s.add_dependency(%q<bunny>, [">= 0"])
92
+ s.add_dependency(%q<json>, [">= 0"])
93
+ end
94
+ else
95
+ s.add_dependency(%q<bundler>, ["> 1.0.0"])
96
+ s.add_dependency(%q<jeweler>, [">= 0"])
97
+ s.add_dependency(%q<blockenspiel>, ["~> 0.4.3"])
98
+ s.add_dependency(%q<celluloid>, [">= 0"])
99
+ s.add_dependency(%q<sidekiq>, [">= 0"])
100
+ s.add_dependency(%q<bunny>, [">= 0"])
101
+ s.add_dependency(%q<json>, [">= 0"])
102
+ end
103
+ end
104
+
data/batsir.png ADDED
Binary file
data/lib/batsir.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'blockenspiel'
2
+ require 'celluloid'
3
+ require 'json'
4
+ require 'bunny'
5
+ require 'sidekiq'
6
+ require 'sidekiq/cli'
7
+ require 'batsir/registry'
8
+ require 'batsir/config'
9
+ require 'batsir/chain'
10
+ require 'batsir/filter'
11
+ require 'batsir/filter_queue'
12
+ require 'batsir/stage'
13
+ require 'batsir/stage_worker'
14
+ require 'batsir/dsl/dsl_mappings'
15
+ require 'batsir/acceptors/acceptor'
16
+ require 'batsir/acceptors/amqp_acceptor'
17
+ require 'batsir/notifiers/notifier'
18
+ require 'batsir/notifiers/amqp_notifier'
19
+ require 'batsir/transformers/transformer'
20
+ require 'batsir/transformers/field_transformer'
21
+ require 'batsir/transformers/json_input_transformer'
22
+ require 'batsir/transformers/json_output_transformer'
23
+ require 'batsir/logo'
24
+
25
+ module Batsir
26
+ def self.config
27
+ @config ||= Batsir::Config.new(config_defaults)
28
+ end
29
+
30
+ def self.config_defaults
31
+ {
32
+ :redis_url => "redis://localhost:6379/0"
33
+ }
34
+ end
35
+
36
+ def self.create(&block)
37
+ puts logo
38
+ new_block = ::Proc.new do
39
+ aggregator_chain &block
40
+ end
41
+ @chain = ::Blockenspiel.invoke(new_block, Batsir::DSL::ChainMapping.new)
42
+ end
43
+
44
+ def self.start
45
+ return unless @chain
46
+
47
+ sidekiq_cli = Sidekiq::CLI.instance
48
+ Sidekiq.options[:queues] << 'default'
49
+
50
+ initialize_sidekiq
51
+
52
+ generated_code = @chain.compile
53
+
54
+ eval(generated_code)
55
+
56
+ @chain.start
57
+ sidekiq_cli.run
58
+ end
59
+
60
+ def self.initialize_sidekiq
61
+ Sidekiq.configure_server do |config|
62
+ config.redis = {:url => Batsir.config.redis_url}
63
+ end
64
+ Sidekiq.configure_client do |config|
65
+ config.redis = {:url => Batsir.config.redis_url}
66
+ end
67
+ end
68
+
69
+ def self.create_and_start(&block)
70
+ create(&block)
71
+ start
72
+ end
73
+ end
@@ -0,0 +1,45 @@
1
+ module Batsir
2
+ module Acceptors
3
+ class Acceptor
4
+ include Celluloid
5
+
6
+ attr_accessor :stage_name
7
+ attr_accessor :transformer_queue
8
+ attr_accessor :cancellator
9
+
10
+ def initialize(options = {})
11
+ options.each do |option, value|
12
+ self.send("#{option}=", value)
13
+ end
14
+ @transformer_queue = []
15
+ end
16
+
17
+ def add_transformer(transformer)
18
+ @transformer_queue << transformer
19
+ end
20
+
21
+ # This method is called automatically when the stage is
22
+ # started, it is here that you set up the accepting
23
+ # logic. Make sure that somewhere within this logic
24
+ # the #start_filter_chain(msg) is called to start
25
+ # actual processing
26
+ #
27
+ # Note that this method will be invoked asynchronously
28
+ # using the Celluloid actor semantics.
29
+ def start
30
+
31
+ end
32
+
33
+ # When a message is accepted by an Acceptor, this method
34
+ # should be invoked with the received payload to start
35
+ # processing of the filter chain
36
+ def start_filter_chain(message)
37
+ klazz = Batsir::Registry.get(stage_name)
38
+ transformer_queue.each do |transformer|
39
+ message = transformer.transform(message)
40
+ end
41
+ klazz.perform_async(message) if klazz
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ require 'batsir/amqp'
2
+
3
+ module Batsir
4
+ module Acceptors
5
+ class AMQPAcceptor < Acceptor
6
+ include Batsir::AMQP
7
+ def start
8
+ Bunny.run( bunny_options ) do |bunny|
9
+ q = bunny.queue( queue )
10
+ exc = bunny.exchange( exchange )
11
+ q.bind( exc, :key => queue)
12
+ q.subscribe(:cancellator => cancellator) do |msg|
13
+ start_filter_chain(msg[:payload])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end