waterdrop 0.4.0 → 1.0.0.alpha1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d6ebd8fb5186e625ad83a4039b1953e145e6ed6e
4
- data.tar.gz: 3f5b6e5d6ac383388a4fff4d18f6600122e91bbc
3
+ metadata.gz: 93db4ab2979f93cc621a72ee8792a6e7f7f6c1f0
4
+ data.tar.gz: acbeb2ccbb8eee1e53b47e1b9cb4da1d484e6b19
5
5
  SHA512:
6
- metadata.gz: 2ffc2c7b34e2e9f53b61db43de61fe5f557af524ffba98f1101a1a630d5af021d90d76e0d65a0c06a2d13114933240d9f984d0b359251ac58af84e2afff04320
7
- data.tar.gz: d1617c0169e356b16332d9b70285e58f0b524185ca2cc413903d53316f379bf1a58a1d383c990883cb2eb827194a6c9d2959d3459a3f04f99548c3c2d5394f47
6
+ metadata.gz: b0abf19f2a2fd07db54d472c7a8f16c32205688377d3ae6038d5dc7d19ece08d3bbdde42e6031647dd914b3e4ccee612929992a4ee991cc3f020c93702e8e365
7
+ data.tar.gz: d177d752b481ba9fde09cd63edc6793e735cdf5516e28071bfa553269acb6fd3b2acace3cea22a870f5885eccb9f08006745246f39a49a065e6e92b1ff33c1b1
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.0
1
+ 2.4.2
data/.travis.yml CHANGED
@@ -1,9 +1,17 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
4
  - 2.3.0
4
5
  - 2.3.1
5
6
  - 2.3.2
6
7
  - 2.3.3
8
+ - 2.3.4
7
9
  - 2.4.0
8
- script:
9
- - bundle exec rake
10
+ - 2.4.1
11
+ - 2.4.2
12
+ - jruby-head
13
+ script: bundle exec rspec spec/
14
+ env:
15
+ global:
16
+ - JRUBY_OPTS='--debug'
17
+ install: bundle install --jobs=3 --retry=3
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # WaterDrop changelog
2
2
 
3
- ## 0.4 unreleased
3
+ ## 1.0.0 unreleased
4
+
5
+ - #37 - ack level for producer
6
+ - Gem bump
7
+ - Ruby 2.4.2 support
8
+ - Raw ruby-kafka driver is now replaced with delivery_boy
9
+ - Sync and async producers
10
+ - Complete update of the API
11
+ - Much better validations for config details
12
+ - Complete API remodel - please read the new README
13
+ - Renamed send_messages to deliver
14
+
15
+ ## 0.4
4
16
  - Bump to match Karafka
5
17
  - Renamed ```hosts``` to ```seed_brokers```
6
18
  - Removed the ```ssl``` scoping for ```kafka``` config namespace to better match Karafka conventions
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
+
4
5
  gemspec
6
+
7
+ group :development, :test do
8
+ gem 'rspec'
9
+ gem 'simplecov'
10
+ end
data/Gemfile.lock CHANGED
@@ -1,57 +1,79 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- waterdrop (0.4.0)
5
- bundler
6
- connection_pool
7
- dry-configurable (~> 0.6)
4
+ waterdrop (1.0.0.alpha1)
5
+ delivery_boy (>= 0.2.2)
6
+ dry-configurable (~> 0.7)
7
+ dry-validation (~> 0.11)
8
8
  null-logger
9
- rake
10
- ruby-kafka (~> 0.4)
11
9
 
12
10
  GEM
13
11
  remote: https://rubygems.org/
14
12
  specs:
15
13
  concurrent-ruby (1.0.5)
16
- connection_pool (2.2.1)
14
+ delivery_boy (0.2.2)
15
+ king_konf (~> 0.1.8)
16
+ ruby-kafka (~> 0.4)
17
17
  diff-lcs (1.3)
18
18
  docile (1.1.5)
19
19
  dry-configurable (0.7.0)
20
20
  concurrent-ruby (~> 1.0)
21
- ffi (1.9.18)
22
- gssapi (1.2.0)
23
- ffi (>= 1.0.1)
21
+ dry-container (0.6.0)
22
+ concurrent-ruby (~> 1.0)
23
+ dry-configurable (~> 0.1, >= 0.1.3)
24
+ dry-core (0.3.4)
25
+ concurrent-ruby (~> 1.0)
26
+ dry-equalizer (0.2.0)
27
+ dry-logic (0.4.2)
28
+ dry-container (~> 0.2, >= 0.2.6)
29
+ dry-core (~> 0.2)
30
+ dry-equalizer (~> 0.2)
31
+ dry-types (0.12.1)
32
+ concurrent-ruby (~> 1.0)
33
+ dry-configurable (~> 0.1)
34
+ dry-container (~> 0.3)
35
+ dry-core (~> 0.2, >= 0.2.1)
36
+ dry-equalizer (~> 0.2)
37
+ dry-logic (~> 0.4, >= 0.4.2)
38
+ inflecto (~> 0.0.0, >= 0.0.2)
39
+ dry-validation (0.11.1)
40
+ concurrent-ruby (~> 1.0)
41
+ dry-configurable (~> 0.1, >= 0.1.3)
42
+ dry-core (~> 0.2, >= 0.2.1)
43
+ dry-equalizer (~> 0.2)
44
+ dry-logic (~> 0.4, >= 0.4.0)
45
+ dry-types (~> 0.12.0)
46
+ inflecto (0.0.2)
24
47
  json (2.1.0)
48
+ king_konf (0.1.8)
25
49
  null-logger (0.1.4)
26
- rake (12.0.0)
27
- rspec (3.6.0)
28
- rspec-core (~> 3.6.0)
29
- rspec-expectations (~> 3.6.0)
30
- rspec-mocks (~> 3.6.0)
31
- rspec-core (3.6.0)
32
- rspec-support (~> 3.6.0)
33
- rspec-expectations (3.6.0)
50
+ rspec (3.7.0)
51
+ rspec-core (~> 3.7.0)
52
+ rspec-expectations (~> 3.7.0)
53
+ rspec-mocks (~> 3.7.0)
54
+ rspec-core (3.7.0)
55
+ rspec-support (~> 3.7.0)
56
+ rspec-expectations (3.7.0)
34
57
  diff-lcs (>= 1.2.0, < 2.0)
35
- rspec-support (~> 3.6.0)
36
- rspec-mocks (3.6.0)
58
+ rspec-support (~> 3.7.0)
59
+ rspec-mocks (3.7.0)
37
60
  diff-lcs (>= 1.2.0, < 2.0)
38
- rspec-support (~> 3.6.0)
39
- rspec-support (3.6.0)
40
- ruby-kafka (0.4.0)
41
- gssapi (>= 1.2.0)
42
- simplecov (0.14.1)
61
+ rspec-support (~> 3.7.0)
62
+ rspec-support (3.7.0)
63
+ ruby-kafka (0.5.0)
64
+ simplecov (0.15.1)
43
65
  docile (~> 1.1.0)
44
66
  json (>= 1.8, < 3)
45
67
  simplecov-html (~> 0.10.0)
46
- simplecov-html (0.10.1)
68
+ simplecov-html (0.10.2)
47
69
 
48
70
  PLATFORMS
49
71
  ruby
50
72
 
51
73
  DEPENDENCIES
52
- rspec (~> 3.6.0)
53
- simplecov (~> 0.14.1)
74
+ rspec
75
+ simplecov
54
76
  waterdrop!
55
77
 
56
78
  BUNDLED WITH
57
- 1.13.7
79
+ 1.15.4
data/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # WaterDrop
2
2
 
3
3
  [![Build Status](https://travis-ci.org/karafka/waterdrop.png)](https://travis-ci.org/karafka/waterdrop)
4
- [![Code Climate](https://codeclimate.com/github/karafka/waterdrop/badges/gpa.svg)](https://codeclimate.com/github/karafka/waterdrop)
5
- [![Gem Version](https://badge.fury.io/rb/waterdrop.svg)](http://badge.fury.io/rb/waterdrop)
6
4
  [![Join the chat at https://gitter.im/karafka/karafka](https://badges.gitter.im/karafka/karafka.svg)](https://gitter.im/karafka/karafka?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
5
 
8
- Gem used to send messages to Kafka in an easy way.
6
+ Gem used to send messages to Kafka in an easy way with an extra validation layer. It is a part of the [Karafka](https://github.com/karafka/karafka) ecosystem.
7
+
8
+ WaterDrop is based on Zendesks [delivery_boy](https://github.com/zendesk/delivery_boy) gem.
9
+
10
+ It is:
11
+
12
+ - Thread safe
13
+ - Supports sync and async producers
9
14
 
10
15
  ## Installation
11
16
 
@@ -27,97 +32,103 @@ bundle install
27
32
 
28
33
  ## Setup
29
34
 
30
- WaterDrop has following configuration options:
31
-
32
- | Option | Required | Value type | Description |
33
- |-----------------------------|------------|---------------|------------------------------------------------------------------------------------|
34
- | send_messages | true | Boolean | Should we send messages to Kafka |
35
- | connect_timeout | false | Integer | Number of seconds to wait while connecting to a broker for the first time |
36
- | socket_timeout | false | Integer | Number of seconds to wait when reading from or writing to a socket |
37
- | connection_pool.size | true | Integer | Kafka connection pool size |
38
- | connection_pool.timeout | true | Integer | Kafka connection pool timeout |
39
- | kafka.seed_brokers | true | Array<String> | Kafka servers hosts with ports |
40
- | raise_on_failure | true | Boolean | Should we raise an exception when we cannot send message to Kafka - if false will silently ignore failures |
41
- | kafka.ssl_ca_cert | false | String | SSL CA certificate |
42
- | kafka.ssl_ca_cert_file_path | false | String | SSL CA certificate file path |
43
- | kafka.ssl_client_cert | false | String | SSL client certificate |
44
- | kafka.ssl_client_cert_key | false | String | SSL client certificate password |
45
- | kafka.sasl_gssapi_principal | false | String | SASL principal |
46
- | kafka.sasl_gssapi_keytab | false | String | SASL keytab |
47
- | kafka.sasl_plain_authzid | false | String | The authorization identity to use |
48
- | kafka.sasl_plain_username | false | String | The username used to authenticate |
49
- | kafka.sasl_plain_password | false | String | The password used to authenticate |
50
-
51
- To apply this configuration, you need to use a *setup* method:
35
+ WaterDrop is a complex tool, that contains multiple configuration options. To keep everything organized, all the configuration options were divided into two groups:
36
+
37
+ - WaterDrop options - options directly related to Karafka framework and it's components
38
+ - Ruby-Kafka driver options - options related to Ruby-Kafka/Delivery boy
39
+
40
+ To apply all those configuration options, you need to use the ```#setup``` method:
52
41
 
53
42
  ```ruby
54
43
  WaterDrop.setup do |config|
55
- config.send_messages = true
56
- config.connection_pool.size = 20
57
- config.connection_pool.timeout = 1
58
- config.kafka.seed_brokers = ['localhost:9092']
59
- config.raise_on_failure = true
44
+ config.deliver = true
45
+ config.kafka.seed_brokers = %w[kafka://localhost:9092]
60
46
  end
61
47
  ```
62
48
 
63
- This configuration can be placed in *config/initializers* and can vary based on the environment:
49
+ ### WaterDrop configuration options
50
+
51
+ | Option | Description |
52
+ |-----------------------------|------------------------------------------------------------------|
53
+ | client_id | This is how the client will identify itself to the Kafka brokers |
54
+ | logger | Logger that we want to use |
55
+ | deliver | Should we send messages to Kafka |
56
+
57
+ ### Ruby-Kafka driver and Delivery boy configuration options
58
+
59
+ **Note:** We've listed here only **the most important** configuration options. If you're interested in all the options, please go to the [config.rb](https://github.com/karafka/waterdrop/blob/master/lib/water_drop/config.rb) file for more details.
60
+
61
+ **Note:** All the options are subject to validations. In order to check what is and what is not acceptable, please go to the [config.rb validation schema](https://github.com/karafka/waterdrop/blob/master/lib/water_drop/schemas/config.rb) file.
62
+
63
+ | Option | Description |
64
+ |---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
65
+ | delivery_interval | The number of seconds between background message deliveries. Disable timer-based background deliveries by setting this to 0. |
66
+ | delivery_threshold | The number of buffered messages that will trigger a background message delivery. Disable buffer size based background deliveries by setting this to 0.|
67
+ | required_acks | The number of Kafka replicas that must acknowledge messages before they're considered as successfully written. |
68
+ | ack_timeout | A timeout executed by a broker when the client is sending messages to it. |
69
+ | max_retries | The number of retries when attempting to deliver messages. |
70
+ | retry_backoff | The number of seconds to wait after a failed attempt to send messages to a Kafka broker before retrying. |
71
+ | max_buffer_bytesize | The maximum number of bytes allowed in the buffer before new messages are rejected. |
72
+ | max_buffer_size | The maximum number of messages allowed in the buffer before new messages are rejected. |
73
+ | max_queue_size | The maximum number of messages allowed in the queue before new messages are rejected. |
74
+ | sasl_plain_username | The username used to authenticate. |
75
+ | sasl_plain_password | The password used to authenticate. |
76
+
77
+ This configuration can be also placed in *config/initializers* and can vary based on the environment:
64
78
 
65
79
  ```ruby
66
80
  WaterDrop.setup do |config|
67
- config.send_messages = Rails.env.production?
68
- config.connection_pool.size = 20
69
- config.connection_pool.timeout = 1
70
- config.kafka.seed_brokers = [Rails.env.production? ? 'prod-host:9091' : 'localhost:9092']
71
- config.raise_on_failure = Rails.env.production?
81
+ config.deliver = Rails.env.production?
82
+ config.kafka.seed_brokers = [Rails.env.production? ? 'kafka://prod-host:9091' : 'kafka://localhost:9092']
72
83
  end
73
84
  ```
74
85
 
75
86
  ## Usage
76
87
 
77
- ### Creating and sending standard messages
78
-
79
- To send Kafka messages, just create and send messages directly:
88
+ To send Kafka messages, just use one of the producers:
80
89
 
81
90
  ```ruby
82
- message = WaterDrop::Message.new('topic', 'message')
83
- message.send!
91
+ WaterDrop::SyncProducer.call('message', topic: 'my-topic')
92
+
93
+ # or for async
84
94
 
85
- message = WaterDrop::Message.new('topic', { user_id: 1 }.to_json)
86
- message.send!
95
+ WaterDrop::AsyncProducer.call('message', topic: 'my-topic')
87
96
  ```
88
97
 
89
- message that you want to send should be either binary or stringified (to_s, to_json, etc).
98
+ Both ```SyncProducer``` and ```AsyncProducer``` accept following options:
90
99
 
91
- ### Using aspects to handle messages
100
+ | Option | Required | Value type | Description |
101
+ |-------------------- |----------|----------------|---------------------------------------------------------------------|
102
+ | ```topic``` | true | String, Symbol | The Kafka topic that should be written to |
103
+ | ```key``` | false | String | The key that should be set on the Kafka message |
104
+ | ```partition``` | false | Integer | A specific partition number that should be written to |
105
+ | ```partition_key``` | false | String | A string that can be used to deterministically select the partition |
92
106
 
93
- WaterDrop no longer depends on [Aspector](https://github.com/gcao/aspector). Please refer to [Aspector](https://github.com/gcao/aspector) documentation if you want to handle messaging in an aspect way.
107
+ Keep in mind, that message you want to send should be either binary or stringified (to_s, to_json, etc).
94
108
 
95
109
  ## References
96
110
 
97
111
  * [Karafka framework](https://github.com/karafka/karafka)
98
- * [Waterdrop](https://github.com/karafka/waterdrop)
99
- * [Worker Glass](https://github.com/karafka/worker-glass)
100
- * [Envlogic](https://github.com/karafka/envlogic)
101
- * [Null Logger](https://github.com/karafka/null-logger)
102
- * [Aspector](https://github.com/gcao/aspector)
103
112
  * [WaterDrop Travis CI](https://travis-ci.org/karafka/waterdrop)
104
- * [WaterDrop Code Climate](https://codeclimate.com/github/karafka/waterdrop)
113
+ * [WaterDrop Coditsu](https://app.coditsu.io/karafka/repositories/waterdrop)
105
114
 
106
115
  ## Note on Patches/Pull Requests
107
116
 
108
117
  Fork the project.
109
118
  Make your feature addition or bug fix.
110
- Add tests for it. This is important so I don't break it in a future version unintentionally.
111
- Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull). Send me a pull request. Bonus points for topic branches.
119
+ Add tests for it. This is important so we don't break it in a future versions unintentionally.
120
+ Commit, do not mess with version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull). Send me a pull request. Bonus points for topic branches.
121
+
122
+ [![coditsu](https://coditsu.io/assets/quality_bar.svg)](https://app.coditsu.io/karafka/repositories/waterdrop)
112
123
 
113
- [![coditsu](https://coditsu.io/assets/quality_bar.svg)](https://coditsu.io)
124
+ Each pull request must pass our quality requirements. To check if everything is as it should be, we use [Coditsu](https://coditsu.io) that combines multiple linters and code analyzers for both code and documentation.
114
125
 
115
- Each pull request must pass our quality requirements. To check if everything is as it should be, we use [Coditsu](https://coditsu.io) that combinse multiple linters and code analyzers. Unfortunately, for now it is invite-only based, so just ping us and we will give you access to the quality results.
126
+ Unfortunately, it does not yet support independent forks, however you should be fine by looking at what we require.
116
127
 
117
128
  Please run:
118
129
 
119
130
  ```bash
120
- bundle exec rake
131
+ bundle exec rspec
121
132
  ```
122
133
 
123
134
  to check if everything is in order. After that you can submit a pull request.
data/config/errors.yml ADDED
@@ -0,0 +1,6 @@
1
+ en:
2
+ errors:
3
+ broker_schema?: >
4
+ has an invalid format.
5
+ Expected schema, host and port number.
6
+ Example: kafka://127.0.0.1:9092 or kafka+ssl://127.0.0.1:9092
data/lib/water_drop.rb CHANGED
@@ -2,49 +2,62 @@
2
2
 
3
3
  # External components
4
4
  %w[
5
- rake
6
- rubygems
7
- bundler
8
- logger
9
- pathname
10
5
  json
11
- kafka
12
- forwardable
13
- connection_pool
6
+ delivery_boy
14
7
  null_logger
15
8
  dry-configurable
9
+ dry-validation
16
10
  ].each { |lib| require lib }
17
11
 
18
12
  # Internal components
19
13
  base_path = File.dirname(__FILE__) + '/water_drop'
20
14
 
21
- %w[
22
- version
23
- producer_proxy
24
- pool
25
- config
26
- message
27
- ].each { |lib| require "#{base_path}/#{lib}" }
28
-
29
15
  # WaterDrop library
30
16
  module WaterDrop
31
17
  class << self
32
- attr_writer :logger
33
-
34
- # @return [Logger] logger that we want to use
35
- def logger
36
- @logger ||= NullLogger.new
37
- end
18
+ attr_accessor :logger
38
19
 
39
20
  # Sets up the whole configuration
40
21
  # @param [Block] block configuration block
41
22
  def setup(&block)
42
23
  Config.setup(&block)
24
+
25
+ DeliveryBoy.logger = self.logger = config.logger
26
+
27
+ # Recursive lambda for mapping config down to delivery boy
28
+ applier = lambda { |db_config, h|
29
+ h.each do |k, v|
30
+ applier.call(db_config, v) && next if v.is_a?(Hash)
31
+ next unless db_config.respond_to?(:"#{k}=")
32
+ db_config.public_send(:"#{k}=", v)
33
+ end
34
+ }
35
+
36
+ DeliveryBoy.config.tap do |config|
37
+ config.brokers = Config.config.kafka.seed_brokers
38
+ applier.call(config, Config.config.to_h)
39
+ end
43
40
  end
44
41
 
45
42
  # @return [WaterDrop::Config] config instance
46
43
  def config
47
44
  Config.config
48
45
  end
46
+
47
+ # @return [String] root path of this gem
48
+ def gem_root
49
+ Pathname.new(File.expand_path('../..', __FILE__))
50
+ end
49
51
  end
50
52
  end
53
+
54
+ %w[
55
+ version
56
+ schemas/message_options
57
+ schemas/config
58
+ config
59
+ errors
60
+ base_producer
61
+ sync_producer
62
+ async_producer
63
+ ].each { |lib| require "#{base_path}/#{lib}" }
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WaterDrop library
4
+ module WaterDrop
5
+ # Async producer for messages
6
+ AsyncProducer = Class.new(BaseProducer)
7
+ AsyncProducer.method_name = :deliver_async
8
+ end
@@ -0,0 +1,35 @@
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
+ class << self
8
+ # Delivery boy method name that we use to invoke producer action
9
+ attr_accessor :method_name
10
+
11
+ # Performs message delivery using method_name method
12
+ # @param message [String] message that we want to send to Kafka
13
+ # @param options [Hash] options (including topic) for producer
14
+ # @raise [WaterDrop::Errors::InvalidMessageOptions] raised when message options are
15
+ # somehow invalid and we cannot perform delivery because of that
16
+ def call(message, options)
17
+ validate!(options)
18
+ return unless WaterDrop.config.deliver
19
+ DeliveryBoy.public_send(method_name, message, options)
20
+ end
21
+
22
+ private
23
+
24
+ # Runs the message options validations and raises an error if anything is invalid
25
+ # @param options [Hash] hash that we want to validate
26
+ # @raise [WaterDrop::Errors::InvalidMessageOptions] raised when message options are
27
+ # somehow invalid and we cannot perform delivery because of that
28
+ def validate!(options)
29
+ validation_result = Schemas::MessageOptions.call(options)
30
+ return true if validation_result.success?
31
+ raise Errors::InvalidMessageOptions, validation_result.errors
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,35 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Configuration and descriptions are based on the delivery boy zendesk gem
4
+ # @see https://github.com/zendesk/delivery_boy
3
5
  module WaterDrop
4
6
  # Configurator for setting up all options required by WaterDrop
5
7
  class Config
6
8
  extend Dry::Configurable
7
9
 
10
+ # WaterDrop options
8
11
  # option client_id [String] identifier of this producer
9
12
  setting :client_id, 'waterdrop'
10
- # option logger [Instance, nil] logger that we want to use or nil to
11
- # fallback to ruby-kafka logger
12
- setting :logger, nil
13
- # Available options
14
- setting :send_messages
15
- # @option raise_on_failure [Boolean] Should raise error when failed to deliver a message
16
- setting :raise_on_failure
13
+ # option [Instance, nil] logger that we want to use or nil to fallback to ruby-kafka logger
14
+ setting :logger, NullLogger.new
15
+ # option [Boolean] should we send messages. Setting this to false can be really useful when
16
+ # testing and or developing because when set to false, won't actually ping Kafka
17
+ setting :deliver, true
17
18
 
18
- # Connection pool options
19
- setting :connection_pool do
20
- # Connection pool size for producers. Note that we take a bigger number because there
21
- # are cases when we might have more sidekiq threads than Karafka consumers (small app)
22
- # or the opposite for bigger systems
23
- setting :size, 2
24
- # How long should we wait for a working resource from the pool before rising timeout
25
- # With a proper connection pool size, this should never happen
26
- setting :timeout, 5
27
- end
28
-
29
- # option kafka [Hash] - optional - kafka configuration options (hosts)
19
+ # Settings directly related to the Kafka driver
30
20
  setting :kafka do
31
- # @option seed_brokers [Array<String>] Array that contains Kafka seed broker hosts with ports
21
+ # option [Array<String>] Array that contains Kafka seed broker hosts with ports
32
22
  setting :seed_brokers
23
+
24
+ # Network timeouts
33
25
  # option connect_timeout [Integer] Sets the number of seconds to wait while connecting to
34
26
  # a broker for the first time. When ruby-kafka initializes, it needs to connect to at
35
27
  # least one host.
@@ -38,15 +30,56 @@ module WaterDrop
38
30
  # writing to a socket connection to a broker. After this timeout expires the connection
39
31
  # will be killed. Note that some Kafka operations are by definition long-running, such as
40
32
  # waiting for new messages to arrive in a partition, so don't set this value too low
41
- setting :socket_timeout, 10
33
+ setting :socket_timeout, 30
34
+
35
+ # Buffering for async producer
36
+ # @option [Integer] The maximum number of bytes allowed in the buffer before new messages
37
+ # are rejected.
38
+ setting :max_buffer_bytesize, 10_000_000
39
+ # @option [Integer] The maximum number of messages allowed in the buffer before new messages
40
+ # are rejected.
41
+ setting :max_buffer_size, 1000
42
+ # @option [Integer] The maximum number of messages allowed in the queue before new messages
43
+ # are rejected. The queue is used to ferry messages from the foreground threads of your
44
+ # application to the background thread that buffers and delivers messages.
45
+ setting :max_queue_size, 1000
46
+
47
+ # option [Integer] A timeout executed by a broker when the client is sending messages to it.
48
+ # It defines the number of seconds the broker should wait for replicas to acknowledge the
49
+ # write before responding to the client with an error. As such, it relates to the
50
+ # required_acks setting. It should be set lower than socket_timeout.
51
+ setting :ack_timeout, 5
52
+ # option [Integer] The number of seconds between background message
53
+ # deliveries. Default is 10 seconds. Disable timer-based background deliveries by
54
+ # setting this to 0.
55
+ setting :delivery_interval, 10
56
+ # option [Integer] The number of buffered messages that will trigger a background message
57
+ # delivery. Default is 100 messages. Disable buffer size based background deliveries by
58
+ # setting this to 0.
59
+ setting :delivery_threshold, 100
60
+
61
+ # option [Integer] The number of retries when attempting to deliver messages.
62
+ setting :max_retries, 2
63
+ # option [Integer]
64
+ setting :required_acks, -1
65
+ # option [Integer]
66
+ setting :retry_backoff, 1
67
+
68
+ # option [Integer] The minimum number of messages that must be buffered before compression is
69
+ # attempted. By default only one message is required. Only relevant if compression_codec
70
+ # is set.
71
+ setting :compression_threshold, 1
72
+ # option [Symbol] The codec used to compress messages. Must be either snappy or gzip.
73
+ setting :compression_codec, nil
74
+
42
75
  # SSL authentication related settings
43
76
  # option ca_cert [String] SSL CA certificate
44
77
  setting :ssl_ca_cert, nil
45
78
  # option ssl_ca_cert_file_path [String] SSL CA certificate file path
46
79
  setting :ssl_ca_cert_file_path, nil
47
- # option client_cert [String] SSL client certificate
80
+ # option ssl_client_cert [String] SSL client certificate
48
81
  setting :ssl_client_cert, nil
49
- # option client_cert_key [String] SSL client certificate password
82
+ # option ssl_client_cert_key [String] SSL client certificate password
50
83
  setting :ssl_client_cert_key, nil
51
84
  # option sasl_gssapi_principal [String] sasl principal
52
85
  setting :sasl_gssapi_principal, nil
@@ -60,6 +93,17 @@ module WaterDrop
60
93
  setting :sasl_plain_password, nil
61
94
  end
62
95
 
96
+ # option producer [Hash] - optional - producer configuration options
97
+ setting :producer do
98
+ # option compression_codec [Symbol] Sets producer compression codec
99
+ # More: https://github.com/zendesk/ruby-kafka#compression
100
+ # More: https://github.com/zendesk/ruby-kafka/blob/master/lib/kafka/compression.rb
101
+ setting :compression_codec, nil
102
+ # option compression_codec [Integer] Sets producer compression threshold
103
+ # More: https://github.com/zendesk/ruby-kafka#compression
104
+ setting :compression_threshold, 1
105
+ end
106
+
63
107
  class << self
64
108
  # Configurating method
65
109
  # @yield Runs a block of code providing a config singleton instance to it
@@ -67,8 +111,21 @@ module WaterDrop
67
111
  def setup
68
112
  configure do |config|
69
113
  yield(config)
114
+ validate!(config.to_h)
70
115
  end
71
116
  end
117
+
118
+ private
119
+
120
+ # Validates the configuration and if anything is wrong, will raise an exception
121
+ # @param config_hash [Hash] config hash with setup details
122
+ # @raise [WaterDrop::Errors::InvalidConfiguration] raised when something is wrong with
123
+ # the configuration
124
+ def validate!(config_hash)
125
+ validation_result = Schemas::Config.call(config_hash)
126
+ return true if validation_result.success?
127
+ raise Errors::InvalidConfiguration, validation_result.errors
128
+ end
72
129
  end
73
130
  end
74
131
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ # Namespace used to encapsulate all the internal errors of WaterDrop
5
+ module Errors
6
+ # Base class for all the WaterDrop internal errors
7
+ BaseError = Class.new(StandardError)
8
+
9
+ # Raised when configuration doesn't match with validation schema
10
+ InvalidConfiguration = Class.new(BaseError)
11
+
12
+ # Raised when we try to send message with invalid optionss
13
+ InvalidMessageOptions = Class.new(BaseError)
14
+ end
15
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ # Namespace for all the schemas for config validations
5
+ module Schemas
6
+ # Schema with validation rules for WaterDrop configuration details
7
+ Config = Dry::Validation.Schema do
8
+ # Valid uri schemas of Kafka broker url
9
+ URI_SCHEMES = %w[
10
+ kafka
11
+ kafka+ssl
12
+ ].freeze
13
+
14
+ # All encryption related options keys
15
+ ENCRYPTION_OPTIONS_KEYS = %i[
16
+ ssl_ca_cert
17
+ ssl_ca_cert_file_path
18
+ ssl_client_cert
19
+ ssl_client_cert_key
20
+ sasl_plain_authzid
21
+ sasl_plain_username
22
+ sasl_plain_password
23
+ sasl_gssapi_principal
24
+ sasl_gssapi_keytab
25
+ ].freeze
26
+
27
+ configure do
28
+ config.messages_file = File.join(
29
+ WaterDrop.gem_root, 'config', 'errors.yml'
30
+ )
31
+
32
+ # Uri validator to check if uri is in a Kafka acceptable format
33
+ # @param uri [String] uri we want to validate
34
+ # @return [Boolean] true if it is a valid uri, otherwise false
35
+ def broker_schema?(uri)
36
+ uri = URI.parse(uri)
37
+ URI_SCHEMES.include?(uri.scheme) && uri.port
38
+ rescue URI::InvalidURIError
39
+ false
40
+ end
41
+ end
42
+
43
+ required(:client_id).filled(:str?, format?: Schemas::TOPIC_REGEXP)
44
+ required(:logger).filled
45
+ required(:deliver).filled(:bool?)
46
+
47
+ required(:kafka).schema do
48
+ required(:seed_brokers).filled { each(:broker_schema?) }
49
+ required(:connect_timeout).filled { (int? | float?) & gt?(0) }
50
+ required(:socket_timeout).filled { (int? | float?) & gt?(0) }
51
+ required(:compression_threshold).filled(:int?, gteq?: 1)
52
+ optional(:compression_codec).maybe(included_in?: %i[snappy gzip])
53
+
54
+ required(:max_buffer_bytesize).filled(:int?, gt?: 0)
55
+ required(:max_buffer_size).filled(:int?, gt?: 0)
56
+ required(:max_queue_size).filled(:int?, gt?: 0)
57
+
58
+ required(:ack_timeout).filled(:int?, gt?: 0)
59
+ required(:delivery_interval).filled(:int?, gteq?: 0)
60
+ required(:delivery_threshold).filled(:int?, gteq?: 0)
61
+
62
+ required(:max_retries).filled(:int?, gteq?: 0)
63
+ required(:retry_backoff).filled(:int?, gteq?: 0)
64
+ required(:required_acks).filled(included_in?: [1, 0, -1, :all])
65
+
66
+ ENCRYPTION_OPTIONS_KEYS.each do |encryption_attribute|
67
+ optional(encryption_attribute).maybe(:str?)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ module Schemas
5
+ # Regexp to check that topic has a valid format
6
+ TOPIC_REGEXP = /\A(\w|\-|\.)+\z/
7
+
8
+ # Schema with validation rules for validating that all the message options that
9
+ # we provide to producer ale valid and usable
10
+ # @note Does not validate message itself as it is not our concern
11
+ MessageOptions = Dry::Validation.Schema do
12
+ required(:topic).filled(:str?, format?: TOPIC_REGEXP)
13
+ optional(:key).maybe(:str?, :filled?)
14
+ optional(:partition).filled(:int?, gteq?: 0)
15
+ optional(:partition_key).maybe(:str?, :filled?)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WaterDrop library
4
+ module WaterDrop
5
+ # Sync producer for messages
6
+ SyncProducer = Class.new(BaseProducer)
7
+ # Sync producer for messages
8
+ Producer = SyncProducer
9
+ SyncProducer.method_name = :deliver
10
+ end
@@ -3,5 +3,5 @@
3
3
  # WaterDrop library
4
4
  module WaterDrop
5
5
  # Current WaterDrop version
6
- VERSION = '0.4.0'
6
+ VERSION = '1.0.0.alpha1'
7
7
  end
data/waterdrop.gemspec CHANGED
@@ -9,22 +9,18 @@ Gem::Specification.new do |spec|
9
9
  spec.name = 'waterdrop'
10
10
  spec.version = ::WaterDrop::VERSION
11
11
  spec.platform = Gem::Platform::RUBY
12
- spec.authors = ['Maciej Mensfeld', 'Pavlo Vavruk']
13
- spec.email = %w[maciej@mensfeld.pl pavlo.vavruk@gmail.com]
12
+ spec.authors = ['Maciej Mensfeld']
13
+ spec.email = %w[maciej@mensfeld.pl]
14
14
  spec.homepage = 'https://github.com/karafka/waterdrop'
15
15
  spec.summary = ' Kafka messaging made easy! '
16
16
  spec.description = spec.summary
17
17
  spec.license = 'MIT'
18
18
 
19
- spec.add_dependency 'bundler', '>= 0'
20
- spec.add_dependency 'rake', '>= 0'
21
- spec.add_dependency 'ruby-kafka', '~> 0.4'
22
- spec.add_dependency 'connection_pool', '>= 0'
19
+ spec.add_dependency 'delivery_boy', '>= 0.2.2'
20
+ spec.add_dependency 'dry-configurable', '~> 0.7'
21
+ spec.add_dependency 'dry-validation', '~> 0.11'
23
22
  spec.add_dependency 'null-logger'
24
- spec.add_dependency 'dry-configurable', '~> 0.6'
25
23
 
26
- spec.add_development_dependency 'rspec', '~> 3.6.0'
27
- spec.add_development_dependency 'simplecov', '~> 0.14.1'
28
24
  spec.required_ruby_version = '>= 2.2.0'
29
25
 
30
26
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
metadata CHANGED
@@ -1,72 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waterdrop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 1.0.0.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
8
- - Pavlo Vavruk
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2017-08-11 00:00:00.000000000 Z
11
+ date: 2017-10-30 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: bundler
14
+ name: delivery_boy
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
17
  - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '0'
21
- type: :runtime
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: rake
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
19
+ version: 0.2.2
35
20
  type: :runtime
36
21
  prerelease: false
37
22
  version_requirements: !ruby/object:Gem::Requirement
38
23
  requirements:
39
24
  - - ">="
40
25
  - !ruby/object:Gem::Version
41
- version: '0'
26
+ version: 0.2.2
42
27
  - !ruby/object:Gem::Dependency
43
- name: ruby-kafka
28
+ name: dry-configurable
44
29
  requirement: !ruby/object:Gem::Requirement
45
30
  requirements:
46
31
  - - "~>"
47
32
  - !ruby/object:Gem::Version
48
- version: '0.4'
33
+ version: '0.7'
49
34
  type: :runtime
50
35
  prerelease: false
51
36
  version_requirements: !ruby/object:Gem::Requirement
52
37
  requirements:
53
38
  - - "~>"
54
39
  - !ruby/object:Gem::Version
55
- version: '0.4'
40
+ version: '0.7'
56
41
  - !ruby/object:Gem::Dependency
57
- name: connection_pool
42
+ name: dry-validation
58
43
  requirement: !ruby/object:Gem::Requirement
59
44
  requirements:
60
- - - ">="
45
+ - - "~>"
61
46
  - !ruby/object:Gem::Version
62
- version: '0'
47
+ version: '0.11'
63
48
  type: :runtime
64
49
  prerelease: false
65
50
  version_requirements: !ruby/object:Gem::Requirement
66
51
  requirements:
67
- - - ">="
52
+ - - "~>"
68
53
  - !ruby/object:Gem::Version
69
- version: '0'
54
+ version: '0.11'
70
55
  - !ruby/object:Gem::Dependency
71
56
  name: null-logger
72
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,52 +66,9 @@ dependencies:
81
66
  - - ">="
82
67
  - !ruby/object:Gem::Version
83
68
  version: '0'
84
- - !ruby/object:Gem::Dependency
85
- name: dry-configurable
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - "~>"
89
- - !ruby/object:Gem::Version
90
- version: '0.6'
91
- type: :runtime
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - "~>"
96
- - !ruby/object:Gem::Version
97
- version: '0.6'
98
- - !ruby/object:Gem::Dependency
99
- name: rspec
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - "~>"
103
- - !ruby/object:Gem::Version
104
- version: 3.6.0
105
- type: :development
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - "~>"
110
- - !ruby/object:Gem::Version
111
- version: 3.6.0
112
- - !ruby/object:Gem::Dependency
113
- name: simplecov
114
- requirement: !ruby/object:Gem::Requirement
115
- requirements:
116
- - - "~>"
117
- - !ruby/object:Gem::Version
118
- version: 0.14.1
119
- type: :development
120
- prerelease: false
121
- version_requirements: !ruby/object:Gem::Requirement
122
- requirements:
123
- - - "~>"
124
- - !ruby/object:Gem::Version
125
- version: 0.14.1
126
69
  description: Kafka messaging made easy!
127
70
  email:
128
71
  - maciej@mensfeld.pl
129
- - pavlo.vavruk@gmail.com
130
72
  executables: []
131
73
  extensions: []
132
74
  extra_rdoc_files: []
@@ -141,12 +83,15 @@ files:
141
83
  - Gemfile.lock
142
84
  - MIT-LICENCE
143
85
  - README.md
144
- - Rakefile
86
+ - config/errors.yml
145
87
  - lib/water_drop.rb
88
+ - lib/water_drop/async_producer.rb
89
+ - lib/water_drop/base_producer.rb
146
90
  - lib/water_drop/config.rb
147
- - lib/water_drop/message.rb
148
- - lib/water_drop/pool.rb
149
- - lib/water_drop/producer_proxy.rb
91
+ - lib/water_drop/errors.rb
92
+ - lib/water_drop/schemas/config.rb
93
+ - lib/water_drop/schemas/message_options.rb
94
+ - lib/water_drop/sync_producer.rb
150
95
  - lib/water_drop/version.rb
151
96
  - lib/waterdrop.rb
152
97
  - waterdrop.gemspec
@@ -165,12 +110,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
110
  version: 2.2.0
166
111
  required_rubygems_version: !ruby/object:Gem::Requirement
167
112
  requirements:
168
- - - ">="
113
+ - - ">"
169
114
  - !ruby/object:Gem::Version
170
- version: '0'
115
+ version: 1.3.1
171
116
  requirements: []
172
117
  rubyforge_project:
173
- rubygems_version: 2.6.11
118
+ rubygems_version: 2.6.13
174
119
  signing_key:
175
120
  specification_version: 4
176
121
  summary: Kafka messaging made easy!
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler'
4
- require 'rake'
5
- require 'rspec/core/rake_task'
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
- task default: :spec
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WaterDrop
4
- # Message class which encapsulate single Kafka message logic and its delivery
5
- class Message
6
- attr_reader :topic, :message, :options
7
-
8
- # @param topic [String, Symbol] a topic to which we want to send a message
9
- # @param message [Object] any object that can be serialized to a JSON string or
10
- # that can be casted to a string
11
- # @param options [Hash] (optional) additional options to pass to the Kafka producer
12
- # @return [WaterDrop::Message] WaterDrop message instance
13
- # @example Creating a new message
14
- # WaterDrop::Message.new(topic, message)
15
- def initialize(topic, message, options = {})
16
- @topic = topic.to_s
17
- @message = message
18
- @options = options
19
- end
20
-
21
- # Sents a current message to Kafka
22
- # @note Won't send any messages if send_messages config flag is set to false
23
- # @example Set a message
24
- # WaterDrop::Message.new(topic, message).send!
25
- def send!
26
- return true unless ::WaterDrop.config.send_messages
27
-
28
- Pool.with { |producer| producer.send_message(self) }
29
-
30
- ::WaterDrop.logger.info("Message #{message} was sent to topic '#{topic}'")
31
- rescue StandardError => e
32
- # Even if we dont reraise this exception, it should log that it happened
33
- ::WaterDrop.logger.error(e)
34
- # Reraise if we want to raise on failure
35
- # Ignore if we dont want to know that something went wrong
36
- return unless ::WaterDrop.config.raise_on_failure
37
- raise(e)
38
- end
39
- end
40
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WaterDrop
4
- # Raw Kafka producers connection pool for WaterDrop messages delivery
5
- module Pool
6
- extend SingleForwardable
7
- # Delegate directly to pool
8
- def_delegators :pool, :with
9
-
10
- class << self
11
- # @return [::ConnectionPool] connection pool instance that we can then use
12
- def pool
13
- @pool ||= ConnectionPool.new(
14
- size: ::WaterDrop.config.connection_pool.size,
15
- timeout: ::WaterDrop.config.connection_pool.timeout
16
- ) do
17
- WaterDrop::ProducerProxy.new
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WaterDrop
4
- # Proxy object for a producer (sender) objects that are inside pool
5
- # We use it to provide additional timeout monitoring layer
6
- # There used to be an issue with Poseidon (previous engine for this lib)
7
- # usage of sockets that are old and not used - that's why we just
8
- # reinitialize connection if the connection layer is not being used for too long
9
- # We keep this logic to avoid problems just in case. If those problems won't occur
10
- # with Ruby-Kafka, we will drop it
11
- class ProducerProxy
12
- # How long should be object considered alive if nothing is being
13
- # send using it. After that time, we will recreate the connection
14
- LIFE_TIME = 5 * 60 # 5 minute
15
-
16
- # If sending fails - how many times we should try with a new connection
17
- MAX_SEND_RETRIES = 1
18
-
19
- # @return [WaterDrop::ProducerProxy] proxy object to Kafka::Producer
20
- # @note To ignore @last_usage nil case - we just assume that it is being
21
- # first used when we create it
22
- def initialize
23
- touch
24
- @attempts = 0
25
- end
26
-
27
- # Sends message to Kafka
28
- # @param message [WaterDrop::Message] message that we want to send
29
- # @note If something goes wrong it will assume that producer is corrupted and will try to
30
- # create a new one
31
- # @example Send 1 message
32
- # ProducerProxy.new.send_message(WaterDrop::Message.new(topic, message))
33
- def send_message(message)
34
- touch
35
- producer.produce(
36
- message.message,
37
- { topic: message.topic }.merge(message.options)
38
- )
39
- producer.deliver_messages
40
- rescue StandardError => e
41
- reload!
42
-
43
- retry if (@attempts += 1) <= MAX_SEND_RETRIES
44
-
45
- raise(e)
46
- ensure
47
- @attempts = 0
48
- end
49
-
50
- private
51
-
52
- # Refreshes last usage value with current time
53
- def touch
54
- @last_usage = Time.now
55
- end
56
-
57
- # @return [Kafka::Producer] producer instance to which we can forward method requests
58
- def producer
59
- reload! if dead?
60
- @producer ||= Kafka.new(
61
- {
62
- client_id: ::WaterDrop.config.client_id,
63
- logger: ::WaterDrop.config.logger
64
- }.merge(::WaterDrop.config.kafka.to_h)
65
- ).producer
66
- end
67
-
68
- # @return [Boolean] true if we cannot use producer anymore because it was not used for a
69
- # long time
70
- def dead?
71
- @last_usage + LIFE_TIME < Time.now
72
- end
73
-
74
- # Resets a producer so a new one will be created once requested
75
- def reload!
76
- @producer&.shutdown
77
- @producer = nil
78
- end
79
- end
80
- end