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.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +9 -0
- data/.metrics +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +34 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +7 -0
- data/Guardfile +14 -0
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/Rakefile +34 -0
- data/config/metrics/STYLEGUIDE +230 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +14 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +71 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/rom-kafka.rb +3 -0
- data/lib/rom/kafka.rb +29 -0
- data/lib/rom/kafka/brokers.rb +72 -0
- data/lib/rom/kafka/brokers/broker.rb +68 -0
- data/lib/rom/kafka/connection.rb +22 -0
- data/lib/rom/kafka/connection/consumer.rb +105 -0
- data/lib/rom/kafka/connection/producer.rb +114 -0
- data/lib/rom/kafka/create.rb +75 -0
- data/lib/rom/kafka/dataset.rb +132 -0
- data/lib/rom/kafka/gateway.rb +165 -0
- data/lib/rom/kafka/relation.rb +78 -0
- data/lib/rom/kafka/version.rb +13 -0
- data/rom-kafka.gemspec +33 -0
- data/spec/integration/basic_usage_spec.rb +58 -0
- data/spec/integration/keys_usage_spec.rb +34 -0
- data/spec/shared/scholars_topic.rb +28 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/brokers/broker_spec.rb +89 -0
- data/spec/unit/brokers_spec.rb +46 -0
- data/spec/unit/connection/consumer_spec.rb +90 -0
- data/spec/unit/connection/producer_spec.rb +79 -0
- data/spec/unit/create_spec.rb +79 -0
- data/spec/unit/dataset_spec.rb +165 -0
- data/spec/unit/gateway_spec.rb +171 -0
- data/spec/unit/relation_spec.rb +96 -0
- metadata +219 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe ROM::Kafka::Connection::Producer do
|
4
|
+
|
5
|
+
# ============================================================================
|
6
|
+
# We test not the poseidon API, but its proper usage by the Producer.
|
7
|
+
# That's why we stub poseidon classes.
|
8
|
+
# ----------------------------------------------------------------------------
|
9
|
+
let(:driver) { Poseidon::Producer }
|
10
|
+
let(:message) { Poseidon::MessageToSend }
|
11
|
+
let(:connection) { double :connection, send_messages: nil }
|
12
|
+
|
13
|
+
before do
|
14
|
+
allow(driver).to receive(:new) { connection }
|
15
|
+
|
16
|
+
allow(message).to receive(:new) do |*args|
|
17
|
+
double :message, to_tuple: Hash[[:topic, :value, :key].zip(args)]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# ============================================================================
|
21
|
+
|
22
|
+
let(:producer) { described_class.new options }
|
23
|
+
let(:client) { "foo" }
|
24
|
+
let(:brokers) { %w(127.0.0.1:9092 127.0.0.2:9093) }
|
25
|
+
let(:options) { attributes.merge(brokers: brokers, client_id: client) }
|
26
|
+
let(:attributes) do
|
27
|
+
{
|
28
|
+
partitioner: proc { |v, c| v % c },
|
29
|
+
type: :sync,
|
30
|
+
compression_codec: :gzip,
|
31
|
+
metadata_refresh_interval_ms: 100,
|
32
|
+
max_send_retries: 3,
|
33
|
+
retry_backoff_ms: 300,
|
34
|
+
required_acks: 2,
|
35
|
+
ack_timeout_ms: 200,
|
36
|
+
socket_timeout_ms: 400
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#connection" do
|
41
|
+
subject { producer.connection }
|
42
|
+
|
43
|
+
it "instantiates the driver" do
|
44
|
+
expect(driver).to receive(:new).with(brokers, client, attributes)
|
45
|
+
expect(subject).to eql(connection)
|
46
|
+
end
|
47
|
+
end # describe #connection
|
48
|
+
|
49
|
+
describe "#publish" do
|
50
|
+
subject { producer.publish(*input) }
|
51
|
+
|
52
|
+
let(:input) do
|
53
|
+
[
|
54
|
+
{ value: "foo", topic: "foos", key: 1 },
|
55
|
+
[{ value: "bar", topic: "bars" }]
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:output) do
|
60
|
+
[
|
61
|
+
{ value: "foo", topic: "foos", key: "1" },
|
62
|
+
{ value: "bar", topic: "bars", key: nil }
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "builds messages and sends it to the #connection" do
|
67
|
+
expect(connection).to receive(:send_messages) do |args|
|
68
|
+
expect(args.map(&:to_tuple)).to eql output
|
69
|
+
end
|
70
|
+
|
71
|
+
subject
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns the plain array of tuples" do
|
75
|
+
expect(subject).to eql output
|
76
|
+
end
|
77
|
+
end # describe #publish
|
78
|
+
|
79
|
+
end # describe ROM::Kafka::Connection::Producer
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe ROM::Kafka::Commands::Create do
|
4
|
+
|
5
|
+
let(:command) { described_class.new relation }
|
6
|
+
let(:relation) { double :relation, dataset: dataset }
|
7
|
+
let(:dataset) { double :dataset, producer: producer, topic: "qux" }
|
8
|
+
let(:producer) { double :producer, publish: output }
|
9
|
+
let(:output) { double :output }
|
10
|
+
|
11
|
+
describe ".adapter" do
|
12
|
+
subject { described_class.adapter }
|
13
|
+
|
14
|
+
it { is_expected.to eql(:kafka) }
|
15
|
+
end # describe .adapter
|
16
|
+
|
17
|
+
describe ".new" do
|
18
|
+
subject { command }
|
19
|
+
|
20
|
+
it { is_expected.to be_kind_of ROM::Commands::Create }
|
21
|
+
end # describe .new
|
22
|
+
|
23
|
+
describe "#key" do
|
24
|
+
subject { command.key }
|
25
|
+
|
26
|
+
it { is_expected.to be_nil }
|
27
|
+
end # describe #key
|
28
|
+
|
29
|
+
describe "#with" do
|
30
|
+
subject { command.with(key: "foo") }
|
31
|
+
|
32
|
+
it "returns a command" do
|
33
|
+
expect(subject).to be_kind_of described_class
|
34
|
+
end
|
35
|
+
|
36
|
+
it "preserves current relation" do
|
37
|
+
expect(subject.relation).to eql relation
|
38
|
+
end
|
39
|
+
|
40
|
+
it "updates the key" do
|
41
|
+
expect(subject.key).to eql("foo")
|
42
|
+
end
|
43
|
+
end # describe #using
|
44
|
+
|
45
|
+
describe "#call" do
|
46
|
+
subject { command.call(:bar, ["baz"]) }
|
47
|
+
|
48
|
+
context "when key isn't set" do
|
49
|
+
let(:bar) { { value: "bar", topic: "qux", key: nil } }
|
50
|
+
let(:baz) { { value: "baz", topic: "qux", key: nil } }
|
51
|
+
|
52
|
+
it "publishes tuples to the producer" do
|
53
|
+
expect(producer).to receive(:publish).with(bar, baz)
|
54
|
+
subject
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns tuples" do
|
58
|
+
expect(subject).to eql output
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when key is set" do
|
63
|
+
let(:command) { described_class.new(relation).with(key: "foo") }
|
64
|
+
|
65
|
+
let(:bar) { { value: "bar", topic: "qux", key: "foo" } }
|
66
|
+
let(:baz) { { value: "baz", topic: "qux", key: "foo" } }
|
67
|
+
|
68
|
+
it "publishes tuples to the producer" do
|
69
|
+
expect(producer).to receive(:publish).with(bar, baz)
|
70
|
+
subject
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns tuples" do
|
74
|
+
expect(subject).to eql output
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end # describe #call
|
78
|
+
|
79
|
+
end # describe ROM::Kafka::Relation
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe ROM::Kafka::Dataset do
|
4
|
+
|
5
|
+
let(:gateway_class) { ROM::Kafka::Gateway }
|
6
|
+
let(:consumer_class) { ROM::Kafka::Connection::Consumer }
|
7
|
+
let(:consumer) { double :consumer }
|
8
|
+
before { allow(consumer_class).to receive(:new) { consumer } }
|
9
|
+
|
10
|
+
let(:dataset) { described_class.new(gateway, topic.to_sym) }
|
11
|
+
let(:gateway) { gateway_class.new client_id: "foo" }
|
12
|
+
let(:topic) { "bar" }
|
13
|
+
|
14
|
+
describe "#gateway" do
|
15
|
+
subject { dataset.gateway }
|
16
|
+
|
17
|
+
it "is initialized" do
|
18
|
+
expect(subject).to eql gateway
|
19
|
+
end
|
20
|
+
end # describe #gateway
|
21
|
+
|
22
|
+
describe "#topic" do
|
23
|
+
subject { dataset.topic }
|
24
|
+
|
25
|
+
it "is initialized" do
|
26
|
+
expect(subject).to eql topic
|
27
|
+
end
|
28
|
+
end # describe #topic
|
29
|
+
|
30
|
+
describe "#attributes" do
|
31
|
+
subject { dataset.attributes }
|
32
|
+
|
33
|
+
context "by default" do
|
34
|
+
let(:attributes) do
|
35
|
+
{
|
36
|
+
partition: 0,
|
37
|
+
offset: 0,
|
38
|
+
limit: 0,
|
39
|
+
min_bytes: gateway.min_bytes,
|
40
|
+
max_bytes: gateway.max_bytes,
|
41
|
+
max_wait_ms: gateway.max_wait_ms
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
it "is taken from a gateway" do
|
46
|
+
expect(subject).to eql attributes
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when options are set" do
|
51
|
+
let(:dataset) { described_class.new(gateway, topic, attributes) }
|
52
|
+
let(:attributes) do
|
53
|
+
{
|
54
|
+
partition: 1,
|
55
|
+
offset: 2,
|
56
|
+
limit: 10,
|
57
|
+
min_bytes: 1_024,
|
58
|
+
max_bytes: 10_240,
|
59
|
+
max_wait_ms: 100
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is initialized" do
|
64
|
+
expect(subject).to eql attributes
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # describe #attributes
|
68
|
+
|
69
|
+
describe "#producer" do
|
70
|
+
subject { dataset.producer }
|
71
|
+
|
72
|
+
it "is taken from #gateway" do
|
73
|
+
expect(subject).to eql gateway.producer
|
74
|
+
end
|
75
|
+
end # describe #producer
|
76
|
+
|
77
|
+
describe "#consumer" do
|
78
|
+
subject { dataset.consumer }
|
79
|
+
|
80
|
+
let(:dataset) { described_class.new(gateway, topic, attributes) }
|
81
|
+
|
82
|
+
let(:attributes) do
|
83
|
+
{
|
84
|
+
partition: 1,
|
85
|
+
offset: 2,
|
86
|
+
limit: 0,
|
87
|
+
min_bytes: 1_024,
|
88
|
+
max_bytes: 10_240,
|
89
|
+
max_wait_ms: 100
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
let(:options) do
|
94
|
+
attributes.merge(
|
95
|
+
topic: topic,
|
96
|
+
client_id: gateway.client_id,
|
97
|
+
brokers: gateway.brokers
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "is initialized with proper options" do
|
102
|
+
expect(consumer_class).to receive(:new).with(options)
|
103
|
+
expect(subject).to eql consumer
|
104
|
+
end
|
105
|
+
end # describe #consumer
|
106
|
+
|
107
|
+
describe "#using" do
|
108
|
+
subject { dataset.using(update) }
|
109
|
+
|
110
|
+
let(:dataset) { described_class.new gateway, topic, min_bytes: 8 }
|
111
|
+
let(:update) { { partition: 1, offset: 2 } }
|
112
|
+
|
113
|
+
it "builds new dataset" do
|
114
|
+
expect(subject).to be_kind_of described_class
|
115
|
+
end
|
116
|
+
|
117
|
+
it "preserves gateway" do
|
118
|
+
expect(subject.gateway).to eql(gateway)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "preserves topic" do
|
122
|
+
expect(subject.topic).to eql(topic)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "updates attributes" do
|
126
|
+
expect(subject.attributes).to eql(dataset.attributes.merge(update))
|
127
|
+
end
|
128
|
+
end # describe #using
|
129
|
+
|
130
|
+
describe "#each" do
|
131
|
+
subject { dataset.to_a }
|
132
|
+
|
133
|
+
let(:consumer) { double :consumer, each: data.each }
|
134
|
+
let(:data) { %w(foo bar baz qux) }
|
135
|
+
|
136
|
+
context "when limit isn't set" do
|
137
|
+
it "is delegated to the consumer" do
|
138
|
+
expect(subject).to eql data
|
139
|
+
end
|
140
|
+
|
141
|
+
it "yields limited number of times" do
|
142
|
+
expect { |b| dataset.each(&b) }.to yield_control.exactly(4).times
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when limit is set" do
|
147
|
+
let(:dataset) { described_class.new(gateway, topic, limit: 2) }
|
148
|
+
|
149
|
+
it "is delegated to the consumer" do
|
150
|
+
expect(subject).to eql data[0..1]
|
151
|
+
end
|
152
|
+
|
153
|
+
it "yields limited number of times" do
|
154
|
+
expect { |b| dataset.each(&b) }.to yield_control.twice
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "without a block" do
|
159
|
+
subject { dataset.each }
|
160
|
+
|
161
|
+
it { is_expected.to be_kind_of Enumerator }
|
162
|
+
end
|
163
|
+
end # describe #each
|
164
|
+
|
165
|
+
end # describe ROM::Kafka::Dataset
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe ROM::Kafka::Gateway do
|
4
|
+
|
5
|
+
let(:gateway) { described_class.new(client_id: :foo) }
|
6
|
+
|
7
|
+
describe ".new" do
|
8
|
+
context "without client id" do
|
9
|
+
subject { gateway }
|
10
|
+
|
11
|
+
it { is_expected.to be_kind_of ROM::Gateway }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "without client id" do
|
15
|
+
subject { described_class.new }
|
16
|
+
|
17
|
+
it "fails" do
|
18
|
+
expect { subject }.to raise_error ArgumentError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end # describe .new
|
22
|
+
|
23
|
+
describe "#brokers" do
|
24
|
+
subject { gateway.brokers }
|
25
|
+
|
26
|
+
let(:brokers) { %w(localhost:9092 127.0.0.1:9092) }
|
27
|
+
|
28
|
+
context "from strings" do
|
29
|
+
let(:gateway) { described_class.new(*brokers, client_id: :foo) }
|
30
|
+
|
31
|
+
it { is_expected.to eql brokers }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "from hosts and port options" do
|
35
|
+
let(:gateway) do
|
36
|
+
described_class
|
37
|
+
.new(client_id: :foo, hosts: %w(localhost 127.0.0.1), port: 9092)
|
38
|
+
end
|
39
|
+
|
40
|
+
it { is_expected.to eql brokers }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "from mixed options" do
|
44
|
+
let(:gateway) do
|
45
|
+
described_class
|
46
|
+
.new("localhost", client_id: :foo, hosts: %w(127.0.0.1), port: 9092)
|
47
|
+
end
|
48
|
+
|
49
|
+
it { is_expected.to eql brokers }
|
50
|
+
end
|
51
|
+
end # describe #hosts
|
52
|
+
|
53
|
+
describe "#attributes" do
|
54
|
+
subject { gateway.attributes }
|
55
|
+
|
56
|
+
context "by default" do
|
57
|
+
let(:attributes) { { client_id: :foo } }
|
58
|
+
|
59
|
+
it "is set" do
|
60
|
+
expect(subject).to eql(
|
61
|
+
ack_timeout_ms: 1_500,
|
62
|
+
brokers: ["localhost:9092"],
|
63
|
+
client_id: "foo",
|
64
|
+
compression_codec: nil,
|
65
|
+
max_bytes: 1_048_576,
|
66
|
+
max_send_retries: 3,
|
67
|
+
max_wait_ms: 100,
|
68
|
+
metadata_refresh_interval_ms: 600_000,
|
69
|
+
min_bytes: 1,
|
70
|
+
partitioner: nil,
|
71
|
+
required_acks: 0,
|
72
|
+
retry_backoff_ms: 100,
|
73
|
+
socket_timeout_ms: 10_000
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when assigned" do
|
79
|
+
let(:gateway) { described_class.new(attributes) }
|
80
|
+
|
81
|
+
let(:attributes) do
|
82
|
+
{
|
83
|
+
ack_timeout_ms: 200,
|
84
|
+
brokers: ["localhost:9092"],
|
85
|
+
client_id: "foo",
|
86
|
+
compression_codec: :gzip,
|
87
|
+
max_bytes: 2_048,
|
88
|
+
max_send_retries: 2,
|
89
|
+
max_wait_ms: 300,
|
90
|
+
metadata_refresh_interval_ms: 300_000,
|
91
|
+
min_bytes: 1_024,
|
92
|
+
partitioner: proc { |value, count| value % count },
|
93
|
+
required_acks: 1,
|
94
|
+
retry_backoff_ms: 200,
|
95
|
+
socket_timeout_ms: 20_000
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it { is_expected.to eql attributes }
|
100
|
+
end
|
101
|
+
end # describe #attributes
|
102
|
+
|
103
|
+
describe "#[]" do
|
104
|
+
subject { gateway[:foo] }
|
105
|
+
|
106
|
+
it { is_expected.to eql(nil) }
|
107
|
+
end # describe #[]
|
108
|
+
|
109
|
+
describe "#dataset?" do
|
110
|
+
before do
|
111
|
+
allow(gateway).to receive(:[]) { |name| { foo: :FOO }[name.to_sym] }
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when dataset is registered" do
|
115
|
+
subject { gateway.dataset? "foo" }
|
116
|
+
|
117
|
+
it { is_expected.to eql true }
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when dataset isn't registered" do
|
121
|
+
subject { gateway.dataset? "bar" }
|
122
|
+
|
123
|
+
it { is_expected.to eql false }
|
124
|
+
end
|
125
|
+
end # describe #dataset
|
126
|
+
|
127
|
+
describe "#dataset" do
|
128
|
+
subject { gateway.dataset topic }
|
129
|
+
|
130
|
+
let(:klass) { ROM::Kafka::Dataset }
|
131
|
+
let(:dataset) { double :dataset }
|
132
|
+
let(:topic) { "foobar" }
|
133
|
+
|
134
|
+
before { allow(klass).to receive(:new) { dataset } }
|
135
|
+
|
136
|
+
it "builds a dataset" do
|
137
|
+
expect(klass).to receive(:new).with(gateway, topic)
|
138
|
+
subject
|
139
|
+
end
|
140
|
+
|
141
|
+
it "registers a dataset by symbol" do
|
142
|
+
expect { subject }.to change { gateway[:foobar] }.from(nil).to(dataset)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "registers a dataset by string" do
|
146
|
+
expect { subject }.to change { gateway["foobar"] }.from(nil).to(dataset)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns a dataset" do
|
150
|
+
expect(subject).to eql(dataset)
|
151
|
+
end
|
152
|
+
end # describe #dataset
|
153
|
+
|
154
|
+
describe "#producer" do
|
155
|
+
subject { gateway.producer }
|
156
|
+
let(:producer) { double :producer }
|
157
|
+
|
158
|
+
it "builds a producer" do
|
159
|
+
attributes = {}
|
160
|
+
producer = double :producer
|
161
|
+
|
162
|
+
expect(ROM::Kafka::Connection::Producer).to receive(:new) do |opts|
|
163
|
+
attributes = opts
|
164
|
+
producer
|
165
|
+
end
|
166
|
+
expect(subject).to eql producer
|
167
|
+
expect(attributes).to eql gateway.attributes
|
168
|
+
end
|
169
|
+
end # describe #producer
|
170
|
+
|
171
|
+
end # describe ROM::Kafka::Gateway
|