karafka 0.5.0
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.
- 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
|