serf 0.1.0.alpha1 → 0.2.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,14 +3,9 @@ source 'http://rubygems.org'
3
3
  # Example:
4
4
  # gem 'activesupport', '>= 2.3.5'
5
5
 
6
- gem 'activesupport', '~> 3.1.1'
6
+ gem 'activemodel', '~> 3.1.3'
7
+ gem 'activesupport', '~> 3.1.3'
7
8
  gem 'eventmachine', '~> 0.12.10'
8
- gem 'i18n', '~> 0.6.0'
9
- gem 'msgpack', '~> 0.4.6'
10
- gem 'msgpack-rpc', '~> 0.4.5'
11
- gem 'multi_json', '~> 1.0.3'
12
- gem 'rack', '~> 1.3.5'
13
- gem 'redis', '~> 2.2.2'
14
9
 
15
10
  # Add dependencies to develop your gem here.
16
11
  # Include everything needed to run rake, tests, features, etc.
@@ -22,6 +17,7 @@ group :development, :test do
22
17
  gem 'rcov', '>= 0'
23
18
 
24
19
  # Soft Dependencies
25
- gem 'log4r', '~> 1.1.9'
26
- gem 'celluloid', '~> 0.5.0' # Only for CelluloidRunner
20
+ #gem 'log4r', '~> 1.1.9'
21
+ gem 'msgpack', '~> 0.4.6'
22
+ #gem 'multi_json', '~> 1.0.3'
27
23
  end
data/Gemfile.lock CHANGED
@@ -1,30 +1,25 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activesupport (3.1.1)
4
+ activemodel (3.1.3)
5
+ activesupport (= 3.1.3)
6
+ builder (~> 3.0.0)
7
+ i18n (~> 0.6)
8
+ activesupport (3.1.3)
5
9
  multi_json (~> 1.0)
6
- celluloid (0.5.0)
10
+ builder (3.0.0)
7
11
  diff-lcs (1.1.3)
8
12
  eventmachine (0.12.10)
9
13
  git (1.2.5)
10
14
  i18n (0.6.0)
11
- iobuffer (1.0.0)
12
15
  jeweler (1.6.4)
13
16
  bundler (~> 1.0)
14
17
  git (>= 1.2.5)
15
18
  rake
16
- log4r (1.1.9)
17
19
  msgpack (0.4.6)
18
- msgpack-rpc (0.4.5)
19
- msgpack (>= 0.4.4)
20
- rev (>= 0.3.0)
21
- multi_json (1.0.3)
22
- rack (1.3.5)
20
+ multi_json (1.0.4)
23
21
  rake (0.9.2.2)
24
22
  rcov (0.9.11)
25
- redis (2.2.2)
26
- rev (0.3.2)
27
- iobuffer (>= 0.1.3)
28
23
  rspec (2.3.0)
29
24
  rspec-core (~> 2.3.0)
30
25
  rspec-expectations (~> 2.3.0)
@@ -39,18 +34,12 @@ PLATFORMS
39
34
  ruby
40
35
 
41
36
  DEPENDENCIES
42
- activesupport (~> 3.1.1)
37
+ activemodel (~> 3.1.3)
38
+ activesupport (~> 3.1.3)
43
39
  bundler (~> 1.0.0)
44
- celluloid (~> 0.5.0)
45
40
  eventmachine (~> 0.12.10)
46
- i18n (~> 0.6.0)
47
41
  jeweler (~> 1.6.4)
48
- log4r (~> 1.1.9)
49
42
  msgpack (~> 0.4.6)
50
- msgpack-rpc (~> 0.4.5)
51
- multi_json (~> 1.0.3)
52
- rack (~> 1.3.5)
53
43
  rcov
54
- redis (~> 2.2.2)
55
44
  rspec (~> 2.3.0)
56
45
  yard (~> 0.6.0)
data/README.md CHANGED
@@ -5,64 +5,9 @@ Serf is a library that scaffolds distributed systems that are architected using
5
5
  Event-Driven Service Oriented Architecture design in combinations with
6
6
  the Command Query Responsibility Separation pattern.
7
7
 
8
- Fundamentally, serf is a server process that receives commands and
9
- emits events. The actual business logic is set up using `Serf's Up`
10
- configuration files similar to rackup files.
11
-
12
- An application developer writes Handler code that knows how to
13
- process commands and events. This handler code is wired up in the
14
- afore mentioned serfup file.
15
-
16
- Handler code is essentally an object that responds to 'call' as with
17
- other Rack based applications. The primary difference is that
18
- the 'env' passed to the Handler object is NOT rack env conformant.
19
-
20
- The message passed to the Handler objects' call methods are a
21
- hash, which has a `kind` keyed element whose value is the type
22
- of command (or event). A Handler SHOULD verify that this value is
23
- what it expects in case of misrouting due to bad serfup configuration.
24
-
25
- The serf library provides a very basic event service bus using
26
- Redis' pubsub capabilities. We hope to implement more advanced topologies
27
- using ZeroMQ in the future.
28
-
29
- View the `examples/config.su` file for an example of a serfup configuration.
30
-
31
- Besides Handlers, we have `Receivers` that expose points of entry for
32
- command and event messages. We implement two types:
33
-
34
- 1. RedisPubsubReceiver - For pubsub event messages.
35
- 2. MsgpackReceiver - For msgpack rpc receipt of command messages.
36
-
37
8
  Middleware
38
9
  ==========
39
10
 
40
- Serf::Middleware::EmRunner
41
- --------------------------
42
-
43
- The EmRunner middleware is a mechanism to take a received message
44
- (from RedisPubsubReceiver or MsgpackReceiver) and have the subsequent
45
- app chain to be processed in the EventMachine deferred thread pool.
46
- This is handy for async messages that come in through the PubSub
47
- channel. And it is helpful in the CQRS model of processing when
48
- accepting commands through Msgpack RPC.
49
-
50
- Serf::Middleware::CelluloidRunner
51
- ---------------------------------
52
-
53
- `celluloid` is a soft dependency.
54
-
55
- But this middleware functions the same as the EmRunner, but with
56
- actors and fibers instead.
57
-
58
- NOTE: The limitation of this is that only 1 actor will be generated
59
- per 'use Serf::Middleware::CelluloidRunner' definition. Thus messages
60
- to any handler within a single 'group' will be processed serially.
61
-
62
- This might be helpful in special cases where you want non-blocking
63
- receipt of messages but also want ordered processing based on
64
- received messages.
65
-
66
11
  Airbrake
67
12
  --------
68
13
 
@@ -76,10 +21,7 @@ You can use Airbrake's middleware to catch exceptions.
76
21
  config.api_key = 'my_api_key'
77
22
  end
78
23
 
79
- group :my_commands do
80
- use Airbrake::Rack
81
- handle 'my_command', MyCommand.new
82
- end
24
+ use Airbrake::Rack
83
25
 
84
26
 
85
27
  Contributing to serf
data/Rakefile CHANGED
@@ -17,7 +17,7 @@ Jeweler::Tasks.new do |gem|
17
17
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
18
18
  gem.name = "serf"
19
19
  gem.homepage = "http://github.com/byu/serf"
20
- gem.license = "MIT"
20
+ gem.license = "Apache 2.0"
21
21
  gem.summary = %Q{Event-Driven SOA with CQRS}
22
22
  gem.description = %Q{Event-Driven SOA with CQRS}
23
23
  gem.email = "benjaminlyu@gmail.com"
data/lib/serf/builder.rb CHANGED
@@ -1,22 +1,21 @@
1
- require 'rack'
1
+ require 'serf/serfer'
2
2
 
3
3
  module Serf
4
4
 
5
5
  ##
6
- # A Serf Builder that processes the SerfUp DSL to set up a new
7
- # serf server processing.
6
+ # A Serf Builder that processes the SerfUp DSL to build a rack-like
7
+ # app to route and process received messages. This builder is
8
+ # implemented with lots of code from Rack::Builder.
8
9
  #
9
10
  # builder = Serf::Builder.parse_file 'examples/config.su'
10
- # builder.run # spins of daemon threads for each receiver
11
- # EventMachine::run # Most likely do this because of EmRunner middleware.
11
+ # builder.to_app
12
12
  #
13
13
  # or
14
14
  #
15
15
  # builder = Serf::Builder.new do
16
16
  # ... A SerfUp Config block here. See the examples/config.su
17
17
  # end
18
- # builder.run
19
- # EventMachine::run
18
+ # builder.to_app
20
19
  #
21
20
  class Builder
22
21
  def self.parse_file(config)
@@ -26,71 +25,75 @@ module Serf
26
25
  return builder
27
26
  end
28
27
 
29
- def initialize(&block)
30
- @already_run = false
31
- @groups = {}
32
- @receivers = []
33
-
34
- reset_current_group
28
+ def initialize(options={}, &block)
29
+ @use = []
30
+ @manifest = {}
31
+ @config = {}
32
+ @not_found = options[:not_found]
33
+ @serfer_class = options.fetch(:serfer_class) { ::Serf::Serfer }
34
+ @serfer_options = options[:serfer_options] || {}
35
35
  instance_eval(&block) if block_given?
36
36
  end
37
37
 
38
- def group(name, &block)
39
- raise 'Already inside group' if @current_group_name
40
- @current_group_name = name
41
- instance_eval(&block) if block_given?
42
- commit_current_group
43
- reset_current_group
38
+ def self.app(default_app=nil, &block)
39
+ self.new(default_app, &block).to_app
44
40
  end
45
41
 
46
42
  def use(middleware, *args, &block)
47
43
  @use << proc { |app| middleware.new(app, *args, &block) }
48
44
  end
49
45
 
50
- def handle(kind, app=nil, &block)
51
- raise 'Not inside group' unless @current_group_name
52
- if app
53
- @handlers[kind.to_s] = app
54
- elsif block_given?
55
- @handlers[kind.to_s] = ::Rack::Builder.new(block).to_app
56
- end
46
+ def register(manifest)
47
+ @manifest.merge! manifest
48
+ end
49
+
50
+ def config(handler, *args, &block)
51
+ @config[handler] = [args, block]
57
52
  end
58
53
 
59
54
  def not_found(app)
60
- raise 'not_found already declared for this group' if @not_found
61
55
  @not_found = app
62
56
  end
63
57
 
64
- def bind(group, receiver, *args)
65
- app = @groups[group]
66
- @receivers << receiver.new(app, *args)
67
- end
68
-
69
- def run
70
- raise 'Already run' if @already_run
71
- @already_run = true
72
- @receivers.each do |receiver|
73
- Thread.new {
74
- receiver.run
75
- }
76
- end
58
+ def to_app
59
+ app = generate_routes
60
+ return @use.reverse.inject(app) { |a,e| e[a] }
77
61
  end
78
62
 
79
63
  private
80
64
 
81
- def reset_current_group
82
- @current_group_name = nil
83
- @not_found = nil
84
- @handlers = {}
85
- @use = []
86
- end
65
+ def generate_routes
66
+ kinds = {}
67
+ handlers = {}
68
+ async_handlers = {}
69
+
70
+ @manifest.each do |kind, options|
71
+ # Instantiate our handler with any possible configuration.
72
+ handler_str = options.fetch(:handler)
73
+ handler_class = handler_str.camelize.constantize
74
+ args, block = @config.fetch(handler_str) { [[], nil] }
75
+ handler = handler_class.new *args, &block
76
+
77
+ # Then put it into the proper map of handlers for either
78
+ # synchronous or asynchronous processing.
79
+ async = options.fetch(:async) { true }
80
+ (async ? async_handlers : handlers)[kind] = handler
81
+
82
+ # Get the implementing message serialization class.
83
+ # For a given message kind, we may have a different (or nil)
84
+ # implementing class. If nil, then we're not going to try to
85
+ # create a message class to validate before passing to handler.
86
+ message_class = options.fetch(:message_class) { kind }
87
+ kinds[kind] = message_class && message_class.camelize.constantize
88
+ end
87
89
 
88
- def commit_current_group
89
- app = ::Serf::Middleware::KindMapper.new(
90
- map: @handlers,
91
- not_found: @not_found)
92
- app = @use.reverse.inject(app) { |a,e| e[a] } if @use.size > 0
93
- @groups[@current_group_name] = app
90
+ # We create the serfer class to handle all the messages.
91
+ return @serfer_class.new(
92
+ @serfer_options.merge(
93
+ kinds: kinds,
94
+ handlers: handlers,
95
+ async_handlers: async_handlers,
96
+ not_found: @not_found))
94
97
  end
95
98
 
96
99
  end
@@ -0,0 +1,67 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/ordered_options'
6
+
7
+ module Serf
8
+
9
+ module Handler
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ # In the class that includes this module, we're going to
14
+ # create an inheritable class attribute that will store
15
+ # our mappings between messages and the methods to call.
16
+ class_attribute :serf_actions
17
+ send(
18
+ 'serf_actions=',
19
+ ActiveSupport::InheritableOptions.new)
20
+
21
+ def self.inherited(kls) #:nodoc:
22
+ super
23
+ # Sets the current subclass class attribute to be an
24
+ # inheritable copy of the superclass options.
25
+ kls.send(
26
+ 'serf_actions=',
27
+ self.serf_actions.inheritable_copy)
28
+ end
29
+
30
+ end
31
+
32
+ module InstanceMethods
33
+
34
+ ##
35
+ # Rack-like call. It receives an environment hash, which we
36
+ # assume is a message.
37
+ #
38
+ def call(env={})
39
+ # Just to stringify the environment keys
40
+ env = env.dup.stringify_keys
41
+ # Make sure a kind was set, and that we can handle it.
42
+ message_kind = env['kind']
43
+ raise ArgumentError, 'No "kind" in call env' if message_kind.blank?
44
+ method = self.class.serf_actions[message_kind]
45
+ raise ArgumentError, "#{message_kind} not found" if method.blank?
46
+ # Now execute the method with the environment parameters
47
+ self.send method, env
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+
53
+ ##
54
+ # registers a method to handle the receipt of a message type.
55
+ #
56
+ def receives(message_kind, options={})
57
+ raise ArgumentError, 'Blank message_kind' if message_kind.blank?
58
+ exposed_method = options[:with]
59
+ raise ArgumentError, 'Missing "with" option' if exposed_method.blank?
60
+ self.serf_actions[message_kind] = exposed_method
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,44 @@
1
+ require 'active_model'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Serf
7
+
8
+ ##
9
+ # A module to represent a message that we're transporting over
10
+ # the wire. This is mainly for commands and events in ED-SOA.
11
+ # Optional, but useful for validations, etc.
12
+ #
13
+ module Message
14
+ extend ActiveSupport::Concern
15
+ include ActiveModel::Serialization
16
+ include ActiveModel::Serializers::JSON
17
+ include ActiveModel::Validations
18
+
19
+ included do
20
+ class_attribute :kind
21
+ send 'kind=', self.to_s.tableize.singularize
22
+ end
23
+
24
+ module InstanceMethods
25
+
26
+ def attributes
27
+ {
28
+ 'kind' => kind
29
+ }
30
+ end
31
+
32
+ def kind
33
+ self.class.kind
34
+ end
35
+
36
+ def to_msgpack
37
+ attributes.to_msgpack
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,78 @@
1
+ require 'eventmachine'
2
+
3
+ require 'serf/util/null_object'
4
+
5
+ module Serf
6
+
7
+ ##
8
+ # The Serfer is a rack app endpoint that:
9
+ class Serfer
10
+
11
+ def initialize(options={})
12
+ # Options for handling the requests
13
+ @kinds = options[:kinds] || {}
14
+ @handlers = options[:handlers] || {}
15
+ @async_handlers = options[:async_handlers] || {}
16
+ @not_found = options[:not_found]
17
+
18
+ # Other processing aspects
19
+ @em = options.fetch(:event_machine) { ::EM }
20
+ @logger = options.fetch(:logger) { ::Serf::Util::NullObject.new }
21
+ end
22
+
23
+ ##
24
+ # Rack-like call to handle a message
25
+ #
26
+ def call(env)
27
+ kind = env['kind']
28
+
29
+ # Do a message_class validation if we have it listed.
30
+ # And use the message attributes instead of raw env when passing
31
+ # to message handler.
32
+ message_class = @kinds[kind]
33
+ if message_class
34
+ message = message_class.new env
35
+ raise message.errors.full_messages.join('. ') unless message.valid?
36
+ params = message.attributes
37
+ else
38
+ params = env.stringify_keys
39
+ end
40
+
41
+ # Run an asynchronous handler if we have it.
42
+ handler = @async_handlers[kind]
43
+ if handler
44
+ @em.defer(proc do
45
+ begin
46
+ handler.call params
47
+ rescue => e
48
+ @logger.error e
49
+ end
50
+ end)
51
+ return [
52
+ 202,
53
+ {
54
+ 'Content-Type' => 'text/plain'
55
+ },
56
+ ['Accepted']
57
+ ]
58
+ end
59
+
60
+ # Run a synchronous handler if we have it.
61
+ handler = @handlers[kind]
62
+ return handler.call(params) if handler
63
+
64
+ # run a not found
65
+ return @not_found.call(params) if @not_found
66
+
67
+ # we can't handle this kind.
68
+ return [
69
+ 404,
70
+ {
71
+ 'Content-Type' => 'text/plain',
72
+ 'X-Cascade' => 'pass'
73
+ },
74
+ ['Not Found']
75
+ ]
76
+ end
77
+ end
78
+ end
@@ -1,4 +1,5 @@
1
1
  module Serf
2
+ module Util
2
3
 
3
4
  ##
4
5
  # A simple NullOject pattern implementation for some Serf code that
@@ -11,3 +12,4 @@ module Serf
11
12
  end
12
13
 
13
14
  end
15
+ end
data/lib/serf/version.rb CHANGED
@@ -2,7 +2,7 @@ module Serf
2
2
 
3
3
  module Version
4
4
  MAJOR = 0
5
- MINOR = 1
5
+ MINOR = 2
6
6
  PATCH = 0
7
7
  BUILD = 'alpha1'
8
8
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join '.'
data/lib/serf.rb CHANGED
@@ -1,21 +1,4 @@
1
1
  module Serf
2
-
3
- autoload :Builder, 'serf/builder'
4
- autoload :NullObject, 'serf/null_object'
5
-
6
- # Emitters
7
- autoload :RedisEmitter, 'serf/emitters/redis_emitter'
8
-
9
- # Receivers
10
- autoload :MsgpackReceiver, 'serf/receivers/msgpack_receiver'
11
- autoload :RedisPubsubReceiver, 'serf/receivers/redis_pubsub_receiver'
12
-
13
- module Middleware
14
- autoload :CelluloidRunner, 'serf/middleware/celluloid_runner'
15
- autoload :EmRunner, 'serf/middleware/em_runner'
16
- autoload :KindMapper, 'serf/middleware/kind_mapper'
17
- end
18
-
19
2
  end
20
3
 
21
4
  require 'serf/version'
data/serf.gemspec CHANGED
@@ -5,14 +5,13 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "serf"
8
- s.version = "0.1.0.alpha1"
8
+ s.version = "0.2.0.alpha1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Benjamin Yu"]
12
- s.date = "2011-11-03"
12
+ s.date = "2011-12-12"
13
13
  s.description = "Event-Driven SOA with CQRS"
14
14
  s.email = "benjaminlyu@gmail.com"
15
- s.executables = ["serfup"]
16
15
  s.extra_rdoc_files = [
17
16
  "LICENSE.txt",
18
17
  "README.md"
@@ -26,24 +25,19 @@ Gem::Specification.new do |s|
26
25
  "NOTICE.txt",
27
26
  "README.md",
28
27
  "Rakefile",
29
- "bin/serfup",
30
- "examples/config.su",
31
28
  "lib/serf.rb",
32
29
  "lib/serf/builder.rb",
33
- "lib/serf/emitters/redis_emitter.rb",
34
- "lib/serf/middleware/celluloid_runner.rb",
35
- "lib/serf/middleware/em_runner.rb",
36
- "lib/serf/middleware/kind_mapper.rb",
37
- "lib/serf/null_object.rb",
38
- "lib/serf/receivers/msgpack_receiver.rb",
39
- "lib/serf/receivers/redis_pubsub_receiver.rb",
30
+ "lib/serf/handler.rb",
31
+ "lib/serf/message.rb",
32
+ "lib/serf/serfer.rb",
33
+ "lib/serf/util/null_object.rb",
40
34
  "lib/serf/version.rb",
41
35
  "serf.gemspec",
42
36
  "spec/serf_spec.rb",
43
37
  "spec/spec_helper.rb"
44
38
  ]
45
39
  s.homepage = "http://github.com/byu/serf"
46
- s.licenses = ["MIT"]
40
+ s.licenses = ["Apache 2.0"]
47
41
  s.require_paths = ["lib"]
48
42
  s.rubygems_version = "1.8.10"
49
43
  s.summary = "Event-Driven SOA with CQRS"
@@ -52,54 +46,36 @@ Gem::Specification.new do |s|
52
46
  s.specification_version = 3
53
47
 
54
48
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
- s.add_runtime_dependency(%q<activesupport>, ["~> 3.1.1"])
49
+ s.add_runtime_dependency(%q<activemodel>, ["~> 3.1.3"])
50
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.1.3"])
56
51
  s.add_runtime_dependency(%q<eventmachine>, ["~> 0.12.10"])
57
- s.add_runtime_dependency(%q<i18n>, ["~> 0.6.0"])
58
- s.add_runtime_dependency(%q<msgpack>, ["~> 0.4.6"])
59
- s.add_runtime_dependency(%q<msgpack-rpc>, ["~> 0.4.5"])
60
- s.add_runtime_dependency(%q<multi_json>, ["~> 1.0.3"])
61
- s.add_runtime_dependency(%q<rack>, ["~> 1.3.5"])
62
- s.add_runtime_dependency(%q<redis>, ["~> 2.2.2"])
63
52
  s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
64
53
  s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
65
54
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
66
55
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
67
56
  s.add_development_dependency(%q<rcov>, [">= 0"])
68
- s.add_development_dependency(%q<log4r>, ["~> 1.1.9"])
69
- s.add_development_dependency(%q<celluloid>, ["~> 0.5.0"])
57
+ s.add_development_dependency(%q<msgpack>, ["~> 0.4.6"])
70
58
  else
71
- s.add_dependency(%q<activesupport>, ["~> 3.1.1"])
59
+ s.add_dependency(%q<activemodel>, ["~> 3.1.3"])
60
+ s.add_dependency(%q<activesupport>, ["~> 3.1.3"])
72
61
  s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
73
- s.add_dependency(%q<i18n>, ["~> 0.6.0"])
74
- s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
75
- s.add_dependency(%q<msgpack-rpc>, ["~> 0.4.5"])
76
- s.add_dependency(%q<multi_json>, ["~> 1.0.3"])
77
- s.add_dependency(%q<rack>, ["~> 1.3.5"])
78
- s.add_dependency(%q<redis>, ["~> 2.2.2"])
79
62
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
80
63
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
81
64
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
82
65
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
83
66
  s.add_dependency(%q<rcov>, [">= 0"])
84
- s.add_dependency(%q<log4r>, ["~> 1.1.9"])
85
- s.add_dependency(%q<celluloid>, ["~> 0.5.0"])
67
+ s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
86
68
  end
87
69
  else
88
- s.add_dependency(%q<activesupport>, ["~> 3.1.1"])
70
+ s.add_dependency(%q<activemodel>, ["~> 3.1.3"])
71
+ s.add_dependency(%q<activesupport>, ["~> 3.1.3"])
89
72
  s.add_dependency(%q<eventmachine>, ["~> 0.12.10"])
90
- s.add_dependency(%q<i18n>, ["~> 0.6.0"])
91
- s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
92
- s.add_dependency(%q<msgpack-rpc>, ["~> 0.4.5"])
93
- s.add_dependency(%q<multi_json>, ["~> 1.0.3"])
94
- s.add_dependency(%q<rack>, ["~> 1.3.5"])
95
- s.add_dependency(%q<redis>, ["~> 2.2.2"])
96
73
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
97
74
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
98
75
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
99
76
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
100
77
  s.add_dependency(%q<rcov>, [">= 0"])
101
- s.add_dependency(%q<log4r>, ["~> 1.1.9"])
102
- s.add_dependency(%q<celluloid>, ["~> 0.5.0"])
78
+ s.add_dependency(%q<msgpack>, ["~> 0.4.6"])
103
79
  end
104
80
  end
105
81
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha1
4
+ version: 0.2.0.alpha1
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,99 +9,44 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-03 00:00:00.000000000Z
12
+ date: 2011-12-12 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: activesupport
16
- requirement: &70284262969520 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ~>
20
- - !ruby/object:Gem::Version
21
- version: 3.1.1
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: *70284262969520
25
- - !ruby/object:Gem::Dependency
26
- name: eventmachine
27
- requirement: &70284262967940 !ruby/object:Gem::Requirement
15
+ name: activemodel
16
+ requirement: &70254700994980 !ruby/object:Gem::Requirement
28
17
  none: false
29
18
  requirements:
30
19
  - - ~>
31
20
  - !ruby/object:Gem::Version
32
- version: 0.12.10
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: *70284262967940
36
- - !ruby/object:Gem::Dependency
37
- name: i18n
38
- requirement: &70284262966820 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ~>
42
- - !ruby/object:Gem::Version
43
- version: 0.6.0
44
- type: :runtime
45
- prerelease: false
46
- version_requirements: *70284262966820
47
- - !ruby/object:Gem::Dependency
48
- name: msgpack
49
- requirement: &70284262965600 !ruby/object:Gem::Requirement
50
- none: false
51
- requirements:
52
- - - ~>
53
- - !ruby/object:Gem::Version
54
- version: 0.4.6
21
+ version: 3.1.3
55
22
  type: :runtime
56
23
  prerelease: false
57
- version_requirements: *70284262965600
24
+ version_requirements: *70254700994980
58
25
  - !ruby/object:Gem::Dependency
59
- name: msgpack-rpc
60
- requirement: &70284262964540 !ruby/object:Gem::Requirement
61
- none: false
62
- requirements:
63
- - - ~>
64
- - !ruby/object:Gem::Version
65
- version: 0.4.5
66
- type: :runtime
67
- prerelease: false
68
- version_requirements: *70284262964540
69
- - !ruby/object:Gem::Dependency
70
- name: multi_json
71
- requirement: &70284262963360 !ruby/object:Gem::Requirement
72
- none: false
73
- requirements:
74
- - - ~>
75
- - !ruby/object:Gem::Version
76
- version: 1.0.3
77
- type: :runtime
78
- prerelease: false
79
- version_requirements: *70284262963360
80
- - !ruby/object:Gem::Dependency
81
- name: rack
82
- requirement: &70284262962220 !ruby/object:Gem::Requirement
26
+ name: activesupport
27
+ requirement: &70254700994500 !ruby/object:Gem::Requirement
83
28
  none: false
84
29
  requirements:
85
30
  - - ~>
86
31
  - !ruby/object:Gem::Version
87
- version: 1.3.5
32
+ version: 3.1.3
88
33
  type: :runtime
89
34
  prerelease: false
90
- version_requirements: *70284262962220
35
+ version_requirements: *70254700994500
91
36
  - !ruby/object:Gem::Dependency
92
- name: redis
93
- requirement: &70284262961440 !ruby/object:Gem::Requirement
37
+ name: eventmachine
38
+ requirement: &70254700994020 !ruby/object:Gem::Requirement
94
39
  none: false
95
40
  requirements:
96
41
  - - ~>
97
42
  - !ruby/object:Gem::Version
98
- version: 2.2.2
43
+ version: 0.12.10
99
44
  type: :runtime
100
45
  prerelease: false
101
- version_requirements: *70284262961440
46
+ version_requirements: *70254700994020
102
47
  - !ruby/object:Gem::Dependency
103
48
  name: rspec
104
- requirement: &70284262960340 !ruby/object:Gem::Requirement
49
+ requirement: &70254700993540 !ruby/object:Gem::Requirement
105
50
  none: false
106
51
  requirements:
107
52
  - - ~>
@@ -109,10 +54,10 @@ dependencies:
109
54
  version: 2.3.0
110
55
  type: :development
111
56
  prerelease: false
112
- version_requirements: *70284262960340
57
+ version_requirements: *70254700993540
113
58
  - !ruby/object:Gem::Dependency
114
59
  name: yard
115
- requirement: &70284262914860 !ruby/object:Gem::Requirement
60
+ requirement: &70254700993060 !ruby/object:Gem::Requirement
116
61
  none: false
117
62
  requirements:
118
63
  - - ~>
@@ -120,10 +65,10 @@ dependencies:
120
65
  version: 0.6.0
121
66
  type: :development
122
67
  prerelease: false
123
- version_requirements: *70284262914860
68
+ version_requirements: *70254700993060
124
69
  - !ruby/object:Gem::Dependency
125
70
  name: bundler
126
- requirement: &70284262913600 !ruby/object:Gem::Requirement
71
+ requirement: &70254700992580 !ruby/object:Gem::Requirement
127
72
  none: false
128
73
  requirements:
129
74
  - - ~>
@@ -131,10 +76,10 @@ dependencies:
131
76
  version: 1.0.0
132
77
  type: :development
133
78
  prerelease: false
134
- version_requirements: *70284262913600
79
+ version_requirements: *70254700992580
135
80
  - !ruby/object:Gem::Dependency
136
81
  name: jeweler
137
- requirement: &70284262912720 !ruby/object:Gem::Requirement
82
+ requirement: &70254700992100 !ruby/object:Gem::Requirement
138
83
  none: false
139
84
  requirements:
140
85
  - - ~>
@@ -142,10 +87,10 @@ dependencies:
142
87
  version: 1.6.4
143
88
  type: :development
144
89
  prerelease: false
145
- version_requirements: *70284262912720
90
+ version_requirements: *70254700992100
146
91
  - !ruby/object:Gem::Dependency
147
92
  name: rcov
148
- requirement: &70284262911900 !ruby/object:Gem::Requirement
93
+ requirement: &70254700991540 !ruby/object:Gem::Requirement
149
94
  none: false
150
95
  requirements:
151
96
  - - ! '>='
@@ -153,33 +98,21 @@ dependencies:
153
98
  version: '0'
154
99
  type: :development
155
100
  prerelease: false
156
- version_requirements: *70284262911900
101
+ version_requirements: *70254700991540
157
102
  - !ruby/object:Gem::Dependency
158
- name: log4r
159
- requirement: &70284262910880 !ruby/object:Gem::Requirement
160
- none: false
161
- requirements:
162
- - - ~>
163
- - !ruby/object:Gem::Version
164
- version: 1.1.9
165
- type: :development
166
- prerelease: false
167
- version_requirements: *70284262910880
168
- - !ruby/object:Gem::Dependency
169
- name: celluloid
170
- requirement: &70284262909900 !ruby/object:Gem::Requirement
103
+ name: msgpack
104
+ requirement: &70254700991060 !ruby/object:Gem::Requirement
171
105
  none: false
172
106
  requirements:
173
107
  - - ~>
174
108
  - !ruby/object:Gem::Version
175
- version: 0.5.0
109
+ version: 0.4.6
176
110
  type: :development
177
111
  prerelease: false
178
- version_requirements: *70284262909900
112
+ version_requirements: *70254700991060
179
113
  description: Event-Driven SOA with CQRS
180
114
  email: benjaminlyu@gmail.com
181
- executables:
182
- - serfup
115
+ executables: []
183
116
  extensions: []
184
117
  extra_rdoc_files:
185
118
  - LICENSE.txt
@@ -193,24 +126,19 @@ files:
193
126
  - NOTICE.txt
194
127
  - README.md
195
128
  - Rakefile
196
- - bin/serfup
197
- - examples/config.su
198
129
  - lib/serf.rb
199
130
  - lib/serf/builder.rb
200
- - lib/serf/emitters/redis_emitter.rb
201
- - lib/serf/middleware/celluloid_runner.rb
202
- - lib/serf/middleware/em_runner.rb
203
- - lib/serf/middleware/kind_mapper.rb
204
- - lib/serf/null_object.rb
205
- - lib/serf/receivers/msgpack_receiver.rb
206
- - lib/serf/receivers/redis_pubsub_receiver.rb
131
+ - lib/serf/handler.rb
132
+ - lib/serf/message.rb
133
+ - lib/serf/serfer.rb
134
+ - lib/serf/util/null_object.rb
207
135
  - lib/serf/version.rb
208
136
  - serf.gemspec
209
137
  - spec/serf_spec.rb
210
138
  - spec/spec_helper.rb
211
139
  homepage: http://github.com/byu/serf
212
140
  licenses:
213
- - MIT
141
+ - Apache 2.0
214
142
  post_install_message:
215
143
  rdoc_options: []
216
144
  require_paths:
@@ -223,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
223
151
  version: '0'
224
152
  segments:
225
153
  - 0
226
- hash: 1899332783665321639
154
+ hash: 1721782691913874176
227
155
  required_rubygems_version: !ruby/object:Gem::Requirement
228
156
  none: false
229
157
  requirements:
data/bin/serfup DELETED
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- #$LOAD_PATH << 'lib'
4
-
5
- require 'eventmachine'
6
- require 'serf'
7
-
8
- Serf::Builder.parse_file(ARGV.first).run
9
- EventMachine::run
data/examples/config.su DELETED
@@ -1,58 +0,0 @@
1
- # A Serf's Up file
2
-
3
- require 'log4r'
4
-
5
- # Set up a general logger for the app
6
- logger = Log4r::Logger.new 'my_logger'
7
- logger.outputters = Log4r::FileOutputter.new(
8
- 'fileOutputter',
9
- filename: 'console.txt')
10
-
11
- # Define a single emitter w/ default redis connections
12
- emitter = Serf::RedisEmitter.new
13
-
14
- # Define a group of events handlers
15
- group :events do
16
- # We use the EmRunner middleware so the handlers get run in the
17
- # EventMachine deferred thread pool.
18
- use Serf::Middleware::EmRunner, logger: logger
19
-
20
- # Define a handler for the post_rated_event kind of message.
21
- handle 'post_rated_event', proc { |env|
22
- logger.info("I'm in post_rated_event #{env.inspect}")
23
- [200, {}, '']
24
- }
25
-
26
- # Define what happens if a message received has a 'kind' attribute that
27
- # is not defined in this group.
28
- not_found(proc { |env|
29
- logger.info("event not found #{env.inspect}")
30
- [200, {},'']
31
- })
32
- end
33
-
34
- # Define a group of handlers that'll be handled by the MsgPackReceiver
35
- group :commands do
36
- # We use the EmRunner middleware so the handlers get run in the
37
- # EventMachine deferred thread pool.
38
- use Serf::Middleware::EmRunner, logger: logger
39
-
40
- # Handle the 'post_rating_request' command message.
41
- handle 'post_rating_request', proc { |env|
42
- logger.info("I'm in post_rating_request #{env.inspect}")
43
- emitter.emit(
44
- kind: 'post_rated_event',
45
- rated_data: env.to_s)
46
- [200, {},'']
47
- }
48
- not_found(proc { |env|
49
- logger.info("command not found #{env.inspect}")
50
- [200, {},'']
51
- })
52
- end
53
-
54
- # Bind our receivers and handler groups
55
- bind :events, Serf::RedisPubsubReceiver
56
- bind :commands, Serf::MsgpackReceiver, :host => '0.0.0.0', :post => 18800
57
-
58
-
@@ -1,23 +0,0 @@
1
- require 'multi_json'
2
- require 'redis'
3
-
4
- module Serf
5
-
6
- ##
7
- # Emits messages/events over a redis pubsub channel.
8
- #
9
- class RedisEmitter
10
- DEFAULT_CHANNEL = 'serf_pubsub_channel'
11
-
12
- def initialize(options={})
13
- @redis = options.fetch(:redis) { Redis.connect }
14
- @channel = options.fetch(:channel) { DEFAULT_CHANNEL }
15
- end
16
-
17
- def emit(message)
18
- encoded_message = MultiJson.encode message
19
- @redis.publish @channel, encoded_message
20
- end
21
-
22
- end
23
- end
@@ -1,45 +0,0 @@
1
- require 'celluloid'
2
-
3
- module Serf
4
- module Middleware
5
-
6
- ##
7
- # Spins off a received message to be run async by a celluloid actor.
8
- #
9
- class CelluloidRunner
10
-
11
- def initialize(app, options={})
12
- actor_class = options.fetch(:actor_class) { CelluloidRunnerActor }
13
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
14
- @actor = actor_class.new app, logger: @logger
15
- end
16
-
17
- def call(message)
18
- @actor.call! message
19
- return [
20
- 202,
21
- {
22
- 'Content-Type' => 'text/plain'
23
- },
24
- ['Accepted']
25
- ]
26
- end
27
- end
28
-
29
- class CelluloidRunnerActor
30
- include Celluloid
31
-
32
- def initialize(app, options={})
33
- @app = app
34
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
35
- end
36
-
37
- def call(message)
38
- @app.call message
39
- rescue => e
40
- @logger.error e
41
- end
42
- end
43
-
44
- end
45
- end
@@ -1,32 +0,0 @@
1
- require 'eventmachine'
2
-
3
- module Serf
4
- module Middleware
5
-
6
- class EmRunner
7
- def initialize(app, options={})
8
- @em = options.fetch(:event_machine) { EM }
9
- @app = app
10
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
11
- end
12
-
13
- def call(env)
14
- @em.defer(proc do
15
- begin
16
- @app.call env
17
- rescue => e
18
- @logger.error e
19
- end
20
- end)
21
- return [
22
- 202,
23
- {
24
- 'Content-Type' => 'text/plain'
25
- },
26
- ['Accepted']
27
- ]
28
- end
29
- end
30
-
31
- end
32
- end
@@ -1,48 +0,0 @@
1
- module Serf
2
- module Middleware
3
-
4
- ##
5
- # Our "router" app that will call the actual registered handler object
6
- # with the received message based on the 'kind' of message received.
7
- #
8
- # This means we look into the 'env' of the call and match the
9
- # env['kind'] value to see if we have it registered.
10
- # If found, we pass to the proper handler, else we run a not_found
11
- # handler. Sans a registered not_found handler, we just return a
12
- # 404 message. Note that if we used any Async middleware (i.e.
13
- # EmRunner or CelluloidRunner), the calling client (using Msgpack RPC)
14
- # will not see the 404 or not found handler results. The Async middleware
15
- # will have returned a 202 Accepted result.
16
- #
17
- # Developers SHOULD implement alternate mechanisms of error handling
18
- # and logging. Even possibly implementing a 404 handler that
19
- # broadcasts such a not found error event message.
20
- #
21
- class KindMapper
22
-
23
- def initialize(options={})
24
- @map = options.fetch(:map) { {} }
25
- @not_found = options[:not_found]
26
- end
27
-
28
- def call(env)
29
- kind = env['kind']
30
- if kind && @map.has_key?(kind)
31
- return @map[kind].call env
32
- elsif @not_found
33
- return @not_found.call env
34
- end
35
- return [
36
- 404,
37
- {
38
- 'Content-Type' => 'text/plain',
39
- 'X-Cascade' => 'pass'
40
- },
41
- ['Not Found']
42
- ]
43
- end
44
-
45
- end
46
-
47
- end
48
- end
@@ -1,55 +0,0 @@
1
- require 'active_support/core_ext/hash'
2
- require 'msgpack/rpc'
3
-
4
- module Serf
5
-
6
- ##
7
- # A MsgpackRpc handler that is just here to act as a protective
8
- # facade to only expose the 'call' rpc method to MsgpackRpc clients.
9
- #
10
- class MsgpackHandler
11
- def initialize(app, options={})
12
- @app = app
13
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
14
- end
15
-
16
- def call(env)
17
- @app.call env.stringify_keys
18
- rescue => e
19
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
20
- # We reraise this error so it's passed on through to the remote client.
21
- raise e
22
- end
23
- end
24
-
25
- ##
26
- # Defines a Msgpack RPC Server to run to receive messages.
27
- #
28
- class MsgpackReceiver
29
- DEFAULT_SERVER_TRANSPORT_CLASS = MessagePack::RPC::TCPServerTransport
30
- DEFAULT_ADDRESS_CLASS = MessagePack::RPC::Address
31
-
32
- def initialize(app, options={})
33
- @handler = options.fetch(:handler) { MsgpackHandler.new(app) }
34
-
35
- @listener = options.fetch(:listener) {
36
- host = options.fetch(:host) { '0.0.0.0' }
37
- port = options.fetch(:port) { 18800 }
38
- address = DEFAULT_ADDRESS_CLASS.new host, port
39
- DEFAULT_SERVER_TRANSPORT_CLASS.new address
40
- }
41
-
42
- @rpc_class = options.fetch(:rpc_class) { MessagePack::RPC::Server }
43
- end
44
-
45
- ##
46
- # Runs, doesn't return.
47
- def run
48
- svr = @rpc_class.new
49
- svr.listen @listener, @handler
50
- svr.run
51
- end
52
-
53
- end
54
-
55
- end
@@ -1,38 +0,0 @@
1
- require 'active_support/core_ext/hash'
2
- require 'multi_json'
3
- require "redis"
4
-
5
- module Serf
6
-
7
- ##
8
- # Defines a receiver that listens for messages from a subscribed
9
- # Redis pubsub channel.
10
- #
11
- class RedisPubsubReceiver
12
-
13
- def initialize(app, options={})
14
- @app = app
15
-
16
- @redis = options.fetch(:redis) { Redis.connect }
17
- @channel = options.fetch(:channel) { 'serf_pubsub_channel' }
18
- @logger = options.fetch(:logger) { ::Serf::NullObject.new }
19
- end
20
-
21
- ##
22
- # Runs, doesn't return.
23
- def run
24
- @redis.subscribe(@channel) do |on|
25
- on.message do |channel, message|
26
- begin
27
- decoded_message = MultiJson.decode message
28
- raise 'Received non-hash JSON' unless decoded_message.kind_of? Hash
29
- @app.call decoded_message.stringify_keys
30
- rescue => e
31
- @logger.error e
32
- end
33
- end
34
- end
35
- end
36
-
37
- end
38
- end