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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +0 -17
  3. data/.tool-versions +1 -0
  4. data/CHANGELOG.md +1 -1
  5. data/README.md +287 -498
  6. data/deimos-ruby.gemspec +4 -4
  7. data/docs/CONFIGURATION.md +133 -227
  8. data/docs/UPGRADING.md +237 -0
  9. data/lib/deimos/active_record_consume/batch_consumption.rb +28 -29
  10. data/lib/deimos/active_record_consume/message_consumption.rb +15 -21
  11. data/lib/deimos/active_record_consumer.rb +36 -26
  12. data/lib/deimos/active_record_producer.rb +28 -9
  13. data/lib/deimos/backends/base.rb +4 -35
  14. data/lib/deimos/backends/kafka.rb +6 -22
  15. data/lib/deimos/backends/kafka_async.rb +6 -22
  16. data/lib/deimos/backends/{db.rb → outbox.rb} +13 -9
  17. data/lib/deimos/config/configuration.rb +116 -385
  18. data/lib/deimos/consume/batch_consumption.rb +24 -124
  19. data/lib/deimos/consume/message_consumption.rb +36 -63
  20. data/lib/deimos/consumer.rb +16 -75
  21. data/lib/deimos/ext/consumer_route.rb +35 -0
  22. data/lib/deimos/ext/producer_middleware.rb +94 -0
  23. data/lib/deimos/ext/producer_route.rb +22 -0
  24. data/lib/deimos/ext/redraw.rb +29 -0
  25. data/lib/deimos/ext/routing_defaults.rb +72 -0
  26. data/lib/deimos/ext/schema_route.rb +70 -0
  27. data/lib/deimos/kafka_message.rb +2 -2
  28. data/lib/deimos/kafka_source.rb +2 -7
  29. data/lib/deimos/kafka_topic_info.rb +1 -1
  30. data/lib/deimos/logging.rb +71 -0
  31. data/lib/deimos/message.rb +2 -11
  32. data/lib/deimos/metrics/datadog.rb +40 -1
  33. data/lib/deimos/metrics/provider.rb +4 -4
  34. data/lib/deimos/producer.rb +39 -116
  35. data/lib/deimos/railtie.rb +6 -0
  36. data/lib/deimos/schema_backends/avro_base.rb +21 -21
  37. data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -2
  38. data/lib/deimos/schema_backends/avro_validation.rb +2 -2
  39. data/lib/deimos/schema_backends/base.rb +19 -12
  40. data/lib/deimos/schema_backends/mock.rb +6 -1
  41. data/lib/deimos/schema_backends/plain.rb +47 -0
  42. data/lib/deimos/schema_class/base.rb +2 -2
  43. data/lib/deimos/schema_class/enum.rb +1 -1
  44. data/lib/deimos/schema_class/record.rb +2 -2
  45. data/lib/deimos/test_helpers.rb +95 -320
  46. data/lib/deimos/tracing/provider.rb +6 -6
  47. data/lib/deimos/transcoder.rb +88 -0
  48. data/lib/deimos/utils/db_poller/base.rb +16 -14
  49. data/lib/deimos/utils/db_poller/state_based.rb +3 -3
  50. data/lib/deimos/utils/db_poller/time_based.rb +4 -4
  51. data/lib/deimos/utils/db_poller.rb +1 -1
  52. data/lib/deimos/utils/deadlock_retry.rb +1 -1
  53. data/lib/deimos/utils/{db_producer.rb → outbox_producer.rb} +16 -47
  54. data/lib/deimos/utils/schema_class.rb +0 -7
  55. data/lib/deimos/version.rb +1 -1
  56. data/lib/deimos.rb +79 -26
  57. data/lib/generators/deimos/{db_backend_generator.rb → outbox_backend_generator.rb} +4 -4
  58. data/lib/generators/deimos/schema_class_generator.rb +0 -1
  59. data/lib/generators/deimos/v2/templates/karafka.rb.tt +149 -0
  60. data/lib/generators/deimos/v2_generator.rb +193 -0
  61. data/lib/tasks/deimos.rake +5 -7
  62. data/spec/active_record_batch_consumer_association_spec.rb +22 -13
  63. data/spec/active_record_batch_consumer_spec.rb +84 -65
  64. data/spec/active_record_consume/batch_consumption_spec.rb +10 -10
  65. data/spec/active_record_consume/batch_slicer_spec.rb +12 -12
  66. data/spec/active_record_consumer_spec.rb +29 -13
  67. data/spec/active_record_producer_spec.rb +36 -26
  68. data/spec/backends/base_spec.rb +0 -23
  69. data/spec/backends/kafka_async_spec.rb +1 -3
  70. data/spec/backends/kafka_spec.rb +1 -3
  71. data/spec/backends/{db_spec.rb → outbox_spec.rb} +14 -20
  72. data/spec/batch_consumer_spec.rb +66 -116
  73. data/spec/consumer_spec.rb +53 -147
  74. data/spec/deimos_spec.rb +10 -126
  75. data/spec/kafka_source_spec.rb +19 -52
  76. data/spec/karafka/karafka.rb +69 -0
  77. data/spec/karafka_config/karafka_spec.rb +97 -0
  78. data/spec/logging_spec.rb +25 -0
  79. data/spec/message_spec.rb +9 -9
  80. data/spec/producer_spec.rb +112 -254
  81. data/spec/rake_spec.rb +1 -3
  82. data/spec/schema_backends/avro_validation_spec.rb +1 -1
  83. data/spec/schemas/com/my-namespace/MySchemaWithTitle.avsc +22 -0
  84. data/spec/snapshots/consumers-no-nest.snap +49 -0
  85. data/spec/snapshots/consumers.snap +49 -0
  86. data/spec/snapshots/consumers_and_producers-no-nest.snap +49 -0
  87. data/spec/snapshots/consumers_and_producers.snap +49 -0
  88. data/spec/snapshots/consumers_circular-no-nest.snap +49 -0
  89. data/spec/snapshots/consumers_circular.snap +49 -0
  90. data/spec/snapshots/consumers_complex_types-no-nest.snap +49 -0
  91. data/spec/snapshots/consumers_complex_types.snap +49 -0
  92. data/spec/snapshots/consumers_nested-no-nest.snap +49 -0
  93. data/spec/snapshots/consumers_nested.snap +49 -0
  94. data/spec/snapshots/namespace_folders.snap +49 -0
  95. data/spec/snapshots/namespace_map.snap +49 -0
  96. data/spec/snapshots/producers_with_key-no-nest.snap +49 -0
  97. data/spec/snapshots/producers_with_key.snap +49 -0
  98. data/spec/spec_helper.rb +61 -29
  99. data/spec/utils/db_poller_spec.rb +49 -39
  100. data/spec/utils/{db_producer_spec.rb → outbox_producer_spec.rb} +17 -184
  101. metadata +58 -67
  102. data/lib/deimos/batch_consumer.rb +0 -7
  103. data/lib/deimos/config/phobos_config.rb +0 -164
  104. data/lib/deimos/instrumentation.rb +0 -95
  105. data/lib/deimos/monkey_patches/phobos_cli.rb +0 -35
  106. data/lib/deimos/utils/inline_consumer.rb +0 -158
  107. data/lib/deimos/utils/lag_reporter.rb +0 -186
  108. data/lib/deimos/utils/schema_controller_mixin.rb +0 -129
  109. data/spec/config/configuration_spec.rb +0 -329
  110. data/spec/kafka_listener_spec.rb +0 -55
  111. data/spec/phobos.bad_db.yml +0 -73
  112. data/spec/phobos.yml +0 -77
  113. data/spec/utils/inline_consumer_spec.rb +0 -31
  114. data/spec/utils/lag_reporter_spec.rb +0 -76
  115. data/spec/utils/platform_schema_validation_spec.rb +0 -0
  116. data/spec/utils/schema_controller_mixin_spec.rb +0 -84
  117. /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/migration +0 -0
  118. /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/rails3_migration +0 -0
@@ -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, payload, _metadata)
16
- payload.to_s == 'fatal'
14
+ def fatal_error?(_exception, messages)
15
+ messages.payloads.first&.dig(:test_id) == ['fatal']
17
16
  end
18
17
 
19
18
  # :nodoc:
20
- def consume(_payload, _metadata)
21
- raise 'This should not be called unless call_original is set'
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 |payload, _metadata|
49
- expect(payload).to be_nil
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
- test_consume_invalid_message(MyConsumer, { 'invalid' => 'key' })
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
- Deimos.configure { |config| config.consumers.reraise_errors = false }
85
- test_consume_invalid_message(MyConsumer, 'fatal')
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
- Deimos.configure do |config|
90
- config.consumers.fatal_error = proc { true }
91
- config.consumers.reraise_errors = false
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
- test_consume_invalid_message(MyConsumer,
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
- Deimos.configure { |config| config.consumers.reraise_errors = false }
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
- Deimos.configure { |config| config.consumers.reraise_errors = false }
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 consume(_payload, _metadata)
153
- raise 'This should not be called unless call_original is set'
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
- it 'should error if required_acks is not all' do
61
- expect {
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 = :db
20
+ config.producers.backend = :outbox
81
21
  end
82
- described_class.start_db_backend!(thread_count: 2)
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.start_db_backend!(thread_count: 2) }.
91
- to raise_error('Publish backend is not set to :db, exiting')
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 = :db
37
+ config.producers.backend = :outbox
98
38
  end
99
- expect { described_class.start_db_backend!(thread_count: nil) }.
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 = :db
46
+ config.producers.backend = :outbox
107
47
  end
108
- expect { described_class.start_db_backend!(thread_count: 0) }.
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
@@ -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 = :db
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 NotImplementedError exception' do
341
+ it 'raises a MissingImplementationError exception' do
375
342
  expect {
376
343
  WidgetBuggy.create(widget_id: 1, name: 'Widget 1')
377
- }.to raise_error(NotImplementedError)
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