waterdrop 1.4.0 → 2.0.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.diffend.yml +3 -0
  5. data/.github/workflows/ci.yml +75 -0
  6. data/.gitignore +2 -0
  7. data/.ruby-version +1 -1
  8. data/CHANGELOG.md +13 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +67 -54
  11. data/LICENSE +165 -0
  12. data/README.md +194 -56
  13. data/config/errors.yml +3 -16
  14. data/docker-compose.yml +17 -0
  15. data/lib/water_drop.rb +4 -24
  16. data/lib/water_drop/config.rb +41 -142
  17. data/lib/water_drop/contracts.rb +0 -2
  18. data/lib/water_drop/contracts/config.rb +8 -121
  19. data/lib/water_drop/contracts/message.rb +41 -0
  20. data/lib/water_drop/errors.rb +31 -5
  21. data/lib/water_drop/instrumentation.rb +7 -0
  22. data/lib/water_drop/instrumentation/monitor.rb +16 -23
  23. data/lib/water_drop/instrumentation/stdout_listener.rb +113 -32
  24. data/lib/water_drop/producer.rb +143 -0
  25. data/lib/water_drop/producer/async.rb +51 -0
  26. data/lib/water_drop/producer/buffer.rb +113 -0
  27. data/lib/water_drop/producer/builder.rb +63 -0
  28. data/lib/water_drop/producer/dummy_client.rb +32 -0
  29. data/lib/water_drop/producer/statistics_decorator.rb +71 -0
  30. data/lib/water_drop/producer/status.rb +52 -0
  31. data/lib/water_drop/producer/sync.rb +65 -0
  32. data/lib/water_drop/version.rb +1 -1
  33. data/waterdrop.gemspec +5 -5
  34. metadata +27 -26
  35. metadata.gz.sig +0 -0
  36. data/.travis.yml +0 -35
  37. data/MIT-LICENCE +0 -18
  38. data/lib/water_drop/async_producer.rb +0 -26
  39. data/lib/water_drop/base_producer.rb +0 -57
  40. data/lib/water_drop/config_applier.rb +0 -52
  41. data/lib/water_drop/contracts/message_options.rb +0 -19
  42. data/lib/water_drop/sync_producer.rb +0 -24
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ class Producer
5
+ # Producer lifecycle status object representation
6
+ class Status
7
+ # States in which the producer can be
8
+ LIFECYCLE = %i[
9
+ initial
10
+ configured
11
+ connected
12
+ closing
13
+ closed
14
+ ].freeze
15
+
16
+ private_constant :LIFECYCLE
17
+
18
+ # Creates a new instance of status with the initial state
19
+ # @return [Status]
20
+ def initialize
21
+ @current = LIFECYCLE.first
22
+ end
23
+
24
+ # @return [Boolean] true if producer is in a active state. Active means, that we can start
25
+ # sending messages. Actives states are connected (connection established) or configured,
26
+ # which means, that producer is configured, but connection with Kafka is
27
+ # not yet established.
28
+ def active?
29
+ connected? || configured?
30
+ end
31
+
32
+ # @return [String] current status as a string
33
+ def to_s
34
+ @current.to_s
35
+ end
36
+
37
+ LIFECYCLE.each do |state|
38
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
39
+ # @return [Boolean] true if current status is as we want, otherwise false
40
+ def #{state}?
41
+ @current == :#{state}
42
+ end
43
+
44
+ # Sets a given state as current
45
+ def #{state}!
46
+ @current = :#{state}
47
+ end
48
+ RUBY
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ class Producer
5
+ # Component for synchronous producer operations
6
+ module Sync
7
+ # Produces a message to Kafka and waits for it to be delivered
8
+ #
9
+ # @param message [Hash] hash that complies with the {Contracts::Message} contract
10
+ #
11
+ # @return [Rdkafka::Producer::DeliveryReport] delivery report
12
+ #
13
+ # @raise [Rdkafka::RdkafkaError] When adding the message to rdkafka's queue failed
14
+ # @raise [Rdkafka::Producer::WaitTimeoutError] When the timeout has been reached and the
15
+ # handle is still pending
16
+ # @raise [Errors::MessageInvalidError] When provided message details are invalid and the
17
+ # message could not be sent to Kafka
18
+ def produce_sync(message)
19
+ ensure_active!
20
+ validate_message!(message)
21
+
22
+ @monitor.instrument(
23
+ 'message.produced_sync',
24
+ producer: self,
25
+ message: message
26
+ ) do
27
+ client
28
+ .produce(**message)
29
+ .wait(
30
+ max_wait_timeout: @config.max_wait_timeout,
31
+ wait_timeout: @config.wait_timeout
32
+ )
33
+ end
34
+ end
35
+
36
+ # Produces many messages to Kafka and waits for them to be delivered
37
+ #
38
+ # @param messages [Array<Hash>] array with messages that comply with the
39
+ # {Contracts::Message} contract
40
+ #
41
+ # @return [Array<Rdkafka::Producer::DeliveryReport>] delivery reports
42
+ #
43
+ # @raise [Rdkafka::RdkafkaError] When adding the messages to rdkafka's queue failed
44
+ # @raise [Rdkafka::Producer::WaitTimeoutError] When the timeout has been reached and the
45
+ # some handles are still pending
46
+ # @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
47
+ # and the message could not be sent to Kafka
48
+ def produce_many_sync(messages)
49
+ ensure_active!
50
+ messages.each { |message| validate_message!(message) }
51
+
52
+ @monitor.instrument('messages.produced_sync', producer: self, messages: messages) do
53
+ messages
54
+ .map { |message| client.produce(**message) }
55
+ .map! do |handler|
56
+ handler.wait(
57
+ max_wait_timeout: @config.max_wait_timeout,
58
+ wait_timeout: @config.wait_timeout
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -3,5 +3,5 @@
3
3
  # WaterDrop library
4
4
  module WaterDrop
5
5
  # Current WaterDrop version
6
- VERSION = '1.4.0'
6
+ VERSION = '2.0.1'
7
7
  end
data/waterdrop.gemspec CHANGED
@@ -14,16 +14,16 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/karafka/waterdrop'
15
15
  spec.summary = 'Kafka messaging made easy!'
16
16
  spec.description = spec.summary
17
- spec.license = 'MIT'
17
+ spec.license = 'LGPL-3.0'
18
18
 
19
- spec.add_dependency 'delivery_boy', '>= 0.2', '< 2.x'
19
+ spec.add_dependency 'concurrent-ruby', '>= 1.1'
20
20
  spec.add_dependency 'dry-configurable', '~> 0.8'
21
21
  spec.add_dependency 'dry-monitor', '~> 0.3'
22
- spec.add_dependency 'dry-validation', '~> 1.2'
23
- spec.add_dependency 'ruby-kafka', '>= 0.7.8'
22
+ spec.add_dependency 'dry-validation', '~> 1.3'
23
+ spec.add_dependency 'rdkafka', '>= 0.6.0'
24
24
  spec.add_dependency 'zeitwerk', '~> 2.1'
25
25
 
26
- spec.required_ruby_version = '>= 2.5.0'
26
+ spec.required_ruby_version = '>= 2.6.0'
27
27
 
28
28
  if $PROGRAM_NAME.end_with?('gem')
29
29
  spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waterdrop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -34,28 +34,22 @@ cert_chain:
34
34
  2DND//YJUikn1zwbz1kT70XmHd97B4Eytpln7K+M1u2g1pHVEPW4owD/ammXNpUy
35
35
  nt70FcDD4yxJQ+0YNiHd0N8IcVBM1TMIVctMNQ==
36
36
  -----END CERTIFICATE-----
37
- date: 2020-08-25 00:00:00.000000000 Z
37
+ date: 2021-06-06 00:00:00.000000000 Z
38
38
  dependencies:
39
39
  - !ruby/object:Gem::Dependency
40
- name: delivery_boy
40
+ name: concurrent-ruby
41
41
  requirement: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '0.2'
46
- - - "<"
47
- - !ruby/object:Gem::Version
48
- version: 2.x
45
+ version: '1.1'
49
46
  type: :runtime
50
47
  prerelease: false
51
48
  version_requirements: !ruby/object:Gem::Requirement
52
49
  requirements:
53
50
  - - ">="
54
51
  - !ruby/object:Gem::Version
55
- version: '0.2'
56
- - - "<"
57
- - !ruby/object:Gem::Version
58
- version: 2.x
52
+ version: '1.1'
59
53
  - !ruby/object:Gem::Dependency
60
54
  name: dry-configurable
61
55
  requirement: !ruby/object:Gem::Requirement
@@ -90,28 +84,28 @@ dependencies:
90
84
  requirements:
91
85
  - - "~>"
92
86
  - !ruby/object:Gem::Version
93
- version: '1.2'
87
+ version: '1.3'
94
88
  type: :runtime
95
89
  prerelease: false
96
90
  version_requirements: !ruby/object:Gem::Requirement
97
91
  requirements:
98
92
  - - "~>"
99
93
  - !ruby/object:Gem::Version
100
- version: '1.2'
94
+ version: '1.3'
101
95
  - !ruby/object:Gem::Dependency
102
- name: ruby-kafka
96
+ name: rdkafka
103
97
  requirement: !ruby/object:Gem::Requirement
104
98
  requirements:
105
99
  - - ">="
106
100
  - !ruby/object:Gem::Version
107
- version: 0.7.8
101
+ version: 0.6.0
108
102
  type: :runtime
109
103
  prerelease: false
110
104
  version_requirements: !ruby/object:Gem::Requirement
111
105
  requirements:
112
106
  - - ">="
113
107
  - !ruby/object:Gem::Version
114
- version: 0.7.8
108
+ version: 0.6.0
115
109
  - !ruby/object:Gem::Dependency
116
110
  name: zeitwerk
117
111
  requirement: !ruby/object:Gem::Requirement
@@ -134,38 +128,45 @@ extensions: []
134
128
  extra_rdoc_files: []
135
129
  files:
136
130
  - ".coditsu/ci.yml"
131
+ - ".diffend.yml"
137
132
  - ".github/FUNDING.yml"
133
+ - ".github/workflows/ci.yml"
138
134
  - ".gitignore"
139
135
  - ".rspec"
140
136
  - ".ruby-gemset"
141
137
  - ".ruby-version"
142
- - ".travis.yml"
143
138
  - CHANGELOG.md
144
139
  - Gemfile
145
140
  - Gemfile.lock
146
- - MIT-LICENCE
141
+ - LICENSE
147
142
  - README.md
148
143
  - certs/mensfeld.pem
149
144
  - config/errors.yml
145
+ - docker-compose.yml
150
146
  - lib/water_drop.rb
151
- - lib/water_drop/async_producer.rb
152
- - lib/water_drop/base_producer.rb
153
147
  - lib/water_drop/config.rb
154
- - lib/water_drop/config_applier.rb
155
148
  - lib/water_drop/contracts.rb
156
149
  - lib/water_drop/contracts/config.rb
157
- - lib/water_drop/contracts/message_options.rb
150
+ - lib/water_drop/contracts/message.rb
158
151
  - lib/water_drop/errors.rb
152
+ - lib/water_drop/instrumentation.rb
159
153
  - lib/water_drop/instrumentation/monitor.rb
160
154
  - lib/water_drop/instrumentation/stdout_listener.rb
161
- - lib/water_drop/sync_producer.rb
155
+ - lib/water_drop/producer.rb
156
+ - lib/water_drop/producer/async.rb
157
+ - lib/water_drop/producer/buffer.rb
158
+ - lib/water_drop/producer/builder.rb
159
+ - lib/water_drop/producer/dummy_client.rb
160
+ - lib/water_drop/producer/statistics_decorator.rb
161
+ - lib/water_drop/producer/status.rb
162
+ - lib/water_drop/producer/sync.rb
162
163
  - lib/water_drop/version.rb
163
164
  - lib/waterdrop.rb
164
165
  - log/.gitkeep
165
166
  - waterdrop.gemspec
166
167
  homepage: https://github.com/karafka/waterdrop
167
168
  licenses:
168
- - MIT
169
+ - LGPL-3.0
169
170
  metadata: {}
170
171
  post_install_message:
171
172
  rdoc_options: []
@@ -175,14 +176,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
176
  requirements:
176
177
  - - ">="
177
178
  - !ruby/object:Gem::Version
178
- version: 2.5.0
179
+ version: 2.6.0
179
180
  required_rubygems_version: !ruby/object:Gem::Requirement
180
181
  requirements:
181
182
  - - ">="
182
183
  - !ruby/object:Gem::Version
183
184
  version: '0'
184
185
  requirements: []
185
- rubygems_version: 3.1.4
186
+ rubygems_version: 3.2.15
186
187
  signing_key:
187
188
  specification_version: 4
188
189
  summary: Kafka messaging made easy!
metadata.gz.sig CHANGED
Binary file
data/.travis.yml DELETED
@@ -1,35 +0,0 @@
1
- services:
2
- - docker
3
-
4
- dist: trusty
5
- cache: bundler
6
-
7
- git:
8
- depth: false
9
-
10
- test: &test
11
- stage: Test
12
- language: ruby
13
- before_install:
14
- - yes | gem update --system
15
- script: bundle exec rspec
16
-
17
- jobs:
18
- include:
19
- - <<: *test
20
- rvm: 2.7.1
21
- - <<: *test
22
- rvm: 2.6.6
23
- - <<: *test
24
- rvm: 2.5.8
25
-
26
- - stage: coditsu
27
- language: ruby
28
- rvm: 2.7.1
29
- before_install:
30
- - yes | gem update --system
31
- script: \curl -sSL https://api.coditsu.io/run/ci | bash
32
-
33
- stages:
34
- - test
35
- - coditsu
data/MIT-LICENCE DELETED
@@ -1,18 +0,0 @@
1
- Permission is hereby granted, free of charge, to any person obtaining
2
- a copy of this software and associated documentation files (the
3
- "Software"), to deal in the Software without restriction, including
4
- without limitation the rights to use, copy, modify, merge, publish,
5
- distribute, sublicense, and/or sell copies of the Software, and to
6
- permit persons to whom the Software is furnished to do so, subject to
7
- the following conditions:
8
-
9
- The above copyright notice and this permission notice shall be
10
- included in all copies or substantial portions of the Software.
11
-
12
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # WaterDrop library
4
- module WaterDrop
5
- # Async producer for messages
6
- class AsyncProducer < BaseProducer
7
- # Performs message delivery using deliver_async method
8
- # @param message [String] message that we want to send to Kafka
9
- # @param options [Hash] options (including topic) for producer
10
- # @raise [WaterDrop::Errors::InvalidMessageOptions] raised when message options are
11
- # somehow invalid and we cannot perform delivery because of that
12
- def self.call(message, options)
13
- attempts_count ||= 0
14
- attempts_count += 1
15
-
16
- validate!(options)
17
- return unless WaterDrop.config.deliver
18
-
19
- d_method = WaterDrop.config.raise_on_buffer_overflow ? :deliver_async! : :deliver_async
20
-
21
- DeliveryBoy.send(d_method, message, **options)
22
- rescue Kafka::Error => e
23
- graceful_attempt?(attempts_count, message, options, e) ? retry : raise(e)
24
- end
25
- end
26
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WaterDrop
4
- # Base messages producer that contains all the logic that is exactly the same for both
5
- # sync and async producers
6
- class BaseProducer
7
- # Contract for checking the correctness of the provided data that someone wants to
8
- # dispatch to Kafka
9
- SCHEMA = Contracts::MessageOptions.new.freeze
10
-
11
- private_constant :SCHEMA
12
-
13
- class << self
14
- private
15
-
16
- # Runs the message options validations and raises an error if anything is invalid
17
- # @param options [Hash] hash that we want to validate
18
- # @raise [WaterDrop::Errors::InvalidMessageOptions] raised when message options are
19
- # somehow invalid and we cannot perform delivery because of that
20
- def validate!(options)
21
- validation_result = SCHEMA.call(options)
22
- return true if validation_result.success?
23
-
24
- raise Errors::InvalidMessageOptions, validation_result.errors
25
- end
26
-
27
- # Upon failed delivery, we may try to resend a message depending on the attempt number
28
- # or re-raise an error if we're unable to do that after given number of retries
29
- # This method checks that and also instruments errors and retries for the delivery
30
- # @param attempts_count [Integer] number of attempt (starting from 1) for the delivery
31
- # @param message [String] message that we want to send to Kafka
32
- # @param options [Hash] options (including topic) for producer
33
- # @param error [Kafka::Error] error that occurred
34
- # @return [Boolean] true if this is a graceful attempt and we can retry or false it this
35
- # was the final one and we should deal with the fact, that we cannot deliver a given
36
- # message
37
- def graceful_attempt?(attempts_count, message, options, error)
38
- scope = "#{to_s.split('::').last.sub('Producer', '_producer').downcase}.call"
39
- payload = {
40
- caller: self,
41
- message: message,
42
- options: options,
43
- error: error,
44
- attempts_count: attempts_count
45
- }
46
-
47
- if attempts_count > WaterDrop.config.kafka.max_retries
48
- WaterDrop.monitor.instrument("#{scope}.error", payload)
49
- false
50
- else
51
- WaterDrop.monitor.instrument("#{scope}.retry", payload)
52
- true
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WaterDrop
4
- # Engine used to propagate config application to DeliveryBoy with corner case handling
5
- module ConfigApplier
6
- class << self
7
- # @param delivery_boy_config [DeliveryBoy::Config] delivery boy config instance
8
- # @param settings [Hash] hash with WaterDrop settings
9
- def call(delivery_boy_config, settings)
10
- # Recursive lambda for mapping config down to delivery boy
11
- settings.each do |key, value|
12
- call(delivery_boy_config, value) && next if value.is_a?(Hash)
13
-
14
- # If this is a special case that needs manual setup instead of a direct reassignment
15
- if respond_to?(key, true)
16
- send(key, delivery_boy_config, value)
17
- else
18
- # If this setting is our internal one, we should not sync it with the delivery boy
19
- next unless delivery_boy_config.respond_to?(:"#{key}=")
20
-
21
- delivery_boy_config.public_send(:"#{key}=", value)
22
- end
23
- end
24
- end
25
-
26
- private
27
-
28
- # Extra setup for the compression codec as it behaves differently than other settings
29
- # that are ported 1:1 from ruby-kafka
30
- # For some crazy reason, delivery boy requires compression codec as a string, when
31
- # ruby-kafka as a symbol. We follow ruby-kafka internal design, so we had to mimic
32
- # that by assigning a string version that down the road will be symbolized again
33
- # by delivery boy
34
- # @param delivery_boy_config [DeliveryBoy::Config] delivery boy config instance
35
- # @param codec_name [Symbol] codec name as a symbol
36
- def compression_codec(delivery_boy_config, codec_name)
37
- # If there is no compression codec, we don't apply anything
38
- return unless codec_name
39
-
40
- delivery_boy_config.compression_codec = codec_name.to_s
41
- end
42
-
43
- # We use the "seed_brokers" name and DeliveryBoy uses "brokers" so we pass the values
44
- # manually
45
- # @param delivery_boy_config [DeliveryBoy::Config] delivery boy config instance
46
- # @param seed_brokers [Array<String>] kafka seed brokers
47
- def seed_brokers(delivery_boy_config, seed_brokers)
48
- delivery_boy_config.brokers = seed_brokers
49
- end
50
- end
51
- end
52
- end