karafka 0.5.0.3 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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