karafka 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +68 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +202 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +216 -0
- data/MIT-LICENCE +18 -0
- data/README.md +831 -0
- data/Rakefile +17 -0
- data/bin/karafka +7 -0
- data/karafka.gemspec +34 -0
- data/lib/karafka.rb +73 -0
- data/lib/karafka/app.rb +45 -0
- data/lib/karafka/base_controller.rb +162 -0
- data/lib/karafka/base_responder.rb +118 -0
- data/lib/karafka/base_worker.rb +41 -0
- data/lib/karafka/capistrano.rb +2 -0
- data/lib/karafka/capistrano/karafka.cap +84 -0
- data/lib/karafka/cli.rb +52 -0
- data/lib/karafka/cli/base.rb +74 -0
- data/lib/karafka/cli/console.rb +23 -0
- data/lib/karafka/cli/flow.rb +46 -0
- data/lib/karafka/cli/info.rb +26 -0
- data/lib/karafka/cli/install.rb +45 -0
- data/lib/karafka/cli/routes.rb +39 -0
- data/lib/karafka/cli/server.rb +59 -0
- data/lib/karafka/cli/worker.rb +26 -0
- data/lib/karafka/connection/consumer.rb +29 -0
- data/lib/karafka/connection/listener.rb +54 -0
- data/lib/karafka/connection/message.rb +17 -0
- data/lib/karafka/connection/topic_consumer.rb +48 -0
- data/lib/karafka/errors.rb +50 -0
- data/lib/karafka/fetcher.rb +40 -0
- data/lib/karafka/helpers/class_matcher.rb +77 -0
- data/lib/karafka/helpers/multi_delegator.rb +31 -0
- data/lib/karafka/loader.rb +77 -0
- data/lib/karafka/logger.rb +52 -0
- data/lib/karafka/monitor.rb +82 -0
- data/lib/karafka/params/interchanger.rb +33 -0
- data/lib/karafka/params/params.rb +102 -0
- data/lib/karafka/patches/dry/configurable/config.rb +37 -0
- data/lib/karafka/process.rb +61 -0
- data/lib/karafka/responders/builder.rb +33 -0
- data/lib/karafka/responders/topic.rb +43 -0
- data/lib/karafka/responders/usage_validator.rb +59 -0
- data/lib/karafka/routing/builder.rb +89 -0
- data/lib/karafka/routing/route.rb +80 -0
- data/lib/karafka/routing/router.rb +38 -0
- data/lib/karafka/server.rb +53 -0
- data/lib/karafka/setup/config.rb +57 -0
- data/lib/karafka/setup/configurators/base.rb +33 -0
- data/lib/karafka/setup/configurators/celluloid.rb +20 -0
- data/lib/karafka/setup/configurators/sidekiq.rb +34 -0
- data/lib/karafka/setup/configurators/water_drop.rb +19 -0
- data/lib/karafka/setup/configurators/worker_glass.rb +13 -0
- data/lib/karafka/status.rb +23 -0
- data/lib/karafka/templates/app.rb.example +26 -0
- data/lib/karafka/templates/application_controller.rb.example +5 -0
- data/lib/karafka/templates/application_responder.rb.example +9 -0
- data/lib/karafka/templates/application_worker.rb.example +12 -0
- data/lib/karafka/templates/config.ru.example +13 -0
- data/lib/karafka/templates/sidekiq.yml.example +26 -0
- data/lib/karafka/version.rb +6 -0
- data/lib/karafka/workers/builder.rb +49 -0
- data/log/.gitkeep +0 -0
- metadata +267 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Default logger for Event Delegator
|
3
|
+
# @note It uses ::Logger features - providing basic logging
|
4
|
+
class Logger < ::Logger
|
5
|
+
# Map containing informations about log level for given environment
|
6
|
+
ENV_MAP = {
|
7
|
+
'production' => ::Logger::ERROR,
|
8
|
+
'test' => ::Logger::ERROR,
|
9
|
+
'development' => ::Logger::INFO,
|
10
|
+
'debug' => ::Logger::DEBUG,
|
11
|
+
default: ::Logger::INFO
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Returns a logger instance with appropriate settings, log level and environment
|
16
|
+
def instance
|
17
|
+
ensure_dir_exists
|
18
|
+
instance = new(target)
|
19
|
+
instance.level = ENV_MAP[Karafka.env] || ENV_MAP[:default]
|
20
|
+
instance
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# @return [Karafka::Helpers::MultiDelegator] multi delegator instance
|
26
|
+
# to which we will be writtng logs
|
27
|
+
# We use this approach to log stuff to file and to the STDOUT at the same time
|
28
|
+
def target
|
29
|
+
Karafka::Helpers::MultiDelegator
|
30
|
+
.delegate(:write, :close)
|
31
|
+
.to(STDOUT, file)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Makes sure the log directory exists
|
35
|
+
def ensure_dir_exists
|
36
|
+
dir = File.dirname(log_path)
|
37
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Pathname] Path to a file to which we should log
|
41
|
+
def log_path
|
42
|
+
Karafka::App.root.join("log/#{Karafka.env}.log")
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [File] file to which we want to write our logs
|
46
|
+
# @note File is being opened in append mode ('a')
|
47
|
+
def file
|
48
|
+
File.open(log_path, 'a')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Monitor is used to hookup external monitoring services to monitor how Karafka works
|
3
|
+
# It provides a standarized API for checking incoming messages/enqueueing etc
|
4
|
+
# By default it implements logging functionalities but can be replaced with any more
|
5
|
+
# sophisticated logging/monitoring system like Errbit, Airbrake, NewRelic
|
6
|
+
# @note This class acts as a singleton because we are only permitted to have single monitor
|
7
|
+
# per running process (just as logger)
|
8
|
+
# Keep in mind, that if you create your own monitor object, you will have to implement also
|
9
|
+
# logging functionality (or just inherit, super and do whatever you want)
|
10
|
+
class Monitor
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
# This method is executed in many important places in the code (during data flow), like
|
14
|
+
# the moment before #perform_async, etc. For full list just grep for 'monitor.notice'
|
15
|
+
# @param caller_class [Class] class of object that executed this call
|
16
|
+
# @param options [Hash] hash with options that we passed to notice. It differs based
|
17
|
+
# on of who and when is calling
|
18
|
+
# @note We don't provide a name of method in which this was called, because we can take
|
19
|
+
# it directly from Ruby (see #caller_label method of this class for more details)
|
20
|
+
# @example Notice about consuming with controller_class
|
21
|
+
# Karafka.monitor.notice(self.class, controller_class: controller_class)
|
22
|
+
# @example Notice about terminating with a signal
|
23
|
+
# Karafka.monitor.notice(self.class, signal: signal)
|
24
|
+
def notice(caller_class, options = {})
|
25
|
+
logger.info("#{caller_class}##{caller_label} with #{options}")
|
26
|
+
end
|
27
|
+
|
28
|
+
# This method is executed when we want to notify about an error that happened somewhere
|
29
|
+
# in the system
|
30
|
+
# @param caller_class [Class] class of object that executed this call
|
31
|
+
# @param e [Exception] exception that was raised
|
32
|
+
# @note We don't provide a name of method in which this was called, because we can take
|
33
|
+
# it directly from Ruby (see #caller_label method of this class for more details)
|
34
|
+
# @example Notify about error
|
35
|
+
# Karafka.monitor.notice(self.class, e)
|
36
|
+
def notice_error(caller_class, e)
|
37
|
+
caller_exceptions_map.each do |level, types|
|
38
|
+
next unless types.include?(caller_class)
|
39
|
+
|
40
|
+
return logger.public_send(level, e)
|
41
|
+
end
|
42
|
+
|
43
|
+
logger.info(e)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @return [Hash] Hash containing informations on which level of notification should
|
49
|
+
# we use for exceptions that happen in certain parts of Karafka
|
50
|
+
# @note Keep in mind that any not handled here class should be logged with info
|
51
|
+
# @note Those are not maps of exceptions classes but of classes that were callers of this
|
52
|
+
# particular exception
|
53
|
+
def caller_exceptions_map
|
54
|
+
@caller_exceptions_map ||= {
|
55
|
+
error: [
|
56
|
+
Karafka::Connection::Consumer,
|
57
|
+
Karafka::Connection::Listener,
|
58
|
+
Karafka::Params::Params
|
59
|
+
],
|
60
|
+
fatal: [
|
61
|
+
Karafka::Fetcher
|
62
|
+
]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [String] label of method that invoked #notice or #notice_error
|
67
|
+
# @example Check label of method that invoked #notice
|
68
|
+
# caller_label #=> 'fetch'
|
69
|
+
# @example Check label of method that invoked #notice in a block
|
70
|
+
# caller_label #=> 'block in fetch'
|
71
|
+
# @example Check label of method that invoked #notice_error
|
72
|
+
# caller_label #=> 'rescue in target'
|
73
|
+
def caller_label
|
74
|
+
caller_locations(1, 2)[1].label
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Logger] logger instance
|
78
|
+
def logger
|
79
|
+
Karafka.logger
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Params
|
3
|
+
# Interchangers allow us to format/encode/pack data that is being send to perform_async
|
4
|
+
# This is meant to target mostly issues with data encoding like this one:
|
5
|
+
# https://github.com/mperham/sidekiq/issues/197
|
6
|
+
# Each custom interchanger should implement following methods:
|
7
|
+
# - load - it is meant to encode params before they get stored inside Redis
|
8
|
+
# - parse - decoded params back to a hash format that we can use
|
9
|
+
class Interchanger
|
10
|
+
class << self
|
11
|
+
# @param params [Karafka::Params::Params] Karafka params object
|
12
|
+
# @note Params might not be parsed because of lazy loading feature. If you implement your
|
13
|
+
# own interchanger logic, this method needs to return data that can be converted to
|
14
|
+
# json with default Sidekiqs logic
|
15
|
+
# @return [Karafka::Params::Params] same as input. We assume that our incoming data is
|
16
|
+
# jsonable-safe and we can rely on a direct Sidekiq encoding logic
|
17
|
+
def load(params)
|
18
|
+
params
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param params [Hash] Sidekiqs params that are now a Hash (after they were JSON#parse)
|
22
|
+
# @note Hash is what we need to build Karafka::Params::Params, so we do nothing
|
23
|
+
# with it. If you implement your own interchanger logic, this method needs to return
|
24
|
+
# a hash with appropriate data that will be used to build Karafka::Params::Params
|
25
|
+
# @return [Hash] We return exactly what we received. We rely on sidekiqs default
|
26
|
+
# interchanging format
|
27
|
+
def parse(params)
|
28
|
+
params
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Params namespace encapsulating all the logic that is directly related to params handling
|
3
|
+
module Params
|
4
|
+
# Class-wrapper for hash with indifferent access with additional lazy loading feature
|
5
|
+
# It provides lazy loading not only until the first usage, but also allows us to skip
|
6
|
+
# using parser until we execute our logic inside worker. That way we can operate with
|
7
|
+
# heavy-parsing data without slowing down the whole application. If we won't use
|
8
|
+
# params in before_enqueue (or if we don't us before_enqueue at all), it will make
|
9
|
+
# Karafka faster, because it will pass data as it is directly to Sidekiq
|
10
|
+
class Params < HashWithIndifferentAccess
|
11
|
+
class << self
|
12
|
+
# We allow building instances only via the #build method
|
13
|
+
private_class_method :new
|
14
|
+
|
15
|
+
# @param message [Karafka::Connection::Message, Hash] message that we get out of Kafka
|
16
|
+
# in case of building params inside main Karafka process in
|
17
|
+
# Karafka::Connection::Consumer, or a hash when we retrieve data from Sidekiq
|
18
|
+
# @param controller [Karafka::BaseController] Karafka's base controllers descendant
|
19
|
+
# instance that wants to use params
|
20
|
+
# @return [Karafka::Params::Params] Karafka params object not yet used parser for
|
21
|
+
# retrieving data that we've got from Kafka
|
22
|
+
# @example Build params instance from a hash
|
23
|
+
# Karafka::Params::Params.build({ key: 'value' }, DataController.new) #=> params object
|
24
|
+
# @example Build params instance from a Karafka::Connection::Message object
|
25
|
+
# Karafka::Params::Params.build(message, IncomingController.new) #=> params object
|
26
|
+
def build(message, controller)
|
27
|
+
# Hash case happens inside workers
|
28
|
+
if message.is_a?(Hash)
|
29
|
+
defaults(controller).merge!(message)
|
30
|
+
else
|
31
|
+
# This happens inside Karafka::Connection::Consumer
|
32
|
+
defaults(controller).merge!(
|
33
|
+
parsed: false,
|
34
|
+
received_at: Time.now,
|
35
|
+
content: message.content
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @param controller [Karafka::BaseController] Karafka's base controllers descendant
|
43
|
+
# instance that wants to use params
|
44
|
+
# @return [Karafka::Params::Params] freshly initialized only with default values object
|
45
|
+
# that can be populated with incoming data
|
46
|
+
def defaults(controller)
|
47
|
+
# We initialize some default values that will be used both in Karafka main process and
|
48
|
+
# inside workers
|
49
|
+
new(
|
50
|
+
controller: controller.class,
|
51
|
+
worker: controller.worker,
|
52
|
+
parser: controller.parser,
|
53
|
+
topic: controller.topic
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Karafka::Params::Params] this will trigger parser execution. If we decide to
|
59
|
+
# retrieve data, parser will be executed to parse data. Output of parsing will be merged
|
60
|
+
# to the current object. This object will be also marked as already parsed, so we won't
|
61
|
+
# parse it again.
|
62
|
+
def retrieve
|
63
|
+
return self if self[:parsed]
|
64
|
+
|
65
|
+
merge!(parse(delete(:content)))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Overwritten merge! method - it behaves differently for keys that are the same in our hash
|
69
|
+
# and in a other_hash - it will not replace keys that are the same in our hash
|
70
|
+
# and in the other one
|
71
|
+
# @param other_hash [Hash, HashWithIndifferentAccess] hash that we want to merge into current
|
72
|
+
# @return [Karafka::Params::Params] our parameters hash with merged values
|
73
|
+
# @example Merge with hash without same keys
|
74
|
+
# new(a: 1, b: 2).merge!(c: 3) #=> { a: 1, b: 2, c: 3 }
|
75
|
+
# @example Merge with hash with same keys (symbol based)
|
76
|
+
# new(a: 1).merge!(a: 2) #=> { a: 1 }
|
77
|
+
# @example Merge with hash with same keys (string based)
|
78
|
+
# new(a: 1).merge!('a' => 2) #=> { a: 1 }
|
79
|
+
# @example Merge with hash with same keys (current string based)
|
80
|
+
# new('a' => 1).merge!(a: 2) #=> { a: 1 }
|
81
|
+
def merge!(other_hash)
|
82
|
+
super(other_hash) { |_key, base_value, _new_value| base_value }
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @param content [String] Raw data that we want to parse using controller's parser
|
88
|
+
# @note If something goes wrong, it will return raw data in a hash with a message key
|
89
|
+
# @return [Hash] parsed data or a hash with message key containing raw data if something
|
90
|
+
# went wrong during parsing
|
91
|
+
def parse(content)
|
92
|
+
self[:parser].parse(content)
|
93
|
+
# We catch both of them, because for default JSON - we use JSON parser directly
|
94
|
+
rescue ::Karafka::Errors::ParserError, JSON::ParserError => e
|
95
|
+
Karafka.monitor.notice_error(self.class, e)
|
96
|
+
return { message: content }
|
97
|
+
ensure
|
98
|
+
self[:parsed] = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Patch that will allow to use proc based lazy evaluated settings with Dry Configurable
|
2
|
+
# @see https://github.com/dry-rb/dry-configurable/blob/master/lib/dry/configurable.rb
|
3
|
+
module Dry
|
4
|
+
# Configurable module for Dry-Configurable
|
5
|
+
module Configurable
|
6
|
+
# Config node instance struct
|
7
|
+
class Config
|
8
|
+
# @param args [Array] All arguments that a Struct accepts
|
9
|
+
def initialize(*args)
|
10
|
+
super
|
11
|
+
setup_dynamics
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Method that sets up all the proc based lazy evaluated dynamic config values
|
17
|
+
def setup_dynamics
|
18
|
+
each_pair do |key, value|
|
19
|
+
next unless value.is_a?(Proc)
|
20
|
+
|
21
|
+
rebuild(key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Method that rebuilds a given accessor, so when it consists a proc value, it will
|
26
|
+
# evaluate it upon return
|
27
|
+
# @param method_name [Symbol] name of an accessor that we want to rebuild
|
28
|
+
def rebuild(method_name)
|
29
|
+
metaclass = class << self; self; end
|
30
|
+
|
31
|
+
metaclass.send(:define_method, method_name) do
|
32
|
+
super().is_a?(Proc) ? super().call : super()
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Class used to catch signals from ruby Signal class in order to manage Karafka shutdown
|
3
|
+
# @note There might be only one process - this class is a singleton
|
4
|
+
class Process
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
# Signal types that we handle
|
8
|
+
HANDLED_SIGNALS = %i(
|
9
|
+
SIGINT SIGQUIT
|
10
|
+
).freeze
|
11
|
+
|
12
|
+
HANDLED_SIGNALS.each do |signal|
|
13
|
+
# Assigns a callback that will happen when certain signal will be send
|
14
|
+
# to Karafka server instance
|
15
|
+
# @note It does not define the callback itself -it needs to be passed in a block
|
16
|
+
# @example Define an action that should be taken on_sigint
|
17
|
+
# process.on_sigint do
|
18
|
+
# Karafka.logger.info('Log something here')
|
19
|
+
# exit
|
20
|
+
# end
|
21
|
+
define_method :"on_#{signal.to_s.downcase}" do |&block|
|
22
|
+
@callbacks[signal] << block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates an instance of process and creates empty hash for callbacks
|
27
|
+
def initialize
|
28
|
+
@callbacks = {}
|
29
|
+
HANDLED_SIGNALS.each { |signal| @callbacks[signal] = [] }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Method catches all HANDLED_SIGNALS and performs appropriate callbacks (if defined)
|
33
|
+
# @note If there are no callbacks, this method will just ignore a given signal that was sent
|
34
|
+
# @yield [Block] block of code that we want to execute and supervise
|
35
|
+
def supervise
|
36
|
+
HANDLED_SIGNALS.each { |signal| trap_signal(signal) }
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Traps a single signal and performs callbacks (if any) or just ignores this signal
|
43
|
+
# @param [Symbol] signal type that we want to catch
|
44
|
+
def trap_signal(signal)
|
45
|
+
trap(signal) do
|
46
|
+
notice_signal(signal)
|
47
|
+
(@callbacks[signal] || []).each(&:call)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Informs monitoring about trapped signal
|
52
|
+
# @param [Symbol] signal type that we received
|
53
|
+
# @note We cannot perform logging from trap context, that's why
|
54
|
+
# we have to spin up a new thread to do this
|
55
|
+
def notice_signal(signal)
|
56
|
+
Thread.new do
|
57
|
+
Karafka.monitor.notice(self.class, signal: signal)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Responders namespace encapsulates all the internal responder implementation parts
|
3
|
+
module Responders
|
4
|
+
# Responders builder is used to find (based on the controller class name) a responder that
|
5
|
+
# match the controller. This is used when user does not provide a responder inside routing
|
6
|
+
# but he still names responder with the same convention (and namespaces) as controller
|
7
|
+
# @example Matching responder exists
|
8
|
+
# Karafka::Responder::Builder(NewEventsController).build #=> NewEventsResponder
|
9
|
+
# @example Matching responder does not exist
|
10
|
+
# Karafka::Responder::Builder(NewBuildsController).build #=> nil
|
11
|
+
class Builder
|
12
|
+
# @param controller_class [Karafka::BaseController, nil] descendant of
|
13
|
+
# Karafka::BaseController
|
14
|
+
# @example Tries to find a responder that matches a given controller. If nothing found,
|
15
|
+
# will return nil (nil is accepted, because it means that a given controller don't
|
16
|
+
# pipe stuff further on)
|
17
|
+
def initialize(controller_class)
|
18
|
+
@controller_class = controller_class
|
19
|
+
end
|
20
|
+
|
21
|
+
# Tries to figure out a responder based on a controller class name
|
22
|
+
# @return [Class] Responder class (not an instance)
|
23
|
+
# @return [nil] or nil if there's no matching responding class
|
24
|
+
def build
|
25
|
+
Helpers::ClassMatcher.new(
|
26
|
+
@controller_class,
|
27
|
+
from: 'Controller',
|
28
|
+
to: 'Responder'
|
29
|
+
).match
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Karafka
|
2
|
+
module Responders
|
3
|
+
# Topic describes a single topic on which we want to respond with responding requirements
|
4
|
+
# @example Define topic (required by default)
|
5
|
+
# Karafka::Responders::Topic.new(:topic_name, {}) #=> #<Karafka::Responders::Topic...
|
6
|
+
# @example Define optional topic
|
7
|
+
# Karafka::Responders::Topic.new(:topic_name, optional: true)
|
8
|
+
# @example Define topic that on which we want to respond multiple times
|
9
|
+
# Karafka::Responders::Topic.new(:topic_name, multiple_usage: true)
|
10
|
+
class Topic
|
11
|
+
# Name of the topic on which we want to respond
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# @param name [Symbol, String] name of a topic on which we want to respond
|
15
|
+
# @param options [Hash] non-default options for this topic
|
16
|
+
# @return [Karafka::Responders::Topic] topic description object
|
17
|
+
def initialize(name, options)
|
18
|
+
@name = name.to_s
|
19
|
+
@options = options
|
20
|
+
validate!
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean] is this a required topic (if not, it is optional)
|
24
|
+
def required?
|
25
|
+
return false if @options[:optional]
|
26
|
+
@options[:required] || true
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean] do we expect to use it multiple times in a single respond flow
|
30
|
+
def multiple_usage?
|
31
|
+
@options[:multiple_usage] || false
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Checks topic name with the same regexp as routing
|
37
|
+
# @raise [Karafka::Errors::InvalidTopicName] raised when topic name is invalid
|
38
|
+
def validate!
|
39
|
+
raise Errors::InvalidTopicName, name if Routing::Route::NAME_FORMAT !~ name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|