deimos-ruby 1.24.3 → 2.0.0.pre.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 +4 -4
- data/.rubocop_todo.yml +0 -17
- data/.tool-versions +1 -0
- data/CHANGELOG.md +1 -1
- data/README.md +287 -498
- data/deimos-ruby.gemspec +4 -4
- data/docs/CONFIGURATION.md +133 -227
- data/docs/UPGRADING.md +237 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +28 -29
- data/lib/deimos/active_record_consume/message_consumption.rb +15 -21
- data/lib/deimos/active_record_consumer.rb +36 -26
- data/lib/deimos/active_record_producer.rb +28 -9
- data/lib/deimos/backends/base.rb +4 -35
- data/lib/deimos/backends/kafka.rb +6 -22
- data/lib/deimos/backends/kafka_async.rb +6 -22
- data/lib/deimos/backends/{db.rb → outbox.rb} +13 -9
- data/lib/deimos/config/configuration.rb +116 -385
- data/lib/deimos/consume/batch_consumption.rb +24 -124
- data/lib/deimos/consume/message_consumption.rb +36 -63
- data/lib/deimos/consumer.rb +16 -75
- data/lib/deimos/ext/consumer_route.rb +35 -0
- data/lib/deimos/ext/producer_middleware.rb +94 -0
- data/lib/deimos/ext/producer_route.rb +22 -0
- data/lib/deimos/ext/redraw.rb +29 -0
- data/lib/deimos/ext/routing_defaults.rb +72 -0
- data/lib/deimos/ext/schema_route.rb +70 -0
- data/lib/deimos/kafka_message.rb +2 -2
- data/lib/deimos/kafka_source.rb +2 -7
- data/lib/deimos/kafka_topic_info.rb +1 -1
- data/lib/deimos/logging.rb +71 -0
- data/lib/deimos/message.rb +2 -11
- data/lib/deimos/metrics/datadog.rb +40 -1
- data/lib/deimos/metrics/provider.rb +4 -4
- data/lib/deimos/producer.rb +39 -116
- data/lib/deimos/railtie.rb +6 -0
- data/lib/deimos/schema_backends/avro_base.rb +21 -21
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -2
- data/lib/deimos/schema_backends/avro_validation.rb +2 -2
- data/lib/deimos/schema_backends/base.rb +19 -12
- data/lib/deimos/schema_backends/mock.rb +6 -1
- data/lib/deimos/schema_backends/plain.rb +47 -0
- data/lib/deimos/schema_class/base.rb +2 -2
- data/lib/deimos/schema_class/enum.rb +1 -1
- data/lib/deimos/schema_class/record.rb +2 -2
- data/lib/deimos/test_helpers.rb +95 -320
- data/lib/deimos/tracing/provider.rb +6 -6
- data/lib/deimos/transcoder.rb +88 -0
- data/lib/deimos/utils/db_poller/base.rb +16 -14
- data/lib/deimos/utils/db_poller/state_based.rb +3 -3
- data/lib/deimos/utils/db_poller/time_based.rb +4 -4
- data/lib/deimos/utils/db_poller.rb +1 -1
- data/lib/deimos/utils/deadlock_retry.rb +1 -1
- data/lib/deimos/utils/{db_producer.rb → outbox_producer.rb} +16 -47
- data/lib/deimos/utils/schema_class.rb +0 -7
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +79 -26
- data/lib/generators/deimos/{db_backend_generator.rb → outbox_backend_generator.rb} +4 -4
- data/lib/generators/deimos/schema_class_generator.rb +0 -1
- data/lib/generators/deimos/v2/templates/karafka.rb.tt +149 -0
- data/lib/generators/deimos/v2_generator.rb +193 -0
- data/lib/tasks/deimos.rake +5 -7
- data/spec/active_record_batch_consumer_association_spec.rb +22 -13
- data/spec/active_record_batch_consumer_spec.rb +84 -65
- data/spec/active_record_consume/batch_consumption_spec.rb +10 -10
- data/spec/active_record_consume/batch_slicer_spec.rb +12 -12
- data/spec/active_record_consumer_spec.rb +29 -13
- data/spec/active_record_producer_spec.rb +36 -26
- data/spec/backends/base_spec.rb +0 -23
- data/spec/backends/kafka_async_spec.rb +1 -3
- data/spec/backends/kafka_spec.rb +1 -3
- data/spec/backends/{db_spec.rb → outbox_spec.rb} +14 -20
- data/spec/batch_consumer_spec.rb +66 -116
- data/spec/consumer_spec.rb +53 -147
- data/spec/deimos_spec.rb +10 -126
- data/spec/kafka_source_spec.rb +19 -52
- data/spec/karafka/karafka.rb +69 -0
- data/spec/karafka_config/karafka_spec.rb +97 -0
- data/spec/logging_spec.rb +25 -0
- data/spec/message_spec.rb +9 -9
- data/spec/producer_spec.rb +112 -254
- data/spec/rake_spec.rb +1 -3
- data/spec/schema_backends/avro_validation_spec.rb +1 -1
- data/spec/schemas/com/my-namespace/MySchemaWithTitle.avsc +22 -0
- data/spec/snapshots/consumers-no-nest.snap +49 -0
- data/spec/snapshots/consumers.snap +49 -0
- data/spec/snapshots/consumers_and_producers-no-nest.snap +49 -0
- data/spec/snapshots/consumers_and_producers.snap +49 -0
- data/spec/snapshots/consumers_circular-no-nest.snap +49 -0
- data/spec/snapshots/consumers_circular.snap +49 -0
- data/spec/snapshots/consumers_complex_types-no-nest.snap +49 -0
- data/spec/snapshots/consumers_complex_types.snap +49 -0
- data/spec/snapshots/consumers_nested-no-nest.snap +49 -0
- data/spec/snapshots/consumers_nested.snap +49 -0
- data/spec/snapshots/namespace_folders.snap +49 -0
- data/spec/snapshots/namespace_map.snap +49 -0
- data/spec/snapshots/producers_with_key-no-nest.snap +49 -0
- data/spec/snapshots/producers_with_key.snap +49 -0
- data/spec/spec_helper.rb +61 -29
- data/spec/utils/db_poller_spec.rb +49 -39
- data/spec/utils/{db_producer_spec.rb → outbox_producer_spec.rb} +17 -184
- metadata +58 -67
- data/lib/deimos/batch_consumer.rb +0 -7
- data/lib/deimos/config/phobos_config.rb +0 -164
- data/lib/deimos/instrumentation.rb +0 -95
- data/lib/deimos/monkey_patches/phobos_cli.rb +0 -35
- data/lib/deimos/utils/inline_consumer.rb +0 -158
- data/lib/deimos/utils/lag_reporter.rb +0 -186
- data/lib/deimos/utils/schema_controller_mixin.rb +0 -129
- data/spec/config/configuration_spec.rb +0 -329
- data/spec/kafka_listener_spec.rb +0 -55
- data/spec/phobos.bad_db.yml +0 -73
- data/spec/phobos.yml +0 -77
- data/spec/utils/inline_consumer_spec.rb +0 -31
- data/spec/utils/lag_reporter_spec.rb +0 -76
- data/spec/utils/platform_schema_validation_spec.rb +0 -0
- data/spec/utils/schema_controller_mixin_spec.rb +0 -84
- /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/migration +0 -0
- /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/rails3_migration +0 -0
data/spec/consumer_spec.rb
CHANGED
|
@@ -4,33 +4,44 @@
|
|
|
4
4
|
# rubocop:disable Metrics/ModuleLength
|
|
5
5
|
module ConsumerTest
|
|
6
6
|
describe Deimos::Consumer, 'Message Consumer' do
|
|
7
|
+
let(:use_schema_classes) { false }
|
|
8
|
+
let(:reraise_errors) { false }
|
|
7
9
|
prepend_before(:each) do
|
|
8
10
|
# :nodoc:
|
|
9
11
|
consumer_class = Class.new(described_class) do
|
|
10
|
-
schema 'MySchema'
|
|
11
|
-
namespace 'com.my-namespace'
|
|
12
|
-
key_config field: 'test_id'
|
|
13
12
|
|
|
14
13
|
# :nodoc:
|
|
15
|
-
def fatal_error?(_exception,
|
|
16
|
-
|
|
14
|
+
def fatal_error?(_exception, messages)
|
|
15
|
+
messages.payloads.first&.dig(:test_id) == ['fatal']
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
# :nodoc:
|
|
20
|
-
def
|
|
21
|
-
|
|
19
|
+
def consume_message(message)
|
|
20
|
+
message.payload
|
|
22
21
|
end
|
|
23
22
|
end
|
|
24
23
|
stub_const('ConsumerTest::MyConsumer', consumer_class)
|
|
24
|
+
route_usc = use_schema_classes
|
|
25
|
+
route_rre = reraise_errors
|
|
26
|
+
Karafka::App.routes.redraw do
|
|
27
|
+
topic 'my_consume_topic' do
|
|
28
|
+
schema 'MySchema'
|
|
29
|
+
namespace 'com.my-namespace'
|
|
30
|
+
key_config field: 'test_id'
|
|
31
|
+
consumer consumer_class
|
|
32
|
+
use_schema_classes route_usc
|
|
33
|
+
reraise_errors route_rre
|
|
34
|
+
end
|
|
35
|
+
end
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
describe 'consume' do
|
|
28
39
|
SCHEMA_CLASS_SETTINGS.each do |setting, use_schema_classes|
|
|
40
|
+
let(:use_schema_classes) { use_schema_classes }
|
|
29
41
|
context "with Schema Class consumption #{setting}" do
|
|
30
42
|
|
|
31
43
|
before(:each) do
|
|
32
44
|
Deimos.configure do |config|
|
|
33
|
-
config.schema.use_schema_classes = use_schema_classes
|
|
34
45
|
config.schema.use_full_namespace = true
|
|
35
46
|
end
|
|
36
47
|
end
|
|
@@ -45,26 +56,9 @@ module ConsumerTest
|
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
it 'should consume a nil message' do
|
|
48
|
-
test_consume_message(MyConsumer, nil) do
|
|
49
|
-
expect(
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it 'should consume a message idempotently' do
|
|
54
|
-
# testing for a crash and re-consuming the same message/metadata
|
|
55
|
-
key = { 'test_id' => 'foo' }
|
|
56
|
-
test_metadata = { key: key }
|
|
57
|
-
allow_any_instance_of(MyConsumer).to(receive(:decode_key)) do |_instance, k|
|
|
58
|
-
k['test_id']
|
|
59
|
+
test_consume_message(MyConsumer, nil, key: 'foo') do
|
|
60
|
+
expect(messages).to be_empty
|
|
59
61
|
end
|
|
60
|
-
MyConsumer.new.around_consume({ 'test_id' => 'foo',
|
|
61
|
-
'some_int' => 123 }, test_metadata) do |_payload, metadata|
|
|
62
|
-
expect(metadata[:key]).to eq('foo')
|
|
63
|
-
end
|
|
64
|
-
MyConsumer.new.around_consume({ 'test_id' => 'foo',
|
|
65
|
-
'some_int' => 123 }, test_metadata) do |_payload, metadata|
|
|
66
|
-
expect(metadata[:key]).to eq('foo')
|
|
67
|
-
end
|
|
68
62
|
end
|
|
69
63
|
|
|
70
64
|
it 'should consume a message on a topic' do
|
|
@@ -77,83 +71,82 @@ module ConsumerTest
|
|
|
77
71
|
end
|
|
78
72
|
|
|
79
73
|
it 'should fail on invalid message' do
|
|
80
|
-
|
|
74
|
+
expect { test_consume_message(MyConsumer, { 'invalid' => 'key' }) }.
|
|
75
|
+
to raise_error(Avro::SchemaValidator::ValidationError)
|
|
81
76
|
end
|
|
82
77
|
|
|
83
78
|
it 'should fail if reraise is false but fatal_error is true' do
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
expect { test_consume_message(MyConsumer, {test_id: 'fatal'}) }.
|
|
80
|
+
to raise_error(Avro::SchemaValidator::ValidationError)
|
|
86
81
|
end
|
|
87
82
|
|
|
88
83
|
it 'should fail if fatal_error is true globally' do
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
93
|
-
test_consume_invalid_message(MyConsumer, { 'invalid' => 'key' })
|
|
84
|
+
set_karafka_config(:fatal_error, proc { true })
|
|
85
|
+
expect { test_consume_message(MyConsumer, { 'invalid' => 'key' }) }.
|
|
86
|
+
to raise_error(Avro::SchemaValidator::ValidationError)
|
|
94
87
|
end
|
|
95
88
|
|
|
96
89
|
it 'should fail on message with extra fields' do
|
|
97
|
-
|
|
90
|
+
allow_any_instance_of(Deimos::SchemaBackends::AvroValidation).
|
|
91
|
+
to receive(:coerce) { |_, m| m.with_indifferent_access }
|
|
92
|
+
expect { test_consume_message(MyConsumer,
|
|
98
93
|
{ 'test_id' => 'foo',
|
|
99
94
|
'some_int' => 123,
|
|
100
|
-
'extra_field' => 'field name' })
|
|
95
|
+
'extra_field' => 'field name' }) }.
|
|
96
|
+
to raise_error(Avro::SchemaValidator::ValidationError)
|
|
101
97
|
end
|
|
102
98
|
|
|
103
99
|
it 'should not fail when before_consume fails without reraising errors' do
|
|
104
|
-
|
|
100
|
+
set_karafka_config(:reraise_errors, false)
|
|
105
101
|
expect {
|
|
106
102
|
test_consume_message(
|
|
107
103
|
MyConsumer,
|
|
108
104
|
{ 'test_id' => 'foo',
|
|
109
|
-
'some_int' => 123 }
|
|
110
|
-
skip_expectation: true
|
|
111
|
-
) { raise 'OH NOES' }
|
|
105
|
+
'some_int' => 123 }) { raise 'OH NOES' }
|
|
112
106
|
}.not_to raise_error
|
|
113
107
|
end
|
|
114
108
|
|
|
115
109
|
it 'should not fail when consume fails without reraising errors' do
|
|
116
|
-
|
|
110
|
+
set_karafka_config(:reraise_errors, false)
|
|
111
|
+
allow(Deimos::ProducerMiddleware).to receive(:call) { |m| m[:payload] = m[:payload].to_json; m }
|
|
117
112
|
expect {
|
|
118
113
|
test_consume_message(
|
|
119
114
|
MyConsumer,
|
|
120
|
-
{ 'invalid' => 'key' }
|
|
121
|
-
skip_expectation: true
|
|
122
|
-
)
|
|
115
|
+
{ 'invalid' => 'key' })
|
|
123
116
|
}.not_to raise_error
|
|
124
117
|
end
|
|
125
|
-
|
|
126
|
-
it 'should call original' do
|
|
127
|
-
expect {
|
|
128
|
-
test_consume_message(MyConsumer,
|
|
129
|
-
{ 'test_id' => 'foo', 'some_int' => 123 },
|
|
130
|
-
call_original: true)
|
|
131
|
-
}.to raise_error('This should not be called unless call_original is set')
|
|
132
|
-
end
|
|
133
118
|
end
|
|
134
119
|
end
|
|
135
120
|
|
|
136
121
|
context 'with overriden schema classes' do
|
|
137
122
|
|
|
138
123
|
before(:each) do
|
|
124
|
+
set_karafka_config(:use_schema_classes, true)
|
|
139
125
|
Deimos.configure do |config|
|
|
140
|
-
config.schema.use_schema_classes = true
|
|
141
126
|
config.schema.use_full_namespace = true
|
|
142
127
|
end
|
|
143
128
|
end
|
|
144
129
|
|
|
145
130
|
prepend_before(:each) do
|
|
146
131
|
consumer_class = Class.new(described_class) do
|
|
147
|
-
schema 'MyUpdatedSchema'
|
|
148
|
-
namespace 'com.my-namespace'
|
|
149
|
-
key_config field: 'test_id'
|
|
150
|
-
|
|
151
132
|
# :nodoc:
|
|
152
|
-
def
|
|
153
|
-
|
|
133
|
+
def consume_message(message)
|
|
134
|
+
message.payload
|
|
154
135
|
end
|
|
155
136
|
end
|
|
156
137
|
stub_const('ConsumerTest::MyConsumer', consumer_class)
|
|
138
|
+
Deimos.config.schema.use_schema_classes = true
|
|
139
|
+
Karafka::App.routes.redraw do
|
|
140
|
+
topic 'my_consume_topic' do
|
|
141
|
+
schema 'MyUpdatedSchema'
|
|
142
|
+
namespace 'com.my-namespace'
|
|
143
|
+
key_config field: 'test_id'
|
|
144
|
+
consumer consumer_class
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
after(:each) do
|
|
149
|
+
Karafka::App.routes.clear
|
|
157
150
|
end
|
|
158
151
|
|
|
159
152
|
it 'should consume messages' do
|
|
@@ -169,93 +162,6 @@ module ConsumerTest
|
|
|
169
162
|
end
|
|
170
163
|
end
|
|
171
164
|
|
|
172
|
-
describe 'decode_key' do
|
|
173
|
-
|
|
174
|
-
it 'should use the key field in the value if set' do
|
|
175
|
-
# actual decoding is disabled in test
|
|
176
|
-
expect(MyConsumer.new.decode_key('test_id' => '123')).to eq('123')
|
|
177
|
-
expect { MyConsumer.new.decode_key(123) }.to raise_error(NoMethodError)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
it 'should use the key schema if set' do
|
|
181
|
-
consumer_class = Class.new(described_class) do
|
|
182
|
-
schema 'MySchema'
|
|
183
|
-
namespace 'com.my-namespace'
|
|
184
|
-
key_config schema: 'MySchema_key'
|
|
185
|
-
end
|
|
186
|
-
stub_const('ConsumerTest::MySchemaConsumer', consumer_class)
|
|
187
|
-
expect(MyConsumer.new.decode_key('test_id' => '123')).to eq('123')
|
|
188
|
-
expect { MyConsumer.new.decode_key(123) }.to raise_error(NoMethodError)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
it 'should not decode if plain is set' do
|
|
192
|
-
consumer_class = Class.new(described_class) do
|
|
193
|
-
schema 'MySchema'
|
|
194
|
-
namespace 'com.my-namespace'
|
|
195
|
-
key_config plain: true
|
|
196
|
-
end
|
|
197
|
-
stub_const('ConsumerTest::MyNonEncodedConsumer', consumer_class)
|
|
198
|
-
expect(MyNonEncodedConsumer.new.decode_key('123')).to eq('123')
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
it 'should error with nothing set' do
|
|
202
|
-
consumer_class = Class.new(described_class) do
|
|
203
|
-
schema 'MySchema'
|
|
204
|
-
namespace 'com.my-namespace'
|
|
205
|
-
end
|
|
206
|
-
stub_const('ConsumerTest::MyErrorConsumer', consumer_class)
|
|
207
|
-
expect { MyErrorConsumer.new.decode_key('123') }.
|
|
208
|
-
to raise_error('No key config given - if you are not decoding keys, please use `key_config plain: true`')
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
describe 'timestamps' do
|
|
214
|
-
before(:each) do
|
|
215
|
-
# :nodoc:
|
|
216
|
-
consumer_class = Class.new(described_class) do
|
|
217
|
-
schema 'MySchemaWithDateTimes'
|
|
218
|
-
namespace 'com.my-namespace'
|
|
219
|
-
key_config plain: true
|
|
220
|
-
|
|
221
|
-
# :nodoc:
|
|
222
|
-
def consume(_payload, _metadata)
|
|
223
|
-
raise 'This should not be called unless call_original is set'
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
stub_const('ConsumerTest::MyConsumer', consumer_class)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
it 'should consume a message' do
|
|
230
|
-
expect(Deimos.config.metrics).to receive(:histogram).twice
|
|
231
|
-
test_consume_message('my_consume_topic',
|
|
232
|
-
{ 'test_id' => 'foo',
|
|
233
|
-
'some_int' => 123,
|
|
234
|
-
'updated_at' => Time.now.to_i,
|
|
235
|
-
'timestamp' => 2.minutes.ago.to_s }) do |payload, _metadata|
|
|
236
|
-
expect(payload['test_id']).to eq('foo')
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
it 'should fail nicely when timestamp wrong format' do
|
|
241
|
-
expect(Deimos.config.metrics).to receive(:histogram).twice
|
|
242
|
-
test_consume_message('my_consume_topic',
|
|
243
|
-
{ 'test_id' => 'foo',
|
|
244
|
-
'some_int' => 123,
|
|
245
|
-
'updated_at' => Time.now.to_i,
|
|
246
|
-
'timestamp' => 'dffdf' }) do |payload, _metadata|
|
|
247
|
-
expect(payload['test_id']).to eq('foo')
|
|
248
|
-
end
|
|
249
|
-
test_consume_message('my_consume_topic',
|
|
250
|
-
{ 'test_id' => 'foo',
|
|
251
|
-
'some_int' => 123,
|
|
252
|
-
'updated_at' => Time.now.to_i,
|
|
253
|
-
'timestamp' => '' }) do |payload, _metadata|
|
|
254
|
-
expect(payload['test_id']).to eq('foo')
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
end
|
|
259
165
|
end
|
|
260
166
|
end
|
|
261
167
|
# rubocop:enable Metrics/ModuleLength
|
data/spec/deimos_spec.rb
CHANGED
|
@@ -2,72 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
describe Deimos do
|
|
4
4
|
|
|
5
|
-
let(:phobos_configuration) do
|
|
6
|
-
{ 'logger' =>
|
|
7
|
-
{ 'file' => 'log/phobos.log',
|
|
8
|
-
'stdout_json' => false,
|
|
9
|
-
'level' => 'debug',
|
|
10
|
-
'ruby_kafka' =>
|
|
11
|
-
{ 'level' => 'debug' } },
|
|
12
|
-
'kafka' =>
|
|
13
|
-
{ 'client_id' => 'phobos',
|
|
14
|
-
'connect_timeout' => 15,
|
|
15
|
-
'socket_timeout' => 15,
|
|
16
|
-
'seed_brokers' => 'my_seed_broker.com',
|
|
17
|
-
'ssl_ca_cert' => 'my_ssl_ca_cert',
|
|
18
|
-
'ssl_client_cert' => 'my_ssl_client_cert',
|
|
19
|
-
'ssl_client_cert_key' => 'my_ssl_client_cert_key' },
|
|
20
|
-
'producer' =>
|
|
21
|
-
{ 'ack_timeout' => 5,
|
|
22
|
-
'required_acks' => :all,
|
|
23
|
-
'max_retries' => 2,
|
|
24
|
-
'retry_backoff' => 1,
|
|
25
|
-
'max_buffer_size' => 10_000,
|
|
26
|
-
'max_buffer_bytesize' => 10_000_000,
|
|
27
|
-
'compression_codec' => nil,
|
|
28
|
-
'compression_threshold' => 1,
|
|
29
|
-
'max_queue_size' => 10_000,
|
|
30
|
-
'delivery_threshold' => 0,
|
|
31
|
-
'delivery_interval' => 0 },
|
|
32
|
-
'consumer' =>
|
|
33
|
-
{ 'session_timeout' => 300,
|
|
34
|
-
'offset_commit_interval' => 10,
|
|
35
|
-
'offset_commit_threshold' => 0,
|
|
36
|
-
'heartbeat_interval' => 10 },
|
|
37
|
-
'backoff' =>
|
|
38
|
-
{ 'min_ms' => 1000,
|
|
39
|
-
'max_ms' => 60_000 },
|
|
40
|
-
'listeners' => [
|
|
41
|
-
{ 'handler' => 'ConsumerTest::MyConsumer',
|
|
42
|
-
'topic' => 'my_consume_topic',
|
|
43
|
-
'group_id' => 'my_group_id',
|
|
44
|
-
'max_bytes_per_partition' => 524_288 },
|
|
45
|
-
{ 'handler' => 'ConsumerTest::MyBatchConsumer',
|
|
46
|
-
'topic' => 'my_batch_consume_topic',
|
|
47
|
-
'group_id' => 'my_batch_group_id',
|
|
48
|
-
'delivery' => 'inline_batch' }
|
|
49
|
-
],
|
|
50
|
-
'custom_logger' => nil,
|
|
51
|
-
'custom_kafka_logger' => nil }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
let(:config_path) { File.join(File.dirname(__FILE__), 'phobos.yml') }
|
|
55
|
-
|
|
56
5
|
it 'should have a version number' do
|
|
57
6
|
expect(Deimos::VERSION).not_to be_nil
|
|
58
7
|
end
|
|
59
8
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
described_class.configure do |config|
|
|
63
|
-
config.producers.backend = :db
|
|
64
|
-
config.phobos_config_file = File.join(File.dirname(__FILE__), 'phobos.bad_db.yml')
|
|
65
|
-
end
|
|
66
|
-
}.to raise_error('Cannot set producers.backend to :db unless producers.required_acks is set to ":all"!')
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
describe '#start_db_backend!' do
|
|
70
|
-
it 'should start if backend is db and thread_count is > 0' do
|
|
9
|
+
describe '#start_outbox_backend!' do
|
|
10
|
+
it 'should start if backend is outbox and thread_count is > 0' do
|
|
71
11
|
signal_handler = instance_double(Sigurd::SignalHandler)
|
|
72
12
|
allow(signal_handler).to receive(:run!)
|
|
73
13
|
expect(Sigurd::Executor).to receive(:new).
|
|
@@ -77,9 +17,9 @@ describe Deimos do
|
|
|
77
17
|
signal_handler
|
|
78
18
|
end
|
|
79
19
|
described_class.configure do |config|
|
|
80
|
-
config.producers.backend = :
|
|
20
|
+
config.producers.backend = :outbox
|
|
81
21
|
end
|
|
82
|
-
described_class.
|
|
22
|
+
described_class.start_outbox_backend!(thread_count: 2)
|
|
83
23
|
end
|
|
84
24
|
|
|
85
25
|
it 'should not start if backend is not db' do
|
|
@@ -87,83 +27,27 @@ describe Deimos do
|
|
|
87
27
|
described_class.configure do |config|
|
|
88
28
|
config.producers.backend = :kafka
|
|
89
29
|
end
|
|
90
|
-
expect { described_class.
|
|
91
|
-
to raise_error('Publish backend is not set to :
|
|
30
|
+
expect { described_class.start_outbox_backend!(thread_count: 2) }.
|
|
31
|
+
to raise_error('Publish backend is not set to :outbox, exiting')
|
|
92
32
|
end
|
|
93
33
|
|
|
94
34
|
it 'should not start if thread_count is nil' do
|
|
95
35
|
expect(Sigurd::SignalHandler).not_to receive(:new)
|
|
96
36
|
described_class.configure do |config|
|
|
97
|
-
config.producers.backend = :
|
|
37
|
+
config.producers.backend = :outbox
|
|
98
38
|
end
|
|
99
|
-
expect { described_class.
|
|
39
|
+
expect { described_class.start_outbox_backend!(thread_count: nil) }.
|
|
100
40
|
to raise_error('Thread count is not given or set to zero, exiting')
|
|
101
41
|
end
|
|
102
42
|
|
|
103
43
|
it 'should not start if thread_count is 0' do
|
|
104
44
|
expect(Sigurd::SignalHandler).not_to receive(:new)
|
|
105
45
|
described_class.configure do |config|
|
|
106
|
-
config.producers.backend = :
|
|
46
|
+
config.producers.backend = :outbox
|
|
107
47
|
end
|
|
108
|
-
expect { described_class.
|
|
48
|
+
expect { described_class.start_outbox_backend!(thread_count: 0) }.
|
|
109
49
|
to raise_error('Thread count is not given or set to zero, exiting')
|
|
110
50
|
end
|
|
111
51
|
end
|
|
112
52
|
|
|
113
|
-
describe 'delivery configuration' do
|
|
114
|
-
before(:each) do
|
|
115
|
-
allow(YAML).to receive(:load).and_return(phobos_configuration)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it 'should not raise an error with properly configured handlers' do
|
|
119
|
-
expect {
|
|
120
|
-
described_class.configure do
|
|
121
|
-
consumer do
|
|
122
|
-
class_name 'ConsumerTest::MyConsumer'
|
|
123
|
-
delivery :message
|
|
124
|
-
end
|
|
125
|
-
consumer do
|
|
126
|
-
class_name 'ConsumerTest::MyConsumer'
|
|
127
|
-
delivery :batch
|
|
128
|
-
end
|
|
129
|
-
consumer do
|
|
130
|
-
class_name 'ConsumerTest::MyBatchConsumer'
|
|
131
|
-
delivery :inline_batch
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
}.not_to raise_error
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
it 'should raise an error if inline_batch listeners do not implement consume_batch' do
|
|
138
|
-
expect {
|
|
139
|
-
described_class.configure do
|
|
140
|
-
consumer do
|
|
141
|
-
class_name 'ConsumerTest::MyConsumer'
|
|
142
|
-
delivery :inline_batch
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
}.to raise_error('BatchConsumer ConsumerTest::MyConsumer does not implement `consume_batch`')
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
it 'should raise an error if Consumers do not have message or batch delivery' do
|
|
149
|
-
expect {
|
|
150
|
-
described_class.configure do
|
|
151
|
-
consumer do
|
|
152
|
-
class_name 'ConsumerTest::MyBatchConsumer'
|
|
153
|
-
delivery :message
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
}.to raise_error('Non-batch Consumer ConsumerTest::MyBatchConsumer does not implement `consume`')
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
it 'should treat nil as `batch`' do
|
|
160
|
-
expect {
|
|
161
|
-
described_class.configure do
|
|
162
|
-
consumer do
|
|
163
|
-
class_name 'ConsumerTest::MyConsumer'
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
}.not_to raise_error
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
53
|
end
|
data/spec/kafka_source_spec.rb
CHANGED
|
@@ -17,18 +17,10 @@ module KafkaSourceSpec
|
|
|
17
17
|
|
|
18
18
|
# Dummy producer which mimicks the behavior of a real producer
|
|
19
19
|
class WidgetProducer < Deimos::ActiveRecordProducer
|
|
20
|
-
topic 'my-topic'
|
|
21
|
-
namespace 'com.my-namespace'
|
|
22
|
-
schema 'Widget'
|
|
23
|
-
key_config field: :id
|
|
24
20
|
end
|
|
25
21
|
|
|
26
22
|
# Dummy producer which mimicks the behavior of a real producer
|
|
27
23
|
class WidgetProducerTheSecond < Deimos::ActiveRecordProducer
|
|
28
|
-
topic 'my-topic-the-second'
|
|
29
|
-
namespace 'com.my-namespace'
|
|
30
|
-
schema 'WidgetTheSecond'
|
|
31
|
-
key_config field: :id
|
|
32
24
|
end
|
|
33
25
|
|
|
34
26
|
# Dummy class we can include the mixin in. Has a backing table created
|
|
@@ -51,6 +43,22 @@ module KafkaSourceSpec
|
|
|
51
43
|
|
|
52
44
|
before(:each) do
|
|
53
45
|
Widget.delete_all
|
|
46
|
+
Karafka::App.routes.redraw do
|
|
47
|
+
topic 'my-topic' do
|
|
48
|
+
namespace 'com.my-namespace'
|
|
49
|
+
schema 'Widget'
|
|
50
|
+
key_config field: :id
|
|
51
|
+
producer_class WidgetProducer
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
topic 'my-topic-the-second' do
|
|
55
|
+
namespace 'com.my-namespace'
|
|
56
|
+
schema 'WidgetTheSecond'
|
|
57
|
+
key_config field: :id
|
|
58
|
+
producer_class WidgetProducerTheSecond
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
54
62
|
end
|
|
55
63
|
|
|
56
64
|
it 'should send events on creation, update, and deletion' do
|
|
@@ -206,10 +214,9 @@ module KafkaSourceSpec
|
|
|
206
214
|
context 'with DB backend' do
|
|
207
215
|
before(:each) do
|
|
208
216
|
Deimos.configure do |config|
|
|
209
|
-
config.producers.backend = :
|
|
217
|
+
config.producers.backend = :outbox
|
|
210
218
|
end
|
|
211
219
|
setup_db(DB_OPTIONS.last) # sqlite
|
|
212
|
-
allow(Deimos::Producer).to receive(:produce_batch).and_call_original
|
|
213
220
|
end
|
|
214
221
|
|
|
215
222
|
it 'should save to the DB' do
|
|
@@ -309,46 +316,6 @@ module KafkaSourceSpec
|
|
|
309
316
|
end
|
|
310
317
|
end
|
|
311
318
|
|
|
312
|
-
context 'with AR models that implement the kafka_producer interface' do
|
|
313
|
-
before(:each) do
|
|
314
|
-
# Dummy class we can include the mixin in. Has a backing table created
|
|
315
|
-
# earlier and has the import hook disabled
|
|
316
|
-
deprecated_class = Class.new(ActiveRecord::Base) do
|
|
317
|
-
include Deimos::KafkaSource
|
|
318
|
-
self.table_name = 'widgets'
|
|
319
|
-
|
|
320
|
-
# :nodoc:
|
|
321
|
-
def self.kafka_config
|
|
322
|
-
{
|
|
323
|
-
update: true,
|
|
324
|
-
delete: true,
|
|
325
|
-
import: false,
|
|
326
|
-
create: true
|
|
327
|
-
}
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# :nodoc:
|
|
331
|
-
def self.kafka_producer
|
|
332
|
-
WidgetProducer
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
stub_const('WidgetDeprecated', deprecated_class)
|
|
336
|
-
WidgetDeprecated.reset_column_information
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
it 'logs a warning and sends the message as usual' do
|
|
340
|
-
expect(Deimos.config.logger).to receive(:warn).with({ message: WidgetDeprecated::DEPRECATION_WARNING })
|
|
341
|
-
widget = WidgetDeprecated.create(widget_id: 1, name: 'Widget 1')
|
|
342
|
-
expect('my-topic').to have_sent({
|
|
343
|
-
widget_id: 1,
|
|
344
|
-
name: 'Widget 1',
|
|
345
|
-
id: widget.id,
|
|
346
|
-
created_at: anything,
|
|
347
|
-
updated_at: anything
|
|
348
|
-
}, widget.id)
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
|
|
352
319
|
context 'with AR models that do not implement any producer interface' do
|
|
353
320
|
before(:each) do
|
|
354
321
|
# Dummy class we can include the mixin in. Has a backing table created
|
|
@@ -371,10 +338,10 @@ module KafkaSourceSpec
|
|
|
371
338
|
WidgetBuggy.reset_column_information
|
|
372
339
|
end
|
|
373
340
|
|
|
374
|
-
it 'raises a
|
|
341
|
+
it 'raises a MissingImplementationError exception' do
|
|
375
342
|
expect {
|
|
376
343
|
WidgetBuggy.create(widget_id: 1, name: 'Widget 1')
|
|
377
|
-
}.to raise_error(
|
|
344
|
+
}.to raise_error(Deimos::MissingImplementationError)
|
|
378
345
|
end
|
|
379
346
|
end
|
|
380
347
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
class KarafkaApp < Karafka::App
|
|
3
|
+
setup do |config|
|
|
4
|
+
config.kafka = { 'bootstrap.servers': '127.0.0.1:9092' }
|
|
5
|
+
config.client_id = 'example_app'
|
|
6
|
+
# Recreate consumers with each batch. This will allow Rails code reload to work in the
|
|
7
|
+
# development mode. Otherwise Karafka process would not be aware of code changes
|
|
8
|
+
config.consumer_persistence = !Rails.env.development?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Comment out this part if you are not using instrumentation and/or you are not
|
|
12
|
+
# interested in logging events for certain environments. Since instrumentation
|
|
13
|
+
# notifications add extra boilerplate, if you want to achieve max performance,
|
|
14
|
+
# listen to only what you really need for given environment.
|
|
15
|
+
Karafka.monitor.subscribe(Karafka::Instrumentation::LoggerListener.new)
|
|
16
|
+
# Karafka.monitor.subscribe(Karafka::Instrumentation::ProctitleListener.new)
|
|
17
|
+
|
|
18
|
+
# This logger prints the producer development info using the Karafka logger.
|
|
19
|
+
# It is similar to the consumer logger listener but producer oriented.
|
|
20
|
+
Karafka.producer.monitor.subscribe(
|
|
21
|
+
WaterDrop::Instrumentation::LoggerListener.new(
|
|
22
|
+
# Log producer operations using the Karafka logger
|
|
23
|
+
Karafka.logger,
|
|
24
|
+
# If you set this to true, logs will contain each message details
|
|
25
|
+
# Please note, that this can be extensive
|
|
26
|
+
log_messages: false
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# You can subscribe to all consumer related errors and record/track then that way
|
|
31
|
+
#
|
|
32
|
+
# Karafka.monitor.subscribe 'error.occurred' do |event|
|
|
33
|
+
# type = event[:type]
|
|
34
|
+
# error = event[:error]
|
|
35
|
+
# details = (error.backtrace || []).join("\n")
|
|
36
|
+
# ErrorTracker.send_error(error, type, details)
|
|
37
|
+
# end
|
|
38
|
+
|
|
39
|
+
# You can subscribe to all producer related errors and record/track then that way
|
|
40
|
+
# Please note, that producer and consumer have their own notifications pipeline so you need to
|
|
41
|
+
# setup error tracking independently for each of them
|
|
42
|
+
#
|
|
43
|
+
# Karafka.producer.monitor.subscribe('error.occurred') do |event|
|
|
44
|
+
# type = event[:type]
|
|
45
|
+
# error = event[:error]
|
|
46
|
+
# details = (error.backtrace || []).join("\n")
|
|
47
|
+
# ErrorTracker.send_error(error, type, details)
|
|
48
|
+
# end
|
|
49
|
+
|
|
50
|
+
routes.draw do
|
|
51
|
+
# Uncomment this if you use Karafka with ActiveJob
|
|
52
|
+
# You need to define the topic per each queue name you use
|
|
53
|
+
# active_job_topic :default
|
|
54
|
+
# topic :example do
|
|
55
|
+
# Uncomment this if you want Karafka to manage your topics configuration
|
|
56
|
+
# Managing topics configuration via routing will allow you to ensure config consistency
|
|
57
|
+
# across multiple environments
|
|
58
|
+
#
|
|
59
|
+
# config(partitions: 2, 'cleanup.policy': 'compact')
|
|
60
|
+
# consumer ExampleConsumer
|
|
61
|
+
# end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Karafka now features a Web UI!
|
|
66
|
+
# Visit the setup documentation to get started and enhance your experience.
|
|
67
|
+
#
|
|
68
|
+
# https://karafka.io/docs/Web-UI-Getting-Started
|
|
69
|
+
Deimos.setup_karafka
|