serf 0.1.0.alpha1 → 0.2.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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