emque-consuming 1.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +40 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +205 -0
- data/Rakefile +14 -0
- data/bin/emque +5 -0
- data/emque-consuming.gemspec +36 -0
- data/lib/emque/consuming/actor.rb +21 -0
- data/lib/emque/consuming/adapter.rb +47 -0
- data/lib/emque/consuming/adapters/rabbit_mq/manager.rb +111 -0
- data/lib/emque/consuming/adapters/rabbit_mq/retry_worker.rb +59 -0
- data/lib/emque/consuming/adapters/rabbit_mq/worker.rb +87 -0
- data/lib/emque/consuming/adapters/rabbit_mq.rb +26 -0
- data/lib/emque/consuming/application.rb +112 -0
- data/lib/emque/consuming/cli.rb +140 -0
- data/lib/emque/consuming/command_receivers/base.rb +37 -0
- data/lib/emque/consuming/command_receivers/http_server.rb +103 -0
- data/lib/emque/consuming/command_receivers/unix_socket.rb +169 -0
- data/lib/emque/consuming/configuration.rb +63 -0
- data/lib/emque/consuming/consumer/common.rb +61 -0
- data/lib/emque/consuming/consumer.rb +33 -0
- data/lib/emque/consuming/consuming.rb +27 -0
- data/lib/emque/consuming/control/errors.rb +41 -0
- data/lib/emque/consuming/control/workers.rb +23 -0
- data/lib/emque/consuming/control.rb +33 -0
- data/lib/emque/consuming/core.rb +89 -0
- data/lib/emque/consuming/error_tracker.rb +39 -0
- data/lib/emque/consuming/generators/application.rb +95 -0
- data/lib/emque/consuming/helpers.rb +29 -0
- data/lib/emque/consuming/logging.rb +32 -0
- data/lib/emque/consuming/message.rb +22 -0
- data/lib/emque/consuming/pidfile.rb +54 -0
- data/lib/emque/consuming/router.rb +73 -0
- data/lib/emque/consuming/runner.rb +168 -0
- data/lib/emque/consuming/status.rb +26 -0
- data/lib/emque/consuming/tasks.rb +121 -0
- data/lib/emque/consuming/transmitter.rb +31 -0
- data/lib/emque/consuming/version.rb +5 -0
- data/lib/emque/consuming.rb +9 -0
- data/lib/emque-consuming.rb +3 -0
- data/lib/templates/.gitignore.tt +25 -0
- data/lib/templates/Gemfile.tt +6 -0
- data/lib/templates/Rakefile.tt +7 -0
- data/lib/templates/config/application.rb.tt +42 -0
- data/lib/templates/config/environments/development.rb.tt +2 -0
- data/lib/templates/config/environments/production.rb.tt +2 -0
- data/lib/templates/config/environments/staging.rb.tt +2 -0
- data/lib/templates/config/environments/test.rb.tt +2 -0
- data/lib/templates/config/routes.rb.tt +8 -0
- data/spec/application_spec.rb +28 -0
- data/spec/cli_spec.rb +136 -0
- data/spec/configuration_spec.rb +47 -0
- data/spec/consumer_spec.rb +56 -0
- data/spec/control/errors_spec.rb +170 -0
- data/spec/control_spec.rb +15 -0
- data/spec/core_spec.rb +121 -0
- data/spec/dummy/config/application.rb +38 -0
- data/spec/dummy/config/environments/test.rb +0 -0
- data/spec/dummy/config/routes.rb +0 -0
- data/spec/error_tracker_spec.rb +64 -0
- data/spec/pidfile_spec.rb +74 -0
- data/spec/router_spec.rb +14 -0
- data/spec/runner_spec.rb +138 -0
- data/spec/spec_helper.rb +43 -0
- metadata +309 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
require "oj"
|
2
|
+
require_relative "consumer/common"
|
3
|
+
|
4
|
+
module Emque
|
5
|
+
module Consuming
|
6
|
+
class BlockingFailure < StandardError; end
|
7
|
+
|
8
|
+
class Consumer
|
9
|
+
include Emque::Consuming.consumer
|
10
|
+
|
11
|
+
def process(message)
|
12
|
+
pipe(message, :through => [:parse, :route])
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse(message)
|
18
|
+
message.with(
|
19
|
+
:values =>
|
20
|
+
Oj.load(message.original, :symbol_keys => true)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def route(message)
|
25
|
+
Emque::Consuming.application.router.route(
|
26
|
+
message.values.fetch(:metadata).fetch(:topic),
|
27
|
+
message.values.fetch(:metadata).fetch(:type),
|
28
|
+
message
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "emque/consuming/runner"
|
2
|
+
require "emque/consuming/logging"
|
3
|
+
|
4
|
+
module Emque
|
5
|
+
module Consuming
|
6
|
+
class << self
|
7
|
+
attr_accessor :application
|
8
|
+
|
9
|
+
# The Configuration instance used to configure the Emque::Consuming environment
|
10
|
+
def config
|
11
|
+
Emque::Consuming.application.config
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger
|
15
|
+
Emque::Consuming::Logging.logger
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger=(log)
|
19
|
+
Emque::Consuming::Logging.logger = log
|
20
|
+
end
|
21
|
+
|
22
|
+
def runner
|
23
|
+
Emque::Consuming::Runner.instance
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Emque
|
2
|
+
module Consuming
|
3
|
+
class Control
|
4
|
+
class Errors
|
5
|
+
include Emque::Consuming::Helpers
|
6
|
+
|
7
|
+
COMMANDS = [:clear, :down, :expire_after, :up, :retry]
|
8
|
+
|
9
|
+
def clear
|
10
|
+
app.error_tracker.occurrences.clear
|
11
|
+
end
|
12
|
+
|
13
|
+
def down
|
14
|
+
if app.error_tracker.limit > 1
|
15
|
+
config.error_limit = app.error_tracker.limit -= 1
|
16
|
+
app.verify_error_status
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def expire_after(seconds)
|
21
|
+
unless seconds.is_a?(Integer)
|
22
|
+
raise ArgumentError, "first argument must be an integer"
|
23
|
+
end
|
24
|
+
config.error_expiration = app.error_tracker.expiration = seconds
|
25
|
+
end
|
26
|
+
|
27
|
+
def up
|
28
|
+
config.error_limit = app.error_tracker.limit += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def retry
|
32
|
+
app.manager.retry_errors
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to?(method)
|
36
|
+
COMMANDS.include?(method.to_sym)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Emque
|
2
|
+
module Consuming
|
3
|
+
class Control
|
4
|
+
class Workers
|
5
|
+
include Emque::Consuming::Helpers
|
6
|
+
|
7
|
+
COMMANDS = [:down, :up]
|
8
|
+
|
9
|
+
def down(topic)
|
10
|
+
app.manager.worker(topic: topic.to_sym, command: :down)
|
11
|
+
end
|
12
|
+
|
13
|
+
def up(topic)
|
14
|
+
app.manager.worker(topic: topic.to_sym, command: :up)
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to?(method)
|
18
|
+
COMMANDS.include?(method.to_sym)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "emque/consuming/control/errors"
|
2
|
+
require "emque/consuming/control/workers"
|
3
|
+
|
4
|
+
module Emque
|
5
|
+
module Consuming
|
6
|
+
class Control
|
7
|
+
include Emque::Consuming::Helpers
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@errors = Emque::Consuming::Control::Errors.new
|
11
|
+
@workers = Emque::Consuming::Control::Workers.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def errors(*args)
|
15
|
+
if args[0] && @errors.respond_to?(args[0])
|
16
|
+
@errors.send(args.shift, *args)
|
17
|
+
true
|
18
|
+
else
|
19
|
+
@errors
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def workers(topic = nil, command = nil, *args)
|
24
|
+
if command && topic && @workers.respond_to?(command)
|
25
|
+
@workers.send(command, topic, *args)
|
26
|
+
true
|
27
|
+
else
|
28
|
+
@workers
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "celluloid"
|
2
|
+
require "inflecto"
|
3
|
+
require "emque/consuming/configuration"
|
4
|
+
require "emque/consuming/adapter"
|
5
|
+
require "emque/consuming/logging"
|
6
|
+
require "emque/consuming/router"
|
7
|
+
require "emque/consuming/helpers"
|
8
|
+
|
9
|
+
module Emque
|
10
|
+
module Consuming
|
11
|
+
module Core
|
12
|
+
def self.extended(descendant)
|
13
|
+
descendant.class_eval do
|
14
|
+
class << self
|
15
|
+
attr_accessor :root, :topic_mapping, :router, :instance
|
16
|
+
|
17
|
+
alias :configure :instance_exec
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize_core!
|
23
|
+
unless root
|
24
|
+
call_stack = caller_locations.map(&:path)
|
25
|
+
self.root = File.expand_path(
|
26
|
+
"../..",
|
27
|
+
call_stack.find { |call| call !~ %r{lib/emque} }
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
self.topic_mapping = {}
|
32
|
+
|
33
|
+
config.app_name = Inflecto.underscore(to_s).gsub("/application","")
|
34
|
+
|
35
|
+
load_app!
|
36
|
+
initialize_environment!
|
37
|
+
initialize_router!
|
38
|
+
end
|
39
|
+
|
40
|
+
def config
|
41
|
+
@config ||= Emque::Consuming::Configuration.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize_environment!
|
45
|
+
require_relative File.join(
|
46
|
+
root,
|
47
|
+
"config",
|
48
|
+
"environments",
|
49
|
+
"#{emque_env}.rb"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize_logger
|
54
|
+
Emque::Consuming::Logging.initialize_logger(logfile)
|
55
|
+
Emque::Consuming.logger.level = config.log_level
|
56
|
+
Celluloid.logger = Emque::Consuming.logger
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize_router!
|
60
|
+
self.router ||= Emque::Consuming::Router.new
|
61
|
+
require_relative File.join(root, "config", "routes.rb")
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_app!
|
65
|
+
app_files = Dir[File.join(root, "app", "**", "*.rb")]
|
66
|
+
|
67
|
+
app_files.each do |app_file|
|
68
|
+
klass = Inflecto.classify(File.basename(app_file, ".rb"))
|
69
|
+
emque_autoload(klass.to_sym, app_file)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def logfile
|
74
|
+
@logfile ||= File.join(root, "log/#{emque_env}.log").tap do |path|
|
75
|
+
directory = File.dirname(path)
|
76
|
+
Dir.mkdir(directory) unless File.exist?(directory)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def logger
|
81
|
+
Emque::Consuming.logger
|
82
|
+
end
|
83
|
+
|
84
|
+
def emque_env
|
85
|
+
config.env_var || ENV["EMQUE_ENV"] || ENV["RACK_ENV"] || "development"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
3
|
+
module Emque
|
4
|
+
module Consuming
|
5
|
+
class ErrorTracker
|
6
|
+
attr_accessor :occurrences, :limit, :expiration
|
7
|
+
|
8
|
+
def initialize(limit: 5, expiration: 3600)
|
9
|
+
self.limit = limit
|
10
|
+
self.expiration = expiration
|
11
|
+
self.occurrences = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def notice_error_for(context)
|
15
|
+
occurrences[key_for(context)] = Time.now + expiration
|
16
|
+
end
|
17
|
+
|
18
|
+
def limit_reached?
|
19
|
+
count >= limit
|
20
|
+
end
|
21
|
+
|
22
|
+
def count
|
23
|
+
recent_errors.keys.count
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def recent_errors
|
29
|
+
occurrences.delete_if do |key, expiration_time|
|
30
|
+
expiration_time < Time.now
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def key_for(context)
|
35
|
+
Digest::SHA256.hexdigest(context.to_s)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "fileutils"
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module Emque
|
6
|
+
module Consuming
|
7
|
+
module Generators
|
8
|
+
class Application
|
9
|
+
IGNORE = [".", ".."]
|
10
|
+
|
11
|
+
def initialize(options, name)
|
12
|
+
self.name = Inflecto.underscore(name)
|
13
|
+
self.options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
context = Class.new(Object) { |obj|
|
18
|
+
def initialize(options, name)
|
19
|
+
@name = Inflecto.camelize(name)
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_binding; binding; end
|
24
|
+
}.new(options, name).get_binding
|
25
|
+
|
26
|
+
@current_dir = File.realdirpath(Dir.pwd)
|
27
|
+
|
28
|
+
recursively_copy_templates(
|
29
|
+
File.realdirpath(
|
30
|
+
File.join(
|
31
|
+
File.dirname(__FILE__),
|
32
|
+
"..",
|
33
|
+
"..",
|
34
|
+
"..",
|
35
|
+
"templates"
|
36
|
+
)
|
37
|
+
),
|
38
|
+
[current_dir, name],
|
39
|
+
context
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_accessor :name, :options
|
46
|
+
attr_reader :current_dir
|
47
|
+
|
48
|
+
def relative_path(path)
|
49
|
+
path.gsub(current_dir, ".")
|
50
|
+
end
|
51
|
+
|
52
|
+
def recursively_copy_templates(path, nesting, context)
|
53
|
+
Dir.entries(path).each do |e|
|
54
|
+
unless IGNORE.include?(e)
|
55
|
+
loc = File.join(path, e)
|
56
|
+
|
57
|
+
if Dir.exists?(loc)
|
58
|
+
new_nest = nesting + [e]
|
59
|
+
create_path = File.join(*new_nest)
|
60
|
+
|
61
|
+
unless Dir.exists?(create_path)
|
62
|
+
FileUtils.mkdir_p(create_path)
|
63
|
+
puts "created directory #{relative_path(create_path)}"
|
64
|
+
end
|
65
|
+
|
66
|
+
recursively_copy_templates(loc, new_nest, context)
|
67
|
+
elsif e =~ /\.tt$/
|
68
|
+
filename = File.join(
|
69
|
+
FileUtils.mkdir_p(File.join(*nesting)).first,
|
70
|
+
e.gsub(".tt", "")
|
71
|
+
)
|
72
|
+
display = relative_path(filename)
|
73
|
+
overwrite = "Y"
|
74
|
+
|
75
|
+
if File.exists?(filename)
|
76
|
+
print "#{display} exists, overwrite? (yN) "
|
77
|
+
overwrite = $stdin.gets
|
78
|
+
end
|
79
|
+
|
80
|
+
if overwrite.upcase.chomp == "Y"
|
81
|
+
File.open(filename, "w") do |f|
|
82
|
+
f.write(ERB.new(File.read(loc)).result(context))
|
83
|
+
end
|
84
|
+
puts "created file #{display}"
|
85
|
+
else
|
86
|
+
puts "skipping file #{display}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Emque
|
2
|
+
module Consuming
|
3
|
+
module Helpers
|
4
|
+
private
|
5
|
+
|
6
|
+
def app
|
7
|
+
@app ||=
|
8
|
+
Emque::Consuming.application.instance ||
|
9
|
+
Emque::Consuming.application.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def config
|
13
|
+
Emque::Consuming.application.config
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger
|
17
|
+
Emque::Consuming.application.logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def router
|
21
|
+
Emque::Consuming.application.router
|
22
|
+
end
|
23
|
+
|
24
|
+
def runner
|
25
|
+
Emque::Consuming.runner
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Emque
|
4
|
+
module Consuming
|
5
|
+
module Logging
|
6
|
+
class LogFormatter < Logger::Formatter
|
7
|
+
def call(severity, time, progname, msg)
|
8
|
+
"#{time.utc} [#{severity}] #{msg}\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.initialize_logger(log_target = STDOUT)
|
13
|
+
@logger = Logger.new(log_target)
|
14
|
+
@logger.level = Logger::INFO
|
15
|
+
@logger.formatter = LogFormatter.new
|
16
|
+
@logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.logger
|
20
|
+
defined?(@logger) ? @logger : initialize_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.logger=(log)
|
24
|
+
@logger = log || Logger.new("/dev/null")
|
25
|
+
end
|
26
|
+
|
27
|
+
def logger
|
28
|
+
Emque::Consuming::Logging.logger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "virtus"
|
2
|
+
|
3
|
+
module Emque
|
4
|
+
module Consuming
|
5
|
+
class Message
|
6
|
+
include Virtus.value_object
|
7
|
+
|
8
|
+
values do
|
9
|
+
attribute :offset, Integer
|
10
|
+
attribute :original, Object
|
11
|
+
attribute :partition, Integer
|
12
|
+
attribute :status, Symbol, :default => :processing
|
13
|
+
attribute :topic, Symbol, :default => :unknown
|
14
|
+
attribute :values, Hash, :default => {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def continue?
|
18
|
+
status == :processing
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Emque
|
4
|
+
module Consuming
|
5
|
+
class Pidfile
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :path
|
9
|
+
|
10
|
+
def_delegators :pid, :to_i, :to_s
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
self.path = path
|
14
|
+
ensure_dir_exists
|
15
|
+
self.pid = File.read(path).to_i if File.exists?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def running?
|
19
|
+
if pid
|
20
|
+
if pid == 0
|
21
|
+
rm_file
|
22
|
+
else
|
23
|
+
begin
|
24
|
+
Process.getpgid(pid)
|
25
|
+
return true
|
26
|
+
rescue Errno::ESRCH
|
27
|
+
rm_file
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def write
|
35
|
+
File.open(path, "w") do |f|
|
36
|
+
f.puts Process.pid
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_writer :path
|
43
|
+
attr_accessor :pid
|
44
|
+
|
45
|
+
def ensure_dir_exists
|
46
|
+
FileUtils.mkdir_p(File.dirname(path))
|
47
|
+
end
|
48
|
+
|
49
|
+
def rm_file
|
50
|
+
FileUtils.rm_f(path) if File.exists?(path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Emque
|
2
|
+
module Consuming
|
3
|
+
class Router
|
4
|
+
def initialize
|
5
|
+
self.mappings = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def map(&block)
|
9
|
+
self.instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def topic(mapping, &block)
|
13
|
+
mapping = Mapping.new(mapping, &block)
|
14
|
+
mappings[mapping.topic.to_sym] ||= []
|
15
|
+
mappings[mapping.topic.to_sym] << mapping
|
16
|
+
end
|
17
|
+
|
18
|
+
def route(topic, type, message)
|
19
|
+
mappings[topic.to_sym].each do |mapping|
|
20
|
+
method = mapping.route(type.to_s)
|
21
|
+
|
22
|
+
if method
|
23
|
+
consumer = mapping.consumer
|
24
|
+
|
25
|
+
consumer.new.consume(method, message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def topic_mapping
|
31
|
+
mappings.inject({}) do |hash, (topic, maps)|
|
32
|
+
hash.tap do |h|
|
33
|
+
h[topic] = maps.map(&:consumer)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def workers(topic)
|
39
|
+
mappings[topic.to_sym].map(&:workers).max
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_accessor :mappings
|
45
|
+
|
46
|
+
class Mapping
|
47
|
+
attr_reader :consumer, :topic, :workers
|
48
|
+
|
49
|
+
def initialize(mapping, &block)
|
50
|
+
self.topic = mapping.keys.first
|
51
|
+
self.workers = mapping.fetch(:workers, 1)
|
52
|
+
self.consumer = mapping.values.first
|
53
|
+
self.mapping = {}
|
54
|
+
|
55
|
+
self.instance_eval(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def map(map)
|
59
|
+
mapping.merge!(map)
|
60
|
+
end
|
61
|
+
|
62
|
+
def route(type)
|
63
|
+
mapping[type]
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_accessor :mapping
|
69
|
+
attr_writer :consumer, :topic, :workers
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|