rom-kafka 0.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +9 -0
  4. data/.metrics +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +2 -0
  7. data/.travis.yml +34 -0
  8. data/.yardopts +3 -0
  9. data/CHANGELOG.md +3 -0
  10. data/Gemfile +7 -0
  11. data/Guardfile +14 -0
  12. data/LICENSE +21 -0
  13. data/README.md +83 -0
  14. data/Rakefile +34 -0
  15. data/config/metrics/STYLEGUIDE +230 -0
  16. data/config/metrics/cane.yml +5 -0
  17. data/config/metrics/churn.yml +6 -0
  18. data/config/metrics/flay.yml +2 -0
  19. data/config/metrics/metric_fu.yml +14 -0
  20. data/config/metrics/reek.yml +1 -0
  21. data/config/metrics/roodi.yml +24 -0
  22. data/config/metrics/rubocop.yml +71 -0
  23. data/config/metrics/saikuro.yml +3 -0
  24. data/config/metrics/simplecov.yml +6 -0
  25. data/config/metrics/yardstick.yml +37 -0
  26. data/lib/rom-kafka.rb +3 -0
  27. data/lib/rom/kafka.rb +29 -0
  28. data/lib/rom/kafka/brokers.rb +72 -0
  29. data/lib/rom/kafka/brokers/broker.rb +68 -0
  30. data/lib/rom/kafka/connection.rb +22 -0
  31. data/lib/rom/kafka/connection/consumer.rb +105 -0
  32. data/lib/rom/kafka/connection/producer.rb +114 -0
  33. data/lib/rom/kafka/create.rb +75 -0
  34. data/lib/rom/kafka/dataset.rb +132 -0
  35. data/lib/rom/kafka/gateway.rb +165 -0
  36. data/lib/rom/kafka/relation.rb +78 -0
  37. data/lib/rom/kafka/version.rb +13 -0
  38. data/rom-kafka.gemspec +33 -0
  39. data/spec/integration/basic_usage_spec.rb +58 -0
  40. data/spec/integration/keys_usage_spec.rb +34 -0
  41. data/spec/shared/scholars_topic.rb +28 -0
  42. data/spec/spec_helper.rb +20 -0
  43. data/spec/unit/brokers/broker_spec.rb +89 -0
  44. data/spec/unit/brokers_spec.rb +46 -0
  45. data/spec/unit/connection/consumer_spec.rb +90 -0
  46. data/spec/unit/connection/producer_spec.rb +79 -0
  47. data/spec/unit/create_spec.rb +79 -0
  48. data/spec/unit/dataset_spec.rb +165 -0
  49. data/spec/unit/gateway_spec.rb +171 -0
  50. data/spec/unit/relation_spec.rb +96 -0
  51. metadata +219 -0
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ module ROM::Kafka
4
+
5
+ # The Kafka-specific implementation of ROM::Relation
6
+ #
7
+ # @example
8
+ # ROM.use(:auto_registration)
9
+ # ROM.setup(:kafka, "localhost:9092")
10
+ #
11
+ # class Users < ROM::Relation[:kafka]
12
+ # topic "users"
13
+ # end
14
+ #
15
+ # rom = ROM.finalize.env
16
+ # users = rom.relation(:users)
17
+ # users.where(partition: 1).offset(0).limit(1).to_a
18
+ # # => [
19
+ # # { value: "Andrew", topic: "users", partition: 1, offset: 0 }
20
+ # # ]
21
+ #
22
+ class Relation < ROM::Relation
23
+
24
+ adapter :kafka
25
+
26
+ # Kafka-specific alias for the ROM `.dataset` helper method.
27
+ #
28
+ # @param [#to_sym] name
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ def self.topic(name)
33
+ dataset(name)
34
+ end
35
+
36
+ # Returns new relation with updated `:partition` attribute
37
+ #
38
+ # @param [Integer] value
39
+ #
40
+ # @return [ROM::Kafka::Relation]
41
+ #
42
+ def from(value)
43
+ using(partition: value)
44
+ end
45
+
46
+ # Returns new relation with updated `:offset` attribute
47
+ #
48
+ # @param [Integer] value
49
+ #
50
+ # @return [ROM::Kafka::Relation]
51
+ #
52
+ def offset(value)
53
+ using(offset: value)
54
+ end
55
+
56
+ # Returns new relation with updated `:limit` attribute
57
+ #
58
+ # @param [Integer] value
59
+ #
60
+ # @return [ROM::Kafka::Relation]
61
+ #
62
+ def limit(value)
63
+ using(limit: value)
64
+ end
65
+
66
+ # Returns new relation where dataset is updated with given attributes
67
+ #
68
+ # @param [Hash] attributes
69
+ #
70
+ # @return [ROM::Kafka::Relation]
71
+ #
72
+ def using(attributes)
73
+ self.class.new dataset.using(attributes)
74
+ end
75
+
76
+ end # class Relation
77
+
78
+ end # module ROM::Kafka
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module ROM
4
+
5
+ module Kafka
6
+
7
+ # The semantic version of the module.
8
+ # @see http://semver.org/ Semantic versioning 2.0
9
+ VERSION = "0.0.1".freeze
10
+
11
+ end # module Kafka
12
+
13
+ end # module ROM
data/rom-kafka.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "rom/kafka/version"
5
+
6
+ Gem::Specification.new do |gem|
7
+
8
+ gem.name = "rom-kafka"
9
+ gem.version = ROM::Kafka::VERSION.dup
10
+ gem.author = ["Andrew Kozin"]
11
+ gem.email = ["andrew.kozin@gmail.com"]
12
+ gem.summary = "Kafka support for Ruby Object Mapper"
13
+ gem.description = gem.summary
14
+ gem.homepage = "https://rom-rb.org"
15
+ gem.license = "MIT"
16
+
17
+ gem.files = `git ls-files -z`.split("\x0")
18
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.extra_rdoc_files = Dir["README.md", "LICENSE"]
21
+ gem.require_paths = ["lib"]
22
+
23
+ gem.required_ruby_version = "~> 1.9", ">= 1.9.3"
24
+
25
+ gem.add_runtime_dependency "rom", "~> 0.9", ">= 0.9.1"
26
+ gem.add_runtime_dependency "poseidon", "~> 0.0", ">= 0.0.5"
27
+ gem.add_runtime_dependency "attributes_dsl", "~> 0.0", ">= 0.0.2"
28
+
29
+ gem.add_development_dependency "hexx-rspec", "~> 0.5"
30
+ gem.add_development_dependency "inflecto", "~> 0.0", ">= 0.0.2"
31
+ gem.add_development_dependency "timecop", "~> 0.8"
32
+
33
+ end # Gem::Specification
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ require "shared/scholars_topic"
3
+
4
+ describe "Basic Usage" do
5
+
6
+ include_context :scholars_topic
7
+
8
+ let(:add_scholars) { insert.with(key: 0) }
9
+
10
+ it "works" do
11
+ # Add messages into the 0 partition (see :add_scholars above)
12
+ expect(add_scholars.call("Matthew", "Mark").to_a).to eql [
13
+ { value: "Matthew", topic: "scholars", key: "0" },
14
+ { value: "Mark", topic: "scholars", key: "0" }
15
+ ]
16
+
17
+ # Fetching from the <default> 0 partition gives all messages
18
+ expect(scholars.call.to_a).to eql [
19
+ { value: "Matthew", topic: "scholars", key: "0", offset: 0 },
20
+ { value: "Mark", topic: "scholars", key: "0", offset: 1 }
21
+ ]
22
+
23
+ # Second call returns nothing because all messages were already fetched
24
+ expect(scholars.call.to_a).to eql []
25
+
26
+ # Add a couple of messages
27
+ expect(add_scholars.call("Luke", "John").to_a).to eql [
28
+ { value: "Luke", topic: "scholars", key: "0" },
29
+ { value: "John", topic: "scholars", key: "0" }
30
+ ]
31
+
32
+ # And fetch them (now starting from the next offset from we stay before)
33
+ expect(scholars.call.to_a).to eql [
34
+ { value: "Luke", topic: "scholars", key: "0", offset: 2 },
35
+ { value: "John", topic: "scholars", key: "0", offset: 3 }
36
+ ]
37
+
38
+ # Re-fetch all the messages from 0 offset
39
+ expect(scholars.offset(0).call.to_a).to eql [
40
+ { value: "Matthew", topic: "scholars", key: "0", offset: 0 },
41
+ { value: "Mark", topic: "scholars", key: "0", offset: 1 },
42
+ { value: "Luke", topic: "scholars", key: "0", offset: 2 },
43
+ { value: "John", topic: "scholars", key: "0", offset: 3 }
44
+ ]
45
+
46
+ # Re-fetch only limited subset of messages
47
+ expect(scholars.offset(1).limit(2).call.to_a).to eql [
48
+ { value: "Mark", topic: "scholars", key: "0", offset: 1 },
49
+ { value: "Luke", topic: "scholars", key: "0", offset: 2 }
50
+ ]
51
+
52
+ # But actually this will move the offset not to 2 but to the end
53
+ # (consumer fetched all the messages, but iterated via 2 only)
54
+ # To start from the next offset, we should set it explicitly.
55
+ expect(scholars.call.to_a).to eql []
56
+ end
57
+
58
+ end # describe Basic Usage
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require "shared/scholars_topic"
3
+
4
+ describe "Keys Usage" do
5
+
6
+ include_context :scholars_topic
7
+
8
+ it "works" do
9
+ # Add messages into the partition 1, to be extracted from key "4"
10
+ expect(insert.with(key: 4).call("Thomas", "Judah").to_a).to eql [
11
+ { value: "Thomas", topic: "scholars", key: "4" },
12
+ { value: "Judah", topic: "scholars", key: "4" }
13
+ ]
14
+
15
+ # Add messages into the partition 2, to be extracted from key "5"
16
+ expect(insert.with(key: 5).call("Maria", "Philip").to_a).to eql [
17
+ { value: "Maria", topic: "scholars", key: "5" },
18
+ { value: "Philip", topic: "scholars", key: "5" }
19
+ ]
20
+
21
+ # Look at the data in the partition 1
22
+ expect(scholars.from(1).offset(0).call.to_a).to eql [
23
+ { value: "Thomas", topic: "scholars", key: "4", offset: 0 },
24
+ { value: "Judah", topic: "scholars", key: "4", offset: 1 }
25
+ ]
26
+
27
+ # Look at the data in the partition 2
28
+ expect(scholars.from(2).offset(0).call.to_a).to eql [
29
+ { value: "Maria", topic: "scholars", key: "5", offset: 0 },
30
+ { value: "Philip", topic: "scholars", key: "5", offset: 1 }
31
+ ]
32
+ end
33
+
34
+ end # describe Keys Usage
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples :scholars_topic do
4
+
5
+ let!(:rom) do
6
+ env = ROM::Environment.new
7
+ env.use :auto_registration
8
+
9
+ setup = env.setup(
10
+ :kafka, "localhost:9092",
11
+ client_id: "admin",
12
+ # use the number of partition as a key
13
+ partitioner: -> key, total { key.to_i % total }
14
+ )
15
+
16
+ setup.relation(:scholars)
17
+ setup.commands(:scholars) do
18
+ define(:create)
19
+ end
20
+
21
+ setup.finalize
22
+ setup.env
23
+ end
24
+
25
+ let(:scholars) { rom.relation(:scholars) }
26
+ let(:insert) { rom.command(:scholars).create }
27
+
28
+ end # shared_examples
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require "hexx-suit"
5
+ Hexx::Suit.load_metrics_for(self)
6
+ rescue LoadError
7
+ require "hexx-rspec"
8
+ Hexx::RSpec.load_metrics_for(self)
9
+ end
10
+
11
+ # Loads the code under test
12
+ require "rom-kafka"
13
+
14
+ # @todo Remove after resolving of mutant PR#444
15
+ # @see https://github.com/mbj/mutant/issues/444
16
+ if ENV["MUTANT"]
17
+ RSpec.configure do |config|
18
+ config.around { |example| Timeout.timeout(0.5, &example) }
19
+ end
20
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ describe ROM::Kafka::Brokers::Broker do
4
+
5
+ let(:broker) { described_class.new }
6
+
7
+ describe ".new" do
8
+ subject { broker }
9
+
10
+ it { is_expected.to be_frozen }
11
+ end # describe .new
12
+
13
+ describe "#host" do
14
+ subject { broker.host }
15
+
16
+ context "by default" do
17
+ it { is_expected.to eql "localhost" }
18
+ end
19
+
20
+ context "when full :host is given" do
21
+ let(:broker) { described_class.new host: :"https://some.path.com:9093" }
22
+
23
+ it { is_expected.to eql "https://some.path.com" }
24
+ end
25
+
26
+ context "when localhost is given" do
27
+ let(:broker) { described_class.new host: :"localhost:9093" }
28
+
29
+ it { is_expected.to eql "localhost" }
30
+ end
31
+ end # describe #host
32
+
33
+ describe "#port" do
34
+ subject { broker.port }
35
+
36
+ context "by default" do
37
+ it { is_expected.to eql 9092 }
38
+ end
39
+
40
+ context "when :host contains port" do
41
+ let(:broker) { described_class.new host: :"https://some.path.com:9093" }
42
+
43
+ it { is_expected.to eql 9093 }
44
+ end
45
+
46
+ context "when :port is set" do
47
+ let(:broker) { described_class.new port: 9093 }
48
+
49
+ it { is_expected.to eql 9093 }
50
+ end
51
+
52
+ context "when :host contains port and :port is set" do
53
+ let(:broker) { described_class.new host: "localhost:9092", port: 9093 }
54
+
55
+ it "prefers the host setting" do
56
+ expect(subject).to eql 9092
57
+ end
58
+ end
59
+ end # describe #port
60
+
61
+ describe "#to_s" do
62
+ subject { described_class.new(host: :"127.0.0.1:9093").to_s }
63
+
64
+ it { is_expected.to eql "127.0.0.1:9093" }
65
+ end # describe #to_s
66
+
67
+ describe "#==" do
68
+ subject { broker == other }
69
+
70
+ context "with the same host and port" do
71
+ let(:other) { described_class.new }
72
+
73
+ it { is_expected.to eql true }
74
+ end
75
+
76
+ context "with another host" do
77
+ let(:other) { described_class.new host: "127.0.0.1" }
78
+
79
+ it { is_expected.to eql false }
80
+ end
81
+
82
+ context "with another port" do
83
+ let(:other) { described_class.new port: 9093 }
84
+
85
+ it { is_expected.to eql false }
86
+ end
87
+ end # describe #==
88
+
89
+ end # describe ROM::Kafka::Brokers::Broker
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ describe ROM::Kafka::Brokers do
4
+
5
+ let(:default_brokers) { described_class.new }
6
+ let(:custom_brokers) do
7
+ described_class.new "foo", "bar:9093", hosts: ["baz:9092"], port: 9094
8
+ end
9
+
10
+ describe ".new" do
11
+ subject { default_brokers }
12
+
13
+ it { is_expected.to be_frozen }
14
+ end # describe .new
15
+
16
+ describe "#to_a" do
17
+ context "by default" do
18
+ subject { default_brokers.to_a }
19
+
20
+ it { is_expected.to eql ["localhost:9092"] }
21
+ end
22
+
23
+ context "customized" do
24
+ subject { custom_brokers.to_a }
25
+
26
+ it { is_expected.to eql ["foo:9094", "bar:9093", "baz:9092"] }
27
+ end
28
+ end # describe #to_a
29
+
30
+ describe "#==" do
31
+ subject { default_brokers == other }
32
+
33
+ context "with the same brokers" do
34
+ let(:other) { described_class.new }
35
+
36
+ it { is_expected.to eql true }
37
+ end
38
+
39
+ context "with different brokers" do
40
+ let(:other) { described_class.new "foo" }
41
+
42
+ it { is_expected.to eql false }
43
+ end
44
+ end # describe #==
45
+
46
+ end # describe ROM::Kafka::Brokers
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ describe ROM::Kafka::Connection::Consumer do
4
+
5
+ # ============================================================================
6
+ # We test not the poseidon API, but its proper usage by the Consumer.
7
+ # That's why we stub poseidon classes.
8
+ # ----------------------------------------------------------------------------
9
+ let(:driver) { Poseidon::PartitionConsumer }
10
+ let(:connection) { double :connection }
11
+ before { allow(driver).to receive(:consumer_for_partition) { connection } }
12
+ # ============================================================================
13
+
14
+ let(:consumer) { described_class.new options }
15
+ let(:options) do
16
+ attributes.merge(
17
+ client_id: client,
18
+ brokers: brokers,
19
+ topic: topic,
20
+ partition: partition,
21
+ offset: offset
22
+ )
23
+ end
24
+ let(:attributes) { { min_bytes: 2, max_bytes: 3000, max_wait_ms: 100 } }
25
+ let(:brokers) { ["127.0.0.1:9092", "127.0.0.2:9092"] }
26
+ let(:client) { "foo" }
27
+ let(:topic) { "bar" }
28
+ let(:partition) { 1 }
29
+ let(:offset) { 100 }
30
+ let(:tuple) { { value: "Hi!", topic: "foo", key: "foo", offset: 100 } }
31
+ let(:message) { double :message, tuple }
32
+
33
+ describe ".new" do
34
+ subject { consumer }
35
+
36
+ it { is_expected.to be_kind_of Enumerable }
37
+ end # describe .new
38
+
39
+ describe "#connection" do
40
+ subject { consumer.connection }
41
+
42
+ it "instantiates the driver" do
43
+ expect(driver)
44
+ .to receive(:consumer_for_partition)
45
+ .with(client, brokers, topic, partition, offset, attributes)
46
+
47
+ expect(subject).to eql(connection)
48
+ end
49
+ end # describe #connection
50
+
51
+ describe "#fetch" do
52
+ subject { consumer.fetch }
53
+
54
+ before { allow(connection).to receive(:fetch) { [message] } }
55
+
56
+ it "fetches messages from a connection" do
57
+ expect(connection).to receive(:fetch)
58
+ expect(subject).to eql [tuple]
59
+ end
60
+ end # describe #fetch
61
+
62
+ describe "#each" do
63
+
64
+ let(:messages) { [message, message] } # stub messages to extract from broker
65
+ before do
66
+ allow(connection)
67
+ .to receive(:fetch) do
68
+ data = [messages.pop].compact
69
+ messages.freeze unless data.any? # the next `pop` should fail
70
+ data
71
+ end
72
+ end
73
+
74
+ context "without a block" do
75
+ subject { consumer.each }
76
+
77
+ it { is_expected.to be_kind_of Enumerator }
78
+ end
79
+
80
+ context "with a block" do
81
+ subject { consumer.to_a }
82
+
83
+ it "fetches messages while received any" do
84
+ expect(connection).to receive(:fetch).exactly(3).times
85
+ expect(subject).to eq [tuple, tuple]
86
+ end
87
+ end
88
+ end # describe #each
89
+
90
+ end # describe ROM::Kafka::Connection::Consumer