waterdrop 0.4.0 → 1.0.0.alpha1

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