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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.console_irbrc +13 -0
  3. data/.github/ISSUE_TEMPLATE.md +2 -0
  4. data/.gitignore +1 -0
  5. data/CHANGELOG.md +59 -1
  6. data/CODE_OF_CONDUCT.md +46 -0
  7. data/CONTRIBUTING.md +67 -0
  8. data/Gemfile +2 -1
  9. data/Gemfile.lock +46 -147
  10. data/README.md +51 -952
  11. data/Rakefile +5 -14
  12. data/karafka.gemspec +19 -13
  13. data/lib/karafka.rb +7 -4
  14. data/lib/karafka/app.rb +10 -6
  15. data/lib/karafka/attributes_map.rb +67 -0
  16. data/lib/karafka/base_controller.rb +42 -52
  17. data/lib/karafka/base_responder.rb +30 -14
  18. data/lib/karafka/base_worker.rb +11 -26
  19. data/lib/karafka/cli.rb +2 -0
  20. data/lib/karafka/cli/base.rb +2 -0
  21. data/lib/karafka/cli/console.rb +7 -1
  22. data/lib/karafka/cli/flow.rb +13 -13
  23. data/lib/karafka/cli/info.rb +7 -4
  24. data/lib/karafka/cli/install.rb +4 -3
  25. data/lib/karafka/cli/server.rb +3 -1
  26. data/lib/karafka/cli/worker.rb +2 -0
  27. data/lib/karafka/connection/config_adapter.rb +103 -0
  28. data/lib/karafka/connection/listener.rb +16 -12
  29. data/lib/karafka/connection/messages_consumer.rb +86 -0
  30. data/lib/karafka/connection/messages_processor.rb +74 -0
  31. data/lib/karafka/errors.rb +15 -29
  32. data/lib/karafka/fetcher.rb +10 -8
  33. data/lib/karafka/helpers/class_matcher.rb +2 -0
  34. data/lib/karafka/helpers/config_retriever.rb +46 -0
  35. data/lib/karafka/helpers/multi_delegator.rb +2 -0
  36. data/lib/karafka/loader.rb +4 -2
  37. data/lib/karafka/logger.rb +37 -36
  38. data/lib/karafka/monitor.rb +3 -1
  39. data/lib/karafka/params/interchanger.rb +2 -0
  40. data/lib/karafka/params/params.rb +34 -41
  41. data/lib/karafka/params/params_batch.rb +46 -0
  42. data/lib/karafka/parsers/json.rb +4 -2
  43. data/lib/karafka/patches/dry_configurable.rb +2 -0
  44. data/lib/karafka/process.rb +4 -2
  45. data/lib/karafka/responders/builder.rb +2 -0
  46. data/lib/karafka/responders/topic.rb +14 -6
  47. data/lib/karafka/routing/builder.rb +22 -59
  48. data/lib/karafka/routing/consumer_group.rb +54 -0
  49. data/lib/karafka/routing/mapper.rb +2 -0
  50. data/lib/karafka/routing/proxy.rb +37 -0
  51. data/lib/karafka/routing/router.rb +18 -16
  52. data/lib/karafka/routing/topic.rb +78 -0
  53. data/lib/karafka/schemas/config.rb +36 -0
  54. data/lib/karafka/schemas/consumer_group.rb +56 -0
  55. data/lib/karafka/schemas/responder_usage.rb +38 -0
  56. data/lib/karafka/server.rb +5 -3
  57. data/lib/karafka/setup/config.rb +79 -32
  58. data/lib/karafka/setup/configurators/base.rb +2 -0
  59. data/lib/karafka/setup/configurators/celluloid.rb +2 -0
  60. data/lib/karafka/setup/configurators/sidekiq.rb +2 -0
  61. data/lib/karafka/setup/configurators/water_drop.rb +15 -3
  62. data/lib/karafka/status.rb +2 -0
  63. data/lib/karafka/templates/app.rb.example +15 -5
  64. data/lib/karafka/templates/application_worker.rb.example +0 -6
  65. data/lib/karafka/version.rb +2 -1
  66. data/lib/karafka/workers/builder.rb +2 -0
  67. metadata +109 -60
  68. data/lib/karafka/cli/routes.rb +0 -36
  69. data/lib/karafka/connection/consumer.rb +0 -33
  70. data/lib/karafka/connection/message.rb +0 -17
  71. data/lib/karafka/connection/topic_consumer.rb +0 -94
  72. data/lib/karafka/responders/usage_validator.rb +0 -60
  73. data/lib/karafka/routing/route.rb +0 -113
  74. data/lib/karafka/setup/config_schema.rb +0 -44
  75. data/lib/karafka/setup/configurators/worker_glass.rb +0 -13
  76. data/lib/karafka/templates/config.ru.example +0 -13
@@ -1,36 +0,0 @@
1
- module Karafka
2
- # Karafka framework Cli
3
- class Cli
4
- # Routes Karafka Cli action
5
- class Routes < Base
6
- desc 'Print out all defined routes in alphabetical order'
7
- option aliases: 'r'
8
-
9
- # Print out all defined routes in alphabetical order
10
- def call
11
- routes.each do |route|
12
- puts "#{route.topic}:"
13
- Karafka::Routing::Route::ATTRIBUTES.each do |attr|
14
- print(attr.to_s.capitalize, route.public_send(attr))
15
- end
16
- end
17
- end
18
-
19
- private
20
-
21
- # @return [Array<Karafka::Routing::Route>] all routes sorted in alphabetical order
22
- def routes
23
- Karafka::App.routes.sort do |route1, route2|
24
- route1.topic <=> route2.topic
25
- end
26
- end
27
-
28
- # Prints a given value with label in a nice way
29
- # @param label [String] label describing value
30
- # @param value [String] value that should be printed
31
- def print(label, value)
32
- printf "%-18s %s\n", " - #{label}:", value
33
- end
34
- end
35
- end
36
- end
@@ -1,33 +0,0 @@
1
- module Karafka
2
- module Connection
3
- # Class that consumes messages for which we listen
4
- class Consumer
5
- # Consumes a message (does something with it)
6
- # It will execute a scheduling task from a proper controller based on a message topic
7
- # @note This should be looped to obtain a constant listening
8
- # @note We catch all the errors here, to make sure that none failures
9
- # for a given consumption will affect other consumed messages
10
- # If we would't catch it, it would propagate up until killing the Celluloid actor
11
- # @param message [Kafka::FetchedMessage] message that was fetched by kafka
12
- def consume(message)
13
- # We map from incoming topic name, as it might be namespaced, etc.
14
- # @see topic_mapper internal docs
15
- mapped_topic = Karafka::App.config.topic_mapper.incoming(message.topic)
16
-
17
- controller = Karafka::Routing::Router.new(mapped_topic).build
18
- # We wrap it around with our internal message format, so we don't pass around
19
- # a raw Kafka message
20
- controller.params = Message.new(mapped_topic, message.value)
21
-
22
- Karafka.monitor.notice(self.class, controller.to_h)
23
-
24
- controller.schedule
25
- # This is on purpose - see the notes for this method
26
- # rubocop:disable RescueException
27
- rescue Exception => e
28
- # rubocop:enable RescueException
29
- Karafka.monitor.notice_error(self.class, e)
30
- end
31
- end
32
- end
33
- end
@@ -1,17 +0,0 @@
1
- module Karafka
2
- # Namespace that encapsulates everything related to connections
3
- module Connection
4
- # Single incoming Kafka message instance wrapper
5
- class Message
6
- attr_reader :topic, :content
7
-
8
- # @param topic [String] topic from which this message comes
9
- # @param content [String] raw message content (not deserialized or anything) from Kafka
10
- # @return [Karafka::Connection::Message] incoming message instance
11
- def initialize(topic, content)
12
- @topic = topic
13
- @content = content
14
- end
15
- end
16
- end
17
- end
@@ -1,94 +0,0 @@
1
- module Karafka
2
- module Connection
3
- # Class used as a wrapper around Ruby-Kafka to simplify additional
4
- # features that we provide/might provide in future
5
- class TopicConsumer
6
- # How long should we wait before trying to reconnect to Kafka cluster
7
- # that went down (in seconds)
8
- RECONNECT_TIMEOUT = 5
9
-
10
- # Creates a queue consumer that will pull the data from Kafka
11
- # @param [Karafka::Routing::Route] route details that will be used to build up a
12
- # queue consumer instance
13
- # @return [Karafka::Connection::QueueConsumer] queue consumer instance
14
- def initialize(route)
15
- @route = route
16
- end
17
-
18
- # Opens connection, gets messages and calls a block for each of the incoming messages
19
- # @yieldparam [Kafka::FetchedMessage] kafka fetched message
20
- # @note This will yield with a raw message - no preprocessing or reformatting
21
- def fetch_loop
22
- send(
23
- @route.batch_mode ? :consume_each_batch : :consume_each_message
24
- ) do |message|
25
- yield(message)
26
- end
27
- end
28
-
29
- # Gracefuly stops topic consumption
30
- def stop
31
- @kafka_consumer&.stop
32
- @kafka_consumer = nil
33
- end
34
-
35
- private
36
-
37
- # Consumes messages from Kafka in batches
38
- # @yieldparam [Kafka::FetchedMessage] kafka fetched message
39
- def consume_each_batch
40
- kafka_consumer.each_batch do |batch|
41
- batch.messages.each do |message|
42
- yield(message)
43
- end
44
- end
45
- end
46
-
47
- # Consumes messages from Kafka one by one
48
- # @yieldparam [Kafka::FetchedMessage] kafka fetched message
49
- def consume_each_message
50
- kafka_consumer.each_message do |message|
51
- yield(message)
52
- end
53
- end
54
-
55
- # @return [Kafka::Consumer] returns a ready to consume Kafka consumer
56
- # that is set up to consume a given routes topic
57
- def kafka_consumer
58
- @kafka_consumer ||= kafka.consumer(
59
- group_id: @route.group,
60
- session_timeout: ::Karafka::App.config.kafka.session_timeout,
61
- offset_commit_interval: ::Karafka::App.config.kafka.offset_commit_interval,
62
- offset_commit_threshold: ::Karafka::App.config.kafka.offset_commit_threshold,
63
- heartbeat_interval: ::Karafka::App.config.kafka.heartbeat_interval
64
- ).tap do |consumer|
65
- consumer.subscribe(
66
- @route.topic,
67
- start_from_beginning: @route.start_from_beginning
68
- )
69
- end
70
- rescue Kafka::ConnectionError
71
- # If we would not wait it would totally spam log file with failed
72
- # attempts if Kafka is down
73
- sleep(RECONNECT_TIMEOUT)
74
- # We don't log and just reraise - this will be logged
75
- # down the road
76
- raise
77
- end
78
-
79
- # @return [Kafka] returns a Kafka
80
- # @note We don't cache it internally because we cache kafka_consumer that uses kafka
81
- # object instance
82
- def kafka
83
- Kafka.new(
84
- seed_brokers: ::Karafka::App.config.kafka.hosts,
85
- logger: ::Karafka.logger,
86
- client_id: ::Karafka::App.config.name,
87
- ssl_ca_cert: ::Karafka::App.config.kafka.ssl.ca_cert,
88
- ssl_client_cert: ::Karafka::App.config.kafka.ssl.client_cert,
89
- ssl_client_cert_key: ::Karafka::App.config.kafka.ssl.client_cert_key
90
- )
91
- end
92
- end
93
- end
94
- end
@@ -1,60 +0,0 @@
1
- module Karafka
2
- module Responders
3
- # Usage validator checks if all the requirements related to responders topics were met
4
- class UsageValidator
5
- # @param registered_topics [Hash] Hash with registered topics objects from
6
- # a given responder class under it's name key
7
- # @param used_topics [Array<String>] Array with names of topics that we used in this
8
- # responding process
9
- # @return [Karafka::Responders::UsageValidator] responding flow usage validator
10
- def initialize(registered_topics, used_topics)
11
- @registered_topics = registered_topics
12
- @used_topics = used_topics
13
- end
14
-
15
- # Validates the whole flow
16
- # @raise [Karafka::Errors::UnregisteredTopic] raised when we used a topic that we didn't
17
- # register using #topic method
18
- # @raise [Karafka::Errors::TopicMultipleUsage] raised when we used a non multipleusage topic
19
- # multiple times
20
- # @raise [Karafka::Errors::UnusedResponderRequiredTopic] raised when we didn't use a topic
21
- # that was defined as required to be used
22
- def validate!
23
- @used_topics.each do |used_topic|
24
- validate_usage_of!(used_topic)
25
- end
26
-
27
- @registered_topics.each do |_name, registered_topic|
28
- validate_requirements_of!(registered_topic)
29
- end
30
- end
31
-
32
- private
33
-
34
- # Checks if a given used topic were used in a proper way
35
- # @raise [Karafka::Errors::UnregisteredTopic] raised when we used a topic that we didn't
36
- # register using #topic method
37
- # @raise [Karafka::Errors::TopicMultipleUsage] raised when we used a non multipleusage topic
38
- # multiple times
39
- # @param used_topic [String] topic to which we've sent a message
40
- def validate_usage_of!(used_topic)
41
- raise(Errors::UnregisteredTopic, used_topic) unless @registered_topics[used_topic]
42
- return if @registered_topics[used_topic].multiple_usage?
43
- return unless @registered_topics[used_topic].required?
44
- return if @used_topics.count(used_topic) < 2
45
- raise(Errors::TopicMultipleUsage, used_topic)
46
- end
47
-
48
- # Checks if we met all the requirements for all the registered topics
49
- # @raise [Karafka::Errors::UnusedResponderRequiredTopic] raised when we didn't use a topic
50
- # that was defined as required to be used
51
- # @param registered_topic [::Karafka::Responders::Topic] registered topic object
52
- def validate_requirements_of!(registered_topic)
53
- return unless registered_topic.required?
54
- return if @used_topics.include?(registered_topic.name)
55
-
56
- raise(Errors::UnusedResponderRequiredTopic, registered_topic.name)
57
- end
58
- end
59
- end
60
- end
@@ -1,113 +0,0 @@
1
- module Karafka
2
- module Routing
3
- # Class representing a single route (from topic to worker) with all additional features
4
- # and elements. Single route contains descriptions of:
5
- # - topic - Kafka topic name (required)
6
- # - controller - Class of a controller that will handle messages from a given topic (required)
7
- # - group - Kafka group that we want to use (optional)
8
- # - worker - Which worker should handle the backend task (optional)
9
- # - parser - What parsed do we want to use to unparse the data (optional)
10
- # - interchanger - What interchanger to encode/decode data do we want to use (optional)
11
- class Route
12
- # Only ASCII alphanumeric characters, underscore, dash and dots
13
- # are allowed in topics and groups
14
- NAME_FORMAT = /\A(\w|\-|\.)+\z/
15
-
16
- # Options that we can set per each route
17
- ATTRIBUTES = %i(
18
- group
19
- topic
20
- worker
21
- parser
22
- interchanger
23
- responder
24
- inline_mode
25
- batch_mode
26
- start_from_beginning
27
- ).freeze
28
-
29
- ATTRIBUTES.each { |attr| attr_writer(attr) }
30
-
31
- # This we can get "directly" because it does not have any details, etc
32
- attr_accessor :controller
33
-
34
- # Initializes default values for all the options that support defaults if their values are
35
- # not yet specified. This is need to be done (cannot be lazy loaded on first use) because
36
- # everywhere except Karafka server command, those would not be initialized on time - for
37
- # example for Sidekiq
38
- def build
39
- ATTRIBUTES.each { |attr| send(attr) }
40
- self
41
- end
42
-
43
- # @return [String] Kafka group name
44
- # @note If group is not provided in a route, will build one based on the app name
45
- # and the route topic (that is required)
46
- def group
47
- (@group ||= "#{Karafka::App.config.name.underscore}_#{topic}").to_s
48
- end
49
-
50
- # @return [String] route topic - this is the core esence of Kafka
51
- def topic
52
- @topic.to_s
53
- end
54
-
55
- # @return [Class] Class (not an instance) of a worker that should be used to schedule the
56
- # background job
57
- # @note If not provided - will be built based on the provided controller
58
- def worker
59
- @worker ||= inline_mode ? nil : Karafka::Workers::Builder.new(controller).build
60
- end
61
-
62
- # @return [Class, nil] Class (not an instance) of a responder that should respond from
63
- # controller back to Kafka (usefull for piping dataflows)
64
- def responder
65
- @responder ||= Karafka::Responders::Builder.new(controller).build
66
- end
67
-
68
- # @return [Class] Parser class (not instance) that we want to use to unparse Kafka messages
69
- # @note If not provided - will use Json as default
70
- def parser
71
- @parser ||= Karafka::Parsers::Json
72
- end
73
-
74
- # @return [Class] Interchanger class (not an instance) that we want to use to interchange
75
- # params between Karafka server and Karafka background job
76
- def interchanger
77
- @interchanger ||= Karafka::Params::Interchanger
78
- end
79
-
80
- # @return [Boolean] Should we perform execution in the background (default) or
81
- # inline. This can be set globally and overwritten by a per route setting
82
- # @note This method can be set to false, so direct assigment ||= would not work
83
- def inline_mode
84
- return @inline_mode unless @inline_mode.nil?
85
- @inline_mode = Karafka::App.config.inline_mode
86
- end
87
-
88
- # @return [Boolean] Should the consumer handle incoming events one at a time, or in batch
89
- def batch_mode
90
- return @batch_mode unless @batch_mode.nil?
91
- @batch_mode = Karafka::App.config.batch_mode
92
- end
93
-
94
- # For each topic subscription it's possible to decide whether to consume messages starting
95
- # at the beginning of the topic or to just consume new messages that are produced to
96
- # the topic.
97
- # @return [Boolean] Should we consume from the beggining or from new incoming messages on
98
- # the first run
99
- def start_from_beginning
100
- return @start_from_beginning unless @start_from_beginning.nil?
101
- @start_from_beginning = Karafka::App.config.start_from_beginning
102
- end
103
-
104
- # Checks if topic and group have proper format (acceptable by Kafka)
105
- # @raise [Karafka::Errors::InvalidTopicName] raised when topic name is invalid
106
- # @raise [Karafka::Errors::InvalidGroupName] raised when group name is invalid
107
- def validate!
108
- raise Errors::InvalidTopicName, topic if NAME_FORMAT !~ topic
109
- raise Errors::InvalidGroupName, group if NAME_FORMAT !~ group
110
- end
111
- end
112
- end
113
- end
@@ -1,44 +0,0 @@
1
- module Karafka
2
- module Setup
3
- # Schema with validation rules for all configuration
4
- ConfigSchema = Dry::Validation.Schema do
5
- required(:name).filled(:str?)
6
- required(:topic_mapper).filled
7
- optional(:inline_mode).filled(:bool?)
8
-
9
- required(:redis).maybe do
10
- schema do
11
- required(:url).filled(:str?)
12
- end
13
- end
14
-
15
- # If inline_mode is true, redis should be filled
16
- rule(redis_presence: [:redis, :inline_mode]) do |redis, inline_mode|
17
- inline_mode.false?.then(redis.filled?)
18
- end
19
-
20
- optional(:batch_mode).filled(:bool?)
21
- optional(:start_from_beginning).filled(:bool?)
22
-
23
- optional(:connection_pool).schema do
24
- required(:size).filled
25
- optional(:timeout).filled(:int?)
26
- end
27
-
28
- required(:kafka).schema do
29
- required(:hosts).filled(:array?)
30
-
31
- required(:session_timeout).filled(:int?)
32
- required(:offset_commit_interval).filled(:int?)
33
- required(:offset_commit_threshold).filled(:int?)
34
- required(:heartbeat_interval).filled(:int?)
35
-
36
- optional(:ssl).schema do
37
- required(:ca_cert).maybe(:str?)
38
- required(:client_cert).maybe(:str?)
39
- required(:client_cert_key).maybe(:str?)
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,13 +0,0 @@
1
- module Karafka
2
- module Setup
3
- class Configurators
4
- # Class responsible for setting up WorkerGlass settings
5
- class WorkerGlass < Base
6
- # Sets up a Karafka logger as celluloid logger
7
- def setup
8
- ::WorkerGlass.logger = ::Karafka.logger
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- %w(
2
- puma
3
- sidekiq/web
4
- ).each { |lib| require lib }
5
-
6
- require Karafka.boot_file
7
-
8
- use Rack::Auth::Basic, 'Protected Area' do |username, password|
9
- username == 'sidekiq' &&
10
- password == 'Pa$$WorD!'
11
- end
12
-
13
- run Sidekiq::Web