karafka 2.1.12 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +41 -0
  4. data/Gemfile.lock +1 -1
  5. data/bin/record_rss +50 -0
  6. data/config/locales/errors.yml +4 -0
  7. data/config/locales/pro_errors.yml +17 -0
  8. data/lib/karafka/admin.rb +21 -33
  9. data/lib/karafka/connection/client.rb +1 -1
  10. data/lib/karafka/contracts/config.rb +24 -0
  11. data/lib/karafka/errors.rb +3 -0
  12. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +5 -2
  13. data/lib/karafka/messages/builders/message.rb +8 -4
  14. data/lib/karafka/pro/active_job/consumer.rb +1 -1
  15. data/lib/karafka/pro/cleaner/errors.rb +27 -0
  16. data/lib/karafka/pro/cleaner/messages/message.rb +46 -0
  17. data/lib/karafka/pro/cleaner/messages/messages.rb +42 -0
  18. data/lib/karafka/pro/cleaner.rb +41 -0
  19. data/lib/karafka/pro/contracts/base.rb +23 -0
  20. data/lib/karafka/pro/contracts/server_cli_options.rb +111 -0
  21. data/lib/karafka/pro/encryption/errors.rb +4 -1
  22. data/lib/karafka/pro/loader.rb +6 -2
  23. data/lib/karafka/pro/processing/strategies/dlq/default.rb +6 -0
  24. data/lib/karafka/pro/routing/features/active_job/builder.rb +45 -0
  25. data/lib/karafka/pro/routing/features/active_job.rb +26 -0
  26. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +53 -0
  27. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +41 -0
  28. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +41 -0
  29. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +44 -0
  30. data/lib/karafka/pro/routing/features/long_running_job/{contract.rb → contracts/topic.rb} +14 -11
  31. data/lib/karafka/pro/routing/features/{filtering/contract.rb → patterns/builder.rb} +13 -16
  32. data/lib/karafka/pro/routing/features/patterns/config.rb +54 -0
  33. data/lib/karafka/pro/routing/features/patterns/consumer_group.rb +68 -0
  34. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +62 -0
  35. data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +46 -0
  36. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +41 -0
  37. data/lib/karafka/pro/routing/features/patterns/detector.rb +68 -0
  38. data/lib/karafka/pro/routing/features/patterns/pattern.rb +81 -0
  39. data/lib/karafka/pro/routing/features/{delaying/contract.rb → patterns/patterns.rb} +11 -14
  40. data/lib/karafka/pro/routing/features/patterns/topic.rb +50 -0
  41. data/lib/karafka/pro/routing/features/patterns/topics.rb +53 -0
  42. data/lib/karafka/pro/routing/features/patterns.rb +33 -0
  43. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +51 -0
  44. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +44 -0
  45. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +55 -0
  46. data/lib/karafka/routing/consumer_group.rb +1 -1
  47. data/lib/karafka/routing/features/active_job/contracts/topic.rb +44 -0
  48. data/lib/karafka/routing/features/active_job/proxy.rb +14 -0
  49. data/lib/karafka/routing/features/base/expander.rb +8 -2
  50. data/lib/karafka/routing/features/base.rb +4 -2
  51. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +46 -0
  52. data/lib/karafka/routing/features/declaratives/contracts/topic.rb +33 -0
  53. data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +27 -0
  54. data/lib/karafka/routing/router.rb +0 -11
  55. data/lib/karafka/routing/subscription_group.rb +9 -0
  56. data/lib/karafka/routing/topic.rb +5 -0
  57. data/lib/karafka/server.rb +9 -4
  58. data/lib/karafka/setup/config.rb +45 -0
  59. data/lib/karafka/version.rb +1 -1
  60. data.tar.gz.sig +0 -0
  61. metadata +37 -15
  62. metadata.gz.sig +0 -0
  63. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +0 -50
  64. data/lib/karafka/pro/routing/features/expiring/contract.rb +0 -38
  65. data/lib/karafka/pro/routing/features/pausing/contract.rb +0 -48
  66. data/lib/karafka/pro/routing/features/throttling/contract.rb +0 -41
  67. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +0 -52
  68. data/lib/karafka/routing/features/active_job/contract.rb +0 -41
  69. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +0 -42
  70. data/lib/karafka/routing/features/declaratives/contract.rb +0 -30
  71. data/lib/karafka/routing/features/manual_offset_management/contract.rb +0 -24
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Contracts
17
+ # Contract for validating correctness of the server cli command options.
18
+ # It differs slightly from the OSS one because it is aware of the routing patterns
19
+ class ServerCliOptions < ::Karafka::Contracts::ServerCliOptions
20
+ configure do |config|
21
+ config.error_messages = YAML.safe_load(
22
+ File.read(
23
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
24
+ )
25
+ ).fetch('en').fetch('validations').fetch('server_cli_options')
26
+ end
27
+
28
+ %i[
29
+ include
30
+ exclude
31
+ ].each do |action|
32
+ optional(:"#{action}_consumer_groups") { |cg| cg.is_a?(Array) }
33
+ optional(:"#{action}_subscription_groups") { |sg| sg.is_a?(Array) }
34
+ optional(:"#{action}_topics") { |topics| topics.is_a?(Array) }
35
+
36
+ virtual do |data, errors|
37
+ next unless errors.empty?
38
+
39
+ value = data.fetch(:"#{action}_consumer_groups")
40
+
41
+ # If there were no consumer_groups declared in the server cli, it means that we will
42
+ # run all of them and no need to validate them here at all
43
+ next if value.empty?
44
+ next if (value - Karafka::App.consumer_groups.map(&:name)).empty?
45
+
46
+ # Found unknown consumer groups
47
+ [[[:"#{action}_consumer_groups"], :consumer_groups_inclusion]]
48
+ end
49
+
50
+ virtual do |data, errors|
51
+ next unless errors.empty?
52
+
53
+ value = data.fetch(:"#{action}_subscription_groups")
54
+
55
+ # If there were no subscription_groups declared in the server cli, it means that we
56
+ # will run all of them and no need to validate them here at all
57
+ next if value.empty?
58
+
59
+ subscription_groups = Karafka::App
60
+ .consumer_groups
61
+ .map(&:subscription_groups)
62
+ .flatten
63
+ .map(&:name)
64
+
65
+ next if (value - subscription_groups).empty?
66
+
67
+ # Found unknown subscription groups
68
+ [[[:"#{action}_subscription_groups"], :subscription_groups_inclusion]]
69
+ end
70
+
71
+ virtual do |data, errors|
72
+ next unless errors.empty?
73
+
74
+ value = data.fetch(:"#{action}_topics")
75
+
76
+ # If there were no topics declared in the server cli, it means that we will
77
+ # run all of them and no need to validate them here at all
78
+ next if value.empty?
79
+
80
+ topics = Karafka::App
81
+ .consumer_groups
82
+ .map(&:subscription_groups)
83
+ .flatten
84
+ .map(&:topics)
85
+ .map { |gtopics| gtopics.map(&:name) }
86
+ .flatten
87
+
88
+ next if (value - topics).empty?
89
+
90
+ # If there are any patterns defined, we cannot report on topics inclusions because
91
+ # topics may be added during boot or runtime. We go with simple assumption:
92
+ # if there are patterns defined, we do not check the inclusions at all
93
+ next unless Karafka::App.consumer_groups.map(&:patterns).flatten.empty?
94
+
95
+ # Found unknown topics
96
+ [[[:"#{action}_topics"], :topics_inclusion]]
97
+ end
98
+ end
99
+
100
+ # Makes sure we have anything to subscribe to when we start the server
101
+ virtual do |_, errors|
102
+ next unless errors.empty?
103
+
104
+ next unless Karafka::App.subscription_groups.empty?
105
+
106
+ [[%i[include_topics], :topics_missing]]
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -16,8 +16,11 @@ module Karafka
16
16
  module Encryption
17
17
  # Encryption related errors
18
18
  module Errors
19
+ # Base for all the encryption errors
20
+ BaseError = Class.new(::Karafka::Errors::BaseError)
21
+
19
22
  # Raised when we have encountered encryption key with version we do not have
20
- PrivateKeyNotFound = Class.new(::Karafka::Errors::BaseError)
23
+ PrivateKeyNotFound = Class.new(BaseError)
21
24
  end
22
25
  end
23
26
  end
@@ -66,10 +66,12 @@ module Karafka
66
66
 
67
67
  private
68
68
 
69
- # @return [Array<Module>] extra non-routing related pro features
69
+ # @return [Array<Module>] extra non-routing related pro features and routing components
70
+ # that need to have some special configuration stuff injected into config, etc
70
71
  def features
71
72
  [
72
- Encryption
73
+ Encryption,
74
+ Cleaner
73
75
  ]
74
76
  end
75
77
 
@@ -78,6 +80,8 @@ module Karafka
78
80
  def reconfigure(config)
79
81
  icfg = config.internal
80
82
 
83
+ icfg.cli.contract = Contracts::ServerCliOptions.new
84
+
81
85
  icfg.processing.coordinator_class = Processing::Coordinator
82
86
  icfg.processing.partitioner_class = Processing::Partitioner
83
87
  icfg.processing.scheduler = Processing::Scheduler.new
@@ -74,6 +74,12 @@ module Karafka
74
74
  # @param skippable_message [Array<Karafka::Messages::Message>] message we want to
75
75
  # dispatch to DLQ
76
76
  def dispatch_to_dlq(skippable_message)
77
+ # DLQ should never try to dispatch a message that was cleaned. It message was
78
+ # cleaned, we will not have all the needed data. If you see this error, it means
79
+ # that your processing flow is not as expected and you have cleaned message that
80
+ # should not be cleaned as it should go to the DLQ
81
+ raise(Cleaner::Errors::MessageCleanedError) if skippable_message.cleaned?
82
+
77
83
  producer.produce_async(
78
84
  build_dlq_message(
79
85
  skippable_message
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class ActiveJob < Base
19
+ # Pro ActiveJob builder expansions
20
+ module Builder
21
+ # This method simplifies routes definition for ActiveJob patterns / queues by
22
+ # auto-injecting the consumer class and other things needed
23
+ #
24
+ # @param regexp_or_name [String, Symbol, Regexp] pattern name or regexp to use
25
+ # auto-generated regexp names
26
+ # @param regexp [Regexp, nil] activejob regexp pattern or nil when regexp is provided
27
+ # as the first argument
28
+ # @param block [Proc] block that we can use for some extra configuration
29
+ def active_job_pattern(regexp_or_name, regexp = nil, &block)
30
+ pattern(regexp_or_name, regexp) do
31
+ consumer App.config.internal.active_job.consumer_class
32
+ active_job true
33
+ manual_offset_management true
34
+
35
+ next unless block
36
+
37
+ instance_eval(&block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ # Small extension to make ActiveJob work with pattern matching.
19
+ # Since our `#active_job_topic` is just a topic wrapper, we can introduce a similar
20
+ # `#active_job_pattern` to align with pattern building
21
+ class ActiveJob < Base
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class DeadLetterQueue < Base
19
+ # Namespace for DLQ contracts
20
+ module Contracts
21
+ # Extended rules for dead letter queue settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ # Make sure that when we use virtual partitions with DLQ, at least one retry is set
32
+ # We cannot use VP with DLQ without retries as we in order to provide ordering
33
+ # warranties on errors with VP, we need to collapse the VPs concurrency and retry
34
+ # without any indeterministic work
35
+ virtual do |data, errors|
36
+ next unless errors.empty?
37
+
38
+ dead_letter_queue = data[:dead_letter_queue]
39
+ virtual_partitions = data[:virtual_partitions]
40
+
41
+ next unless dead_letter_queue[:active]
42
+ next unless virtual_partitions[:active]
43
+ next if dead_letter_queue[:max_retries].positive?
44
+
45
+ [[%i[dead_letter_queue], :with_virtual_partitions]]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Delaying < Base
19
+ # Namespace for delaying feature contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the expiring feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:delaying) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ required(:delay) { |val| val.nil? || (val.is_a?(Integer) && val.positive?) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Expiring < Base
19
+ # Namespace for expiring messages contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the expiring feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:expiring) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ required(:ttl) { |val| val.nil? || (val.is_a?(Integer) && val.positive?) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Filtering < Base
19
+ # Namespace for filtering feature contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the filtering feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:filtering) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+
34
+ required(:factories) do |val|
35
+ val.is_a?(Array) && val.all? { |factory| factory.respond_to?(:call) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -16,18 +16,21 @@ module Karafka
16
16
  module Routing
17
17
  module Features
18
18
  class LongRunningJob < Base
19
- # Rules around long-running job settings
20
- class Contract < Contracts::Base
21
- configure do |config|
22
- config.error_messages = YAML.safe_load(
23
- File.read(
24
- File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
25
- )
26
- ).fetch('en').fetch('validations').fetch('topic')
27
- end
19
+ # Namespace for LRJ contracts
20
+ module Contracts
21
+ # Rules around long-running job settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
28
30
 
29
- nested(:long_running_job) do
30
- required(:active) { |val| [true, false].include?(val) }
31
+ nested(:long_running_job) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ end
31
34
  end
32
35
  end
33
36
  end
@@ -15,22 +15,19 @@ module Karafka
15
15
  module Pro
16
16
  module Routing
17
17
  module Features
18
- class Filtering < Base
19
- # Contract to validate configuration of the filtering feature
20
- class Contract < Contracts::Base
21
- configure do |config|
22
- config.error_messages = YAML.safe_load(
23
- File.read(
24
- File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
25
- )
26
- ).fetch('en').fetch('validations').fetch('topic')
27
- end
28
-
29
- nested(:filtering) do
30
- required(:active) { |val| [true, false].include?(val) }
31
-
32
- required(:factories) do |val|
33
- val.is_a?(Array) && val.all? { |factory| factory.respond_to?(:call) }
18
+ class Patterns < Base
19
+ # Expansions for the routing builder
20
+ module Builder
21
+ # Allows us to define the simple routing pattern matching
22
+ #
23
+ # @param regexp_or_name [Symbol, String, Regexp] name of the pattern or regexp for
24
+ # automatic-based named patterns
25
+ # @param regexp [Regexp, nil] nil if we use auto-generated name based on the regexp or
26
+ # the regexp if we used named patterns
27
+ # @param block [Proc]
28
+ def pattern(regexp_or_name, regexp = nil, &block)
29
+ consumer_group('app') do
30
+ pattern(regexp_or_name, regexp, &block)
34
31
  end
35
32
  end
36
33
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ # Holds pattern info reference
19
+ # Type is set to:
20
+ # `:regular` - in case patterns are not used and topic is just a regular existing topic
21
+ # matched directly based on the name
22
+ # `:discovered` - in case it is a real topic on which we started to listed
23
+ # `:matcher` - represents a regular expression used by librdkafka
24
+ class Patterns < Base
25
+ # Config for pattern based topic
26
+ # Only pattern related topics are active in this context
27
+ Config = Struct.new(
28
+ :active,
29
+ :type,
30
+ :pattern,
31
+ keyword_init: true
32
+ ) do
33
+ alias_method :active?, :active
34
+
35
+ # @return [Boolean] is this a matcher topic
36
+ def matcher?
37
+ type == :matcher
38
+ end
39
+
40
+ # @return [Boolean] is this a discovered topic
41
+ def discovered?
42
+ type == :discovered
43
+ end
44
+
45
+ # @return [Boolean] is this a regular topic
46
+ def regular?
47
+ type == :regular
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Patterns < Base
19
+ # Expansion of the consumer groups routing component to work with patterns
20
+ module ConsumerGroup
21
+ # @param args [Object] whatever consumer group accepts
22
+ def initialize(*args)
23
+ super
24
+ @patterns = Patterns.new([])
25
+ end
26
+
27
+ # @return [::Karafka::Pro::Routing::Features::Patterns::Patterns] created patterns
28
+ def patterns
29
+ @patterns
30
+ end
31
+
32
+ # Creates the pattern for topic matching with appropriate virtual topic
33
+ # @param regexp_or_name [Symbol, String, Regexp] name of the pattern or regexp for
34
+ # automatic-based named patterns
35
+ # @param regexp [Regexp, nil] nil if we use auto-generated name based on the regexp or
36
+ # the regexp if we used named patterns
37
+ # @param block [Proc] appropriate underlying topic settings
38
+ def pattern=(regexp_or_name, regexp = nil, &block)
39
+ # This code allows us to have a nice nameless (automatic-named) patterns that do not
40
+ # have to be explicitly named. However if someone wants to use names for exclusions
41
+ # it can be done by providing both
42
+ if regexp_or_name.is_a?(Regexp)
43
+ name = nil
44
+ regexp = regexp_or_name
45
+ else
46
+ name = regexp_or_name
47
+ end
48
+
49
+ pattern = Pattern.new(name, regexp, block)
50
+ virtual_topic = public_send(:topic=, pattern.name, &block)
51
+ # Indicate the nature of this topic (matcher)
52
+ virtual_topic.patterns(active: true, type: :matcher, pattern: pattern)
53
+ pattern.topic = virtual_topic
54
+ @patterns << pattern
55
+ end
56
+
57
+ # @return [Hash] consumer group with patterns injected
58
+ def to_h
59
+ super.merge(
60
+ patterns: patterns.map(&:to_h)
61
+ ).freeze
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Patterns < Base
19
+ module Contracts
20
+ # Contract to validate configuration of the filtering feature
21
+ class ConsumerGroup < Karafka::Contracts::Base
22
+ configure do |config|
23
+ config.error_messages = YAML.safe_load(
24
+ File.read(
25
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
26
+ )
27
+ ).fetch('en').fetch('validations').fetch('consumer_group')
28
+
29
+ required(:patterns) { |val| val.is_a?(Array) && val.all? { |el| el.is_a?(Hash) } }
30
+
31
+ virtual do |data, errors|
32
+ next unless errors.empty?
33
+
34
+ validator = Pattern.new
35
+
36
+ data[:patterns].each do |pattern|
37
+ validator.validate!(pattern)
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ # Make sure, that there are no same regular expressions with different names
44
+ # in a single consumer group
45
+ virtual do |data, errors|
46
+ next unless errors.empty?
47
+
48
+ regexp_strings = data[:patterns].map { |pattern| pattern.fetch(:regexp_string) }
49
+
50
+ next if regexp_strings.empty?
51
+ next if regexp_strings.uniq.size == regexp_strings.size
52
+
53
+ [[%i[patterns], :regexps_not_unique]]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end