karafka 0.5.0.3 → 0.6.0.rc1
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 +4 -4
- data/.console_irbrc +13 -0
- data/.github/ISSUE_TEMPLATE.md +2 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +59 -1
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +67 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +46 -147
- data/README.md +51 -952
- data/Rakefile +5 -14
- data/karafka.gemspec +19 -13
- data/lib/karafka.rb +7 -4
- data/lib/karafka/app.rb +10 -6
- data/lib/karafka/attributes_map.rb +67 -0
- data/lib/karafka/base_controller.rb +42 -52
- data/lib/karafka/base_responder.rb +30 -14
- data/lib/karafka/base_worker.rb +11 -26
- data/lib/karafka/cli.rb +2 -0
- data/lib/karafka/cli/base.rb +2 -0
- data/lib/karafka/cli/console.rb +7 -1
- data/lib/karafka/cli/flow.rb +13 -13
- data/lib/karafka/cli/info.rb +7 -4
- data/lib/karafka/cli/install.rb +4 -3
- data/lib/karafka/cli/server.rb +3 -1
- data/lib/karafka/cli/worker.rb +2 -0
- data/lib/karafka/connection/config_adapter.rb +103 -0
- data/lib/karafka/connection/listener.rb +16 -12
- data/lib/karafka/connection/messages_consumer.rb +86 -0
- data/lib/karafka/connection/messages_processor.rb +74 -0
- data/lib/karafka/errors.rb +15 -29
- data/lib/karafka/fetcher.rb +10 -8
- data/lib/karafka/helpers/class_matcher.rb +2 -0
- data/lib/karafka/helpers/config_retriever.rb +46 -0
- data/lib/karafka/helpers/multi_delegator.rb +2 -0
- data/lib/karafka/loader.rb +4 -2
- data/lib/karafka/logger.rb +37 -36
- data/lib/karafka/monitor.rb +3 -1
- data/lib/karafka/params/interchanger.rb +2 -0
- data/lib/karafka/params/params.rb +34 -41
- data/lib/karafka/params/params_batch.rb +46 -0
- data/lib/karafka/parsers/json.rb +4 -2
- data/lib/karafka/patches/dry_configurable.rb +2 -0
- data/lib/karafka/process.rb +4 -2
- data/lib/karafka/responders/builder.rb +2 -0
- data/lib/karafka/responders/topic.rb +14 -6
- data/lib/karafka/routing/builder.rb +22 -59
- data/lib/karafka/routing/consumer_group.rb +54 -0
- data/lib/karafka/routing/mapper.rb +2 -0
- data/lib/karafka/routing/proxy.rb +37 -0
- data/lib/karafka/routing/router.rb +18 -16
- data/lib/karafka/routing/topic.rb +78 -0
- data/lib/karafka/schemas/config.rb +36 -0
- data/lib/karafka/schemas/consumer_group.rb +56 -0
- data/lib/karafka/schemas/responder_usage.rb +38 -0
- data/lib/karafka/server.rb +5 -3
- data/lib/karafka/setup/config.rb +79 -32
- data/lib/karafka/setup/configurators/base.rb +2 -0
- data/lib/karafka/setup/configurators/celluloid.rb +2 -0
- data/lib/karafka/setup/configurators/sidekiq.rb +2 -0
- data/lib/karafka/setup/configurators/water_drop.rb +15 -3
- data/lib/karafka/status.rb +2 -0
- data/lib/karafka/templates/app.rb.example +15 -5
- data/lib/karafka/templates/application_worker.rb.example +0 -6
- data/lib/karafka/version.rb +2 -1
- data/lib/karafka/workers/builder.rb +2 -0
- metadata +109 -60
- data/lib/karafka/cli/routes.rb +0 -36
- data/lib/karafka/connection/consumer.rb +0 -33
- data/lib/karafka/connection/message.rb +0 -17
- data/lib/karafka/connection/topic_consumer.rb +0 -94
- data/lib/karafka/responders/usage_validator.rb +0 -60
- data/lib/karafka/routing/route.rb +0 -113
- data/lib/karafka/setup/config_schema.rb +0 -44
- data/lib/karafka/setup/configurators/worker_glass.rb +0 -13
- data/lib/karafka/templates/config.ru.example +0 -13
data/lib/karafka/cli.rb
CHANGED
data/lib/karafka/cli/base.rb
CHANGED
data/lib/karafka/cli/console.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
# Karafka framework Cli
|
3
5
|
class Cli
|
@@ -10,7 +12,11 @@ module Karafka
|
|
10
12
|
# @example
|
11
13
|
# Karafka::Cli::Console.command #=> 'KARAFKA_CONSOLE=true bundle exec irb...'
|
12
14
|
def self.command
|
13
|
-
|
15
|
+
envs = [
|
16
|
+
"IRBRC='#{Karafka.gem_root}/.console_irbrc'",
|
17
|
+
'KARAFKA_CONSOLE=true'
|
18
|
+
]
|
19
|
+
"#{envs.join(' ')} bundle exec irb"
|
14
20
|
end
|
15
21
|
|
16
22
|
# Start the Karafka console
|
data/lib/karafka/cli/flow.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
# Karafka framework Cli
|
3
5
|
class Cli
|
@@ -7,32 +9,30 @@ module Karafka
|
|
7
9
|
|
8
10
|
# Print out all defined routes in alphabetical order
|
9
11
|
def call
|
10
|
-
|
11
|
-
any_topics = !
|
12
|
+
topics.each do |topic|
|
13
|
+
any_topics = !topic.responder&.topics.nil?
|
12
14
|
|
13
15
|
if any_topics
|
14
|
-
puts "#{
|
16
|
+
puts "#{topic.name} =>"
|
15
17
|
|
16
|
-
|
18
|
+
topic.responder.topics.each do |_name, responder_topic|
|
17
19
|
features = []
|
18
|
-
features << (
|
19
|
-
features << (
|
20
|
+
features << (responder_topic.required? ? 'always' : 'conditionally')
|
21
|
+
features << (responder_topic.multiple_usage? ? 'one or more' : 'exactly once')
|
20
22
|
|
21
|
-
print
|
23
|
+
print responder_topic.name, "(#{features.join(', ')})"
|
22
24
|
end
|
23
25
|
else
|
24
|
-
puts "#{
|
26
|
+
puts "#{topic.name} => (nothing)"
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
30
32
|
|
31
|
-
# @return [Array<Karafka::Routing::
|
32
|
-
def
|
33
|
-
Karafka::App.
|
34
|
-
route1.topic <=> route2.topic
|
35
|
-
end
|
33
|
+
# @return [Array<Karafka::Routing::Topic>] all topics sorted in alphabetical order
|
34
|
+
def topics
|
35
|
+
Karafka::App.consumer_groups.map(&:topics).flatten.sort_by(&:name)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Prints a given value with label in a nice way
|
data/lib/karafka/cli/info.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
# Karafka framework Cli
|
3
5
|
class Cli
|
@@ -11,13 +13,14 @@ module Karafka
|
|
11
13
|
|
12
14
|
info = [
|
13
15
|
"Karafka framework version: #{Karafka::VERSION}",
|
14
|
-
"Application
|
15
|
-
"Inline
|
16
|
-
"Batch
|
16
|
+
"Application client id: #{config.client_id}",
|
17
|
+
"Inline processing: #{config.inline_processing}",
|
18
|
+
"Batch consuming: #{config.batch_consuming}",
|
19
|
+
"Batch processing: #{config.batch_processing}",
|
17
20
|
"Number of threads: #{config.concurrency}",
|
18
21
|
"Boot file: #{Karafka.boot_file}",
|
19
22
|
"Environment: #{Karafka.env}",
|
20
|
-
"Kafka
|
23
|
+
"Kafka seed brokers: #{config.kafka.seed_brokers}",
|
21
24
|
"Redis: #{config.redis.to_h}"
|
22
25
|
]
|
23
26
|
|
data/lib/karafka/cli/install.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
# Karafka framework Cli
|
3
5
|
class Cli
|
@@ -6,7 +8,7 @@ module Karafka
|
|
6
8
|
desc 'Install all required things for Karafka application in current directory'
|
7
9
|
|
8
10
|
# Directories created by default
|
9
|
-
INSTALL_DIRS = %w
|
11
|
+
INSTALL_DIRS = %w[
|
10
12
|
app/models
|
11
13
|
app/controllers
|
12
14
|
app/responders
|
@@ -14,12 +16,11 @@ module Karafka
|
|
14
16
|
config
|
15
17
|
log
|
16
18
|
tmp/pids
|
17
|
-
|
19
|
+
].freeze
|
18
20
|
|
19
21
|
# Where should we map proper files from templates
|
20
22
|
INSTALL_FILES_MAP = {
|
21
23
|
'app.rb.example' => Karafka.boot_file.basename,
|
22
|
-
'config.ru.example' => 'config.ru',
|
23
24
|
'sidekiq.yml.example' => 'config/sidekiq.yml.example',
|
24
25
|
'application_worker.rb.example' => 'app/workers/application_worker.rb',
|
25
26
|
'application_controller.rb.example' => 'app/controllers/application_controller.rb',
|
data/lib/karafka/cli/server.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
# Karafka framework Cli
|
3
5
|
class Cli
|
@@ -26,7 +28,7 @@ module Karafka
|
|
26
28
|
end
|
27
29
|
|
28
30
|
# Remove pidfile on shutdown
|
29
|
-
ObjectSpace.define_finalizer(
|
31
|
+
ObjectSpace.define_finalizer(String.new, proc { send(:clean) })
|
30
32
|
|
31
33
|
# After we fork, we can boot celluloid again
|
32
34
|
Karafka::Server.run
|
data/lib/karafka/cli/worker.rb
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
# Namespace for all the things related to Kafka connection
|
5
|
+
module Connection
|
6
|
+
# Mapper used to convert our internal settings into ruby-kafka settings
|
7
|
+
# Since ruby-kafka has more and more options and there are few "levels" on which
|
8
|
+
# we have to apply them (despite the fact, that in Karafka you configure all of it
|
9
|
+
# in one place), we have to remap it into what ruby-kafka driver requires
|
10
|
+
# @note The good thing about Kafka.new method is that it ignores all options that
|
11
|
+
# do nothing. So we don't have to worry about injecting our internal settings
|
12
|
+
# into the client and breaking stuff
|
13
|
+
module ConfigAdapter
|
14
|
+
class << self
|
15
|
+
# Builds all the configuration settings for Kafka.new method
|
16
|
+
# @param _consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
17
|
+
# @return [Hash] hash with all the settings required by Kafka.new method
|
18
|
+
def client(_consumer_group)
|
19
|
+
# This one is a default that takes all the settings except special
|
20
|
+
# cases defined in the map
|
21
|
+
settings = {
|
22
|
+
logger: ::Karafka.logger,
|
23
|
+
client_id: ::Karafka::App.config.client_id
|
24
|
+
}
|
25
|
+
|
26
|
+
kafka_configs.each do |setting_name, setting_value|
|
27
|
+
# All options for config adapter should be ignored as we're just interested
|
28
|
+
# in what is left, as we want to pass all the options that are "typical"
|
29
|
+
# and not listed in the config_adapter special cases mapping. All the values
|
30
|
+
# from the config_adapter mapping go somewhere else, not to the client directly
|
31
|
+
next if AttributesMap.config_adapter.values.flatten.include?(setting_name)
|
32
|
+
|
33
|
+
settings[setting_name] = setting_value
|
34
|
+
end
|
35
|
+
|
36
|
+
sanitize(settings)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Builds all the configuration settings for kafka#consumer method
|
40
|
+
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
41
|
+
# @return [Hash] hash with all the settings required by Kafka#consumer method
|
42
|
+
def consumer(consumer_group)
|
43
|
+
settings = { group_id: consumer_group.id }
|
44
|
+
|
45
|
+
kafka_configs.each do |setting_name, setting_value|
|
46
|
+
next unless AttributesMap.config_adapter[:consumer].include?(setting_name)
|
47
|
+
next if settings.keys.include?(setting_name)
|
48
|
+
settings[setting_name] = setting_value
|
49
|
+
end
|
50
|
+
|
51
|
+
sanitize(settings)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Builds all the configuration settings for kafka consumer consume_each_batch and
|
55
|
+
# consume_each_message methods
|
56
|
+
# @param _consumer_group [Karafka::Routing::ConsumerGroup] consumer group details
|
57
|
+
# @return [Hash] hash with all the settings required by
|
58
|
+
# Kafka::Consumer#consume_each_message and Kafka::Consumer#consume_each_batch method
|
59
|
+
def consuming(_consumer_group)
|
60
|
+
settings = {}
|
61
|
+
|
62
|
+
kafka_configs.each do |setting_name, setting_value|
|
63
|
+
next unless AttributesMap.config_adapter[:consuming].include?(setting_name)
|
64
|
+
next if settings.keys.include?(setting_name)
|
65
|
+
settings[setting_name] = setting_value
|
66
|
+
end
|
67
|
+
|
68
|
+
sanitize(settings)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Builds all the configuration settings for kafka consumer#subscribe method
|
72
|
+
# @param topic [Karafka::Routing::Topic] topic that holds details for a given subscription
|
73
|
+
# @return [Hash] hash with all the settings required by kafka consumer#subscribe method
|
74
|
+
def subscription(topic)
|
75
|
+
settings = { start_from_beginning: topic.start_from_beginning }
|
76
|
+
|
77
|
+
kafka_configs.each do |setting_name, setting_value|
|
78
|
+
next unless AttributesMap.config_adapter[:subscription].include?(setting_name)
|
79
|
+
next if settings.keys.include?(setting_name)
|
80
|
+
settings[setting_name] = setting_value
|
81
|
+
end
|
82
|
+
|
83
|
+
[topic.name, sanitize(settings)]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Removes nil containing keys from the final settings so it can use Kafkas driver
|
89
|
+
# defaults for those
|
90
|
+
# @param settings [Hash] settings that may contain nil values
|
91
|
+
# @return [Hash] settings without nil using keys (non of karafka options should be nil)
|
92
|
+
def sanitize(settings)
|
93
|
+
settings.reject { |_key, value| value.nil? }
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Hash] Kafka config details as a hash
|
97
|
+
def kafka_configs
|
98
|
+
::Karafka::App.config.kafka.to_h
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Karafka
|
2
4
|
module Connection
|
3
5
|
# A single listener that listens to incoming messages from a single route
|
@@ -9,16 +11,18 @@ module Karafka
|
|
9
11
|
|
10
12
|
execute_block_on_receiver :fetch_loop
|
11
13
|
|
12
|
-
attr_reader :
|
14
|
+
attr_reader :consumer_group
|
13
15
|
|
16
|
+
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group that holds details
|
17
|
+
# on what topics and with what settings should we listen
|
14
18
|
# @return [Karafka::Connection::Listener] listener instance
|
15
|
-
def initialize(
|
16
|
-
@
|
19
|
+
def initialize(consumer_group)
|
20
|
+
@consumer_group = consumer_group
|
17
21
|
end
|
18
22
|
|
19
23
|
# Opens connection, gets messages and calls a block for each of the incoming messages
|
20
|
-
# @yieldparam [
|
21
|
-
# @yieldparam [Kafka::FetchedMessage] kafka fetched
|
24
|
+
# @yieldparam [String] consumer group id
|
25
|
+
# @yieldparam [Array<Kafka::FetchedMessage>] kafka fetched messages
|
22
26
|
# @note This will yield with a raw message - no preprocessing or reformatting
|
23
27
|
# @note We catch all the errors here, so they don't affect other listeners (or this one)
|
24
28
|
# so we will be able to listen and consume other incoming messages.
|
@@ -27,25 +31,25 @@ module Karafka
|
|
27
31
|
# Kafka connections / Internet connection issues / Etc. Business logic problems should not
|
28
32
|
# propagate this far
|
29
33
|
def fetch_loop(block)
|
30
|
-
|
31
|
-
block.call(
|
34
|
+
messages_consumer.fetch_loop do |raw_messages|
|
35
|
+
block.call(consumer_group.id, raw_messages)
|
32
36
|
end
|
33
37
|
# This is on purpose - see the notes for this method
|
34
38
|
# rubocop:disable RescueException
|
35
39
|
rescue Exception => e
|
36
40
|
# rubocop:enable RescueException
|
37
41
|
Karafka.monitor.notice_error(self.class, e)
|
38
|
-
@
|
39
|
-
retry if @
|
42
|
+
@messages_consumer&.stop
|
43
|
+
retry if @messages_consumer
|
40
44
|
end
|
41
45
|
|
42
46
|
private
|
43
47
|
|
44
|
-
# @return [Karafka::Connection::
|
48
|
+
# @return [Karafka::Connection::MessagesConsumer] wrapped kafka consumer for a given topic
|
45
49
|
# consumption
|
46
50
|
# @note It adds consumer into Karafka::Server consumers pool for graceful shutdown on exit
|
47
|
-
def
|
48
|
-
@
|
51
|
+
def messages_consumer
|
52
|
+
@messages_consumer ||= MessagesConsumer.new(consumer_group).tap do |consumer|
|
49
53
|
Karafka::Server.consumers << consumer if Karafka::Server.consumers
|
50
54
|
end
|
51
55
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Connection
|
5
|
+
# Class used as a wrapper around Ruby-Kafka to simplify additional
|
6
|
+
# features that we provide/might provide in future
|
7
|
+
class MessagesConsumer
|
8
|
+
# Creates a queue consumer that will pull the data from Kafka
|
9
|
+
# @param consumer_group [Karafka::Routing::ConsumerGroup] consumer group for which
|
10
|
+
# we create a client
|
11
|
+
# @return [Karafka::Connection::MessagesConsumer] group consumer that can subscribe to
|
12
|
+
# multiple topics
|
13
|
+
def initialize(consumer_group)
|
14
|
+
@consumer_group = consumer_group
|
15
|
+
end
|
16
|
+
|
17
|
+
# Opens connection, gets messages and calls a block for each of the incoming messages
|
18
|
+
# @yieldparam [Array<Kafka::FetchedMessage>] kafka fetched messages
|
19
|
+
# @note This will yield with raw messages - no preprocessing or reformatting.
|
20
|
+
def fetch_loop
|
21
|
+
send(
|
22
|
+
consumer_group.batch_consuming ? :consume_each_batch : :consume_each_message
|
23
|
+
) do |messages|
|
24
|
+
yield(messages)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Gracefuly stops topic consumption
|
29
|
+
def stop
|
30
|
+
@kafka_consumer&.stop
|
31
|
+
@kafka_consumer = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :consumer_group
|
37
|
+
|
38
|
+
# Consumes messages from Kafka in batches
|
39
|
+
# @yieldparam [Array<Kafka::FetchedMessage>] kafka fetched messages
|
40
|
+
def consume_each_batch
|
41
|
+
kafka_consumer.each_batch(
|
42
|
+
ConfigAdapter.consuming(consumer_group)
|
43
|
+
) do |batch|
|
44
|
+
yield(batch.messages)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Consumes messages from Kafka one by one
|
49
|
+
# @yieldparam [Array<Kafka::FetchedMessage>] kafka fetched messages
|
50
|
+
def consume_each_message
|
51
|
+
kafka_consumer.each_message(
|
52
|
+
ConfigAdapter.consuming(consumer_group)
|
53
|
+
) do |message|
|
54
|
+
# always yield an array of messages, so we have consistent API (always a batch)
|
55
|
+
yield([message])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Kafka::Consumer] returns a ready to consume Kafka consumer
|
60
|
+
# that is set up to consume from topics of a given consumer group
|
61
|
+
def kafka_consumer
|
62
|
+
@kafka_consumer ||= kafka.consumer(
|
63
|
+
ConfigAdapter.consumer(consumer_group)
|
64
|
+
).tap do |consumer|
|
65
|
+
consumer_group.topics.each do |topic|
|
66
|
+
consumer.subscribe(*ConfigAdapter.subscription(topic))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue Kafka::ConnectionError
|
70
|
+
# If we would not wait it would totally spam log file with failed
|
71
|
+
# attempts if Kafka is down
|
72
|
+
sleep(consumer_group.reconnect_timeout)
|
73
|
+
# We don't log and just reraise - this will be logged
|
74
|
+
# down the road
|
75
|
+
raise
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Kafka] returns a Kafka
|
79
|
+
# @note We don't cache it internally because we cache kafka_consumer that uses kafka
|
80
|
+
# object instance
|
81
|
+
def kafka
|
82
|
+
Kafka.new(ConfigAdapter.client(consumer_group))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Connection
|
5
|
+
# Class that consumes messages for which we listen
|
6
|
+
module MessagesProcessor
|
7
|
+
class << self
|
8
|
+
# Processes messages (does something with them)
|
9
|
+
# It will either schedule or run a proper controller action for messages
|
10
|
+
# @note This should be looped to obtain a constant listening
|
11
|
+
# @note We catch all the errors here, to make sure that none failures
|
12
|
+
# for a given consumption will affect other consumed messages
|
13
|
+
# If we wouldn't catch it, it would propagate up until killing the Celluloid actor
|
14
|
+
# @param group_id [String] group_id of a group from which a given message came
|
15
|
+
# @param kafka_messages [Array<Kafka::FetchedMessage>] raw messages fetched from kafka
|
16
|
+
def process(group_id, kafka_messages)
|
17
|
+
# @note We always get messages by topic and partition so we can take topic from the
|
18
|
+
# first one and it will be valid for all the messages
|
19
|
+
# We map from incoming topic name, as it might be namespaced, etc.
|
20
|
+
# @see topic_mapper internal docs
|
21
|
+
mapped_topic = Karafka::App.config.topic_mapper.incoming(kafka_messages[0].topic)
|
22
|
+
# @note We search based on the topic id - that is a combination of group id and
|
23
|
+
# topic name
|
24
|
+
controller = Karafka::Routing::Router.build("#{group_id}_#{mapped_topic}")
|
25
|
+
handler = controller.topic.batch_processing ? :process_batch : :process_each
|
26
|
+
|
27
|
+
send(handler, controller, mapped_topic, kafka_messages)
|
28
|
+
# This is on purpose - see the notes for this method
|
29
|
+
# rubocop:disable RescueException
|
30
|
+
rescue Exception => e
|
31
|
+
# rubocop:enable RescueException
|
32
|
+
Karafka.monitor.notice_error(self, e)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Processes whole batch in one request (all at once)
|
38
|
+
# @param controller [Karafka::BaseController] base controller descendant
|
39
|
+
# @param mapped_topic [String] mapped topic name
|
40
|
+
# @param kafka_messages [Array<Kafka::FetchedMessage>] raw messages from kafka
|
41
|
+
def process_batch(controller, mapped_topic, kafka_messages)
|
42
|
+
messages_batch = kafka_messages.map do |kafka_message|
|
43
|
+
# Since we support topic mapping (for Kafka providers that require namespaces)
|
44
|
+
# we have to overwrite topic with our mapped topic version
|
45
|
+
# @note For the default mapper, it will be the same as topic
|
46
|
+
# @note We have to use instance_variable_set, as the Kafka::FetchedMessage does not
|
47
|
+
# provide attribute writers
|
48
|
+
kafka_message.instance_variable_set(:'@topic', mapped_topic)
|
49
|
+
kafka_message
|
50
|
+
end
|
51
|
+
|
52
|
+
controller.params_batch = messages_batch
|
53
|
+
|
54
|
+
Karafka.monitor.notice(self, messages_batch)
|
55
|
+
|
56
|
+
controller.schedule
|
57
|
+
end
|
58
|
+
|
59
|
+
# Processes messages one by one (like with std http requests)
|
60
|
+
# @param controller [Karafka::BaseController] base controller descendant
|
61
|
+
# @param mapped_topic [String] mapped topic name
|
62
|
+
# @param kafka_messages [Array<Kafka::FetchedMessage>] raw messages from kafka
|
63
|
+
def process_each(controller, mapped_topic, kafka_messages)
|
64
|
+
kafka_messages.each do |kafka_message|
|
65
|
+
# @note This is a simple trick - we just process one after another, but in order
|
66
|
+
# not to handle everywhere both cases (single vs batch), we just "fake" batching with
|
67
|
+
# a single message for each
|
68
|
+
process_batch(controller, mapped_topic, [kafka_message])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|