deimos-ruby 1.24.3 → 2.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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
data/docs/UPGRADING.md CHANGED
@@ -1,5 +1,242 @@
1
1
  # Upgrading Deimos
2
2
 
3
+ ## Upgrading to 2.x
4
+
5
+ 2.x is a major rewrite from 1.0. The underlying library has been changed from [Phobos](https://github.com/phobos/phobos) to [Karafka](https://karafka.io/). This change has given us an opportunity to fix some issues and deprecated code paths from version 1.0 as well as provide much more functionality by integrating more fully with the Karafka ecosystem.
6
+
7
+ For a deeper dive into the internal changes, please see [...]().
8
+
9
+ There are a number of breaking changes. We provide a `v2` generator to attempt to auto-fix many of these breaking changes automatically. To run the generator:
10
+
11
+ KARAFKA_BOOT_FILE=false rails g deimos:v2
12
+
13
+ ### Running Deimos
14
+
15
+ Instead of running `rake deimos:start`, you now run your Kafka consumers the same way any Karafka consumers are run: `karafka server`.
16
+
17
+ ### Configuration
18
+
19
+ In V1, Deimos configuration was all done in a single `Deimos.configure` block, including Kafka configs, consumers and producers:
20
+
21
+ ```ruby
22
+ Deimos.configure do
23
+ producers.schema_namespace 'com.my-namespace'
24
+ kafka.seed_brokers ['my-broker:9092']
25
+
26
+ consumer do
27
+ class_name 'MyConsumer'
28
+ topic 'MyTopic'
29
+ session_timeout 30
30
+ schema 'MySchema'
31
+ key_config field: :id
32
+ namespace 'com.my-namespace'
33
+ end
34
+
35
+ producer do
36
+ class_name 'MyProducer'
37
+ topic 'MyTopic2'
38
+ schema 'MySchema2'
39
+ key_config none: true
40
+ end
41
+ end
42
+ ```
43
+
44
+ In V2, the `Deimos.configure` block now only takes Deimos-specific settings, and is **not** used to configure producers and consumers. Kafka settings now go in the Karafka `kafka` setting method, and producers and consumers use Karafka [routing](https://karafka.io/docs/Routing/). There are Deimos-specific extensions to routing to apply to consumers and producers, either via a `defaults` block (applying to all consumers and producers) or in individual `topic` blocks:
45
+
46
+ ```ruby
47
+ Deimos.configure do
48
+ producers.schema_namespace 'com.my-namespace'
49
+ end
50
+
51
+ class KarafkaApp < Karafka::App
52
+ setup do |config|
53
+ config.kafka = {
54
+ "bootstrap.servers": "my-broker:9092"
55
+ }
56
+ end
57
+
58
+ routes.draw do
59
+ defaults do
60
+ namespace "com.my-namespace"
61
+ end
62
+
63
+ topic "MyTopic" do
64
+ # Karafka settings
65
+ consumer MyConsumer
66
+ kafka({"session.timeout.ms": 30_000})
67
+ # Deimos settings
68
+ schema "MySchema" # the res
69
+ key_config({field: id})
70
+ end
71
+
72
+ topic "MyTopic2" do
73
+ # these are all Deimos settings since Karafka doesn't actually do per-topic producer configs
74
+ producer_class MyProducer
75
+ schema 'MySchema2'
76
+ key_config none: true
77
+ end
78
+ end
79
+ end
80
+ ```
81
+
82
+ This configuration must be in a file called `karafka.rb` at the root of your application. The V2 generator will generate this file for you. Without the generator, if you have this file and start up your app with the old `Deimos.configure` code, you will get notifications of the correct places to put these settings.
83
+
84
+
85
+ ### Removed deprecations
86
+
87
+ The following were deprecated in version 1.x and are removed in 2.0.
88
+
89
+ * The `kafka_producer` method for KafkaSource is no longer supported. Please use `kafka_producers`. (This is not addressed by the V2 generator.)
90
+
91
+ ```ruby
92
+ # before:
93
+ class MyRecord < ApplicationRecord
94
+ def kafka_producer
95
+ MyProducer
96
+ end
97
+ end
98
+
99
+ # after:
100
+ class MyRecord < ApplicationRecord
101
+ def kafka_producers
102
+ [MyProducer]
103
+ end
104
+ end
105
+ ```
106
+
107
+ * The `record_attributes` method for ActiveRecordConsumer now must take two parameters, not one. (The V2 generator can fix this.)
108
+
109
+ ```ruby
110
+ # before:
111
+ class MyConsumer < Deimos::ActiveRecordConsumer
112
+ def record_attributes(payload)
113
+ # ...
114
+ end
115
+ end
116
+
117
+ # after:
118
+ class MyConsumer < Deimos::ActiveRecordConsumer
119
+ def record_attributes(payload, key)
120
+ # ...
121
+ end
122
+ end
123
+ ```
124
+
125
+ * The `BatchConsumer` class has been removed. Please use the `Consumer` class.
126
+ * You can no longer configure your application using a `phobos.yml` file. The V2 generator will not be able to work on apps using this approach.
127
+ * Removed `test_consume_invalid_message` and `test_consume_batch_invalid_message` test helpers. These did not serve a useful purpose.
128
+ * The following deprecated testing functions have been removed: `stub_producers_and_consumers!`, `stub_producer`, `stub_consumer`, `stub_batch_consumer`. These have not done anything in a long time.
129
+
130
+ ### Major breaking changes
131
+ * Since Karafka only supports Ruby >= 3.0, that means Deimos also only supports those versions.
132
+ * Deimos no longer supports a separate logger from Karafka. When you configure a Karafka logger, Deimos will use that logger for all its logging. (Deimos logs will be prefixed with a `[Deimos]` tag.)
133
+ * The `:db` backend has been renamed to `:outbox`. All associated classes (like `DbProducer`) have likewise been renamed. The Rake task has also been renamed to `rake deimos:outbox`.
134
+ * The `SchemaControllerMixin` has been removed as there was no serious usage for it.
135
+ * `InlineConsumer` has been removed - Karafka Pro has an [Iterator API](https://karafka.io/docs/Pro-Iterator-API/) that does the same thing. There also has been no evidence that it was used (and was probably pretty buggy).
136
+ * The `:test` backend has been removed and the `Deimos::TestHelpers` module is now largely powered by [karafka-testing](https://github.com/karafka/karafka-testing/). This means that you can no longer use `Deimos::Backends::Test.sent_messages` - you need to use `Deimos::TestHelpers.sent_messages`. (The V2 generator should fix this.)
137
+ * Individual consumer and producer settings now live within Karafka route configuration. This means you can no longer call e.g. `consumer.schema` to retrieve this information, as settings are no longer stored directly on the consumer and producer objects (it is still available, but via different methods).
138
+ * Consumers should no longer define a `consume` method, as the semantics have changed with Karafka. Instead, you can define a `consume_message` or `consume_batch` method. Both of these methods now take Karafka `Message` objects instead of hashes. The V2 generator can handle translating this for you, but if you create new consumers, you should take advantage of the Karafka functionality and use it first-class.
139
+ * Phobos `delivery_method` is no longer relevant. Instead, specify an `each_message` setting for your consumer. If set to true, you should define a `consume_message` method. Otherwise, you should define a `consume_batch` method. (Note that this is the reverse from the previous default, which assumed `delivery_method: message`.) The V2 generator will create the correct setting for each consumer.
140
+
141
+ ```ruby
142
+ # before:
143
+ class MyConsumer < Deimos::Consumer
144
+ def consume(payload, metadata)
145
+ # payload and metadata are both hashes
146
+ end
147
+
148
+ # OR with delivery_method: inline_batch
149
+ def batch_consume(payloads, metadata)
150
+ # payloads is an array of hashes, metadata is a hash
151
+ end
152
+ end
153
+
154
+ # now:
155
+ class MyConsumer < Deimos::Consumer
156
+ def consume_batch
157
+ payloads = messages.payloads # messages is an instance method and `payloads` will return the decoded hashes
158
+ end
159
+
160
+ # OR with batch(false)
161
+ def consume_message(message)
162
+ # message is a Karafka Message object
163
+ payload = message.payload
164
+ key = message.key # etc.
165
+ end
166
+ end
167
+ ```
168
+
169
+ ### Metrics
170
+
171
+ The following metrics have been **removed** in favor of Karafka's more robust [DataDog metrics](https://karafka.io/docs/Monitoring-and-Logging/#datadog-and-statsd-integration) and WaterDrop's [DataDog metrics](https://karafka.io/docs/WaterDrop-Monitoring-and-Logging/#datadog-and-statsd-integration):
172
+ * `consumer_lag` (use `consumer.lags`)
173
+ * `handler` (use `consumer.consumed.time_taken`)
174
+ * `publish` (use `produced_sync` and `produced_async`)
175
+ * `publish_error` (use `deliver.errors`)
176
+
177
+ You will need to manually add the DataDog MetricsListener as shown in the above pages.
178
+
179
+ The following metrics have been **renamed**:
180
+
181
+ * `db_producer.insert` -> `outbox.insert`
182
+ * `db_producer.process` -> `outbox.process`
183
+
184
+ ### Instrumentation
185
+
186
+ Deimos's own instrumentation layer has been removed in favor of Karafka's. You can still subscribe to Deimos notifications - you simply do it via Karafka's monitor instead of Deimos's.
187
+
188
+ ```ruby
189
+ # before:
190
+ Deimos.subscribe('encode_messages') do |event|
191
+ # ...
192
+ end
193
+
194
+ # after:
195
+ Karafka.monitor.subscribe('deimos.encode_messages') do |event|
196
+ # ...
197
+ end
198
+ ```
199
+
200
+ Note that Karafka's monitors do not support the legacy "splatted" subscribe:
201
+ ```ruby
202
+ Deimos.subscribe("event") do |*args|
203
+ payload = ActiveSupport::Notifications::Event.new(*args).payload
204
+ end
205
+ ```
206
+
207
+ The following instrumentation events have been **removed** in favor of Karafka's [events](https://karafka.io/docs/Monitoring-and-Logging/#subscribing-to-the-instrumentation-events):
208
+
209
+ * `produce_error` (use `error.occurred`)
210
+
211
+ The following events have been **renamed**:
212
+ * `encode_messages` -> `deimos.encode_message` (**note that only one message is instrumented at a time now**)
213
+ * `db_producer.produce` -> `deimos.outbox.produce`
214
+ * `batch_consumption.valid_records` -> `deimos.batch_consumption.valid_records`
215
+ * `batch_consumption.invalid_records` -> `deimos.batch_consumption.invalid_records`
216
+
217
+ ### Additional breaking changes
218
+ * `key_config` now defaults to `{none: true}` instead of erroring out if not set.
219
+ * `fatal_error?` now receives a Karafka `messages` object instead of a payload hash or array of hashes.
220
+ * `watched_attributes` has been moved from the corresponding ActiveRecord class to the ActiveRecordProducer class. The object being watched is passed into the method.
221
+ * Removed `TestHelpers.full_integration_test!` and `kafka_test!` as Karafka does not currently support these use cases. If we need them back, we will need to put in changes to the testing library to support them.
222
+ * `test_consume_message` and `test_consume_batch` used to not fully validate schemas when using the `:avro_validation` backend. Now these are fully validated, which may cause test errors when upgrading.
223
+
224
+ ### New functionality
225
+
226
+ * When setting up a Datadog metrics client, you can pass `:karafka_namespace`, `:karafka_distribution_mode`, or `:rd_kafka_metrics` tags to specify the Karafka settings for Datadog metrics.
227
+ - The `payload_log` setting now works for consumers as well as producers, as it is now a topic setting.
228
+ - You can publish messages **without a Deimos Producer class**. Karafka producers take a hash with `:message`, `:topic`, `:key`, `:headers` and `:partition_key` keys. As long as the topic is configured in `karafka.rb`, you don't need a special class to send the message. You can simply call `Karafka.producer.produce()`.
229
+ - The only features that are now available on the bare Producer (as opposed to ActiveRecordProducer) class are:
230
+ - Outbox backend
231
+ - Instance method to determine partition key (rather than passing it in)
232
+ - Using `Deimos.disable_producers`
233
+ - If you need these features, you must continue using a `Deimos::Producer`.
234
+ - You can now call `.produce(messages)` directly on a `Deimos::Producer` which allows for use of these features while still passing a Karafka message hash. This removes the need to add a `payload_key` key into your payload. This is now the recommended method to use in a Deimos Producer.
235
+
236
+ ### New deprecations
237
+ * For testing, you no longer have to call `unit_test!` to get the right settings. It is handled automatically by Karafka. The only thing this method now does is set the schema backend to `:avro_validation`, and you can do that in a single line.
238
+ * The `skip_expectation` and `call_original` arguments to `test_consume_message` and `test_consume_batch` have been deprecated and no longer need to be provided. The assumption is that `call_original` is always true.
239
+
3
240
  ## Upgrading from < 1.5.0 to >= 1.5.0
4
241
 
5
242
  If you are using Confluent's schema registry to Avro-encode your
@@ -4,6 +4,7 @@ require 'deimos/active_record_consume/batch_slicer'
4
4
  require 'deimos/active_record_consume/batch_record'
5
5
  require 'deimos/active_record_consume/batch_record_list'
6
6
  require 'deimos/active_record_consume/mass_updater'
7
+ require 'deimos/consume/batch_consumption'
7
8
 
8
9
  require 'deimos/utils/deadlock_retry'
9
10
  require 'deimos/message'
@@ -14,28 +15,26 @@ module Deimos
14
15
  # Methods for consuming batches of messages and saving them to the database
15
16
  # in bulk ActiveRecord operations.
16
17
  module BatchConsumption
18
+ include Deimos::Consume::BatchConsumption
19
+
17
20
  # Handle a batch of Kafka messages. Batches are split into "slices",
18
21
  # which are groups of independent messages that can be processed together
19
22
  # in a single database operation.
20
23
  # If two messages in a batch have the same key, we cannot process them
21
24
  # in the same operation as they would interfere with each other. Thus
22
25
  # they are split
23
- # @param payloads [Array<Hash,Deimos::SchemaClass::Record>] Decoded payloads
24
- # @param metadata [Hash] Information about batch, including keys.
25
26
  # @return [void]
26
- def consume_batch(payloads, metadata)
27
- messages = payloads.
28
- zip(metadata[:keys]).
29
- map { |p, k| Deimos::Message.new(p, nil, key: k) }
27
+ def consume_batch
28
+ deimos_messages = messages.map { |p| Deimos::Message.new(p.payload, key: p.key) }
30
29
 
31
- tag = metadata[:topic]
30
+ tag = topic.name
32
31
  Deimos.config.tracer.active_span.set_tag('topic', tag)
33
32
 
34
- Deimos.instrument('ar_consumer.consume_batch', tag) do
35
- if @compacted || self.class.config[:no_keys]
36
- update_database(compact_messages(messages))
33
+ Karafka.monitor.instrument('deimos.ar_consumer.consume_batch', {topic: tag}) do
34
+ if @compacted && deimos_messages.map(&:key).compact.any?
35
+ update_database(compact_messages(deimos_messages))
37
36
  else
38
- uncompacted_update(messages)
37
+ uncompacted_update(deimos_messages)
39
38
  end
40
39
  end
41
40
  end
@@ -68,11 +67,11 @@ module Deimos
68
67
  if key.nil?
69
68
  {}
70
69
  elsif key.is_a?(Hash)
71
- @key_converter.convert(key)
72
- elsif self.class.config[:key_field].nil?
70
+ self.key_converter.convert(key)
71
+ elsif self.topic.key_config[:field].nil?
73
72
  { @klass.primary_key => key }
74
73
  else
75
- { self.class.config[:key_field] => key }
74
+ { self.topic.key_config[:field].to_s => key }
76
75
  end
77
76
  end
78
77
 
@@ -154,10 +153,10 @@ module Deimos
154
153
  record_list = build_records(messages)
155
154
  invalid = filter_records(record_list)
156
155
  if invalid.any?
157
- ActiveSupport::Notifications.instrument('batch_consumption.invalid_records', {
158
- records: invalid,
159
- consumer: self.class
160
- })
156
+ Karafka.monitor.instrument('deimos.batch_consumption.invalid_records', {
157
+ records: invalid,
158
+ consumer: self.class
159
+ })
161
160
  end
162
161
  return if record_list.empty?
163
162
 
@@ -167,14 +166,14 @@ module Deimos
167
166
  updater = MassUpdater.new(@klass,
168
167
  key_col_proc: key_col_proc,
169
168
  col_proc: col_proc,
170
- replace_associations: self.class.replace_associations,
171
- bulk_import_id_generator: self.class.bulk_import_id_generator,
172
- save_associations_first: self.class.save_associations_first,
173
- bulk_import_id_column: self.class.bulk_import_id_column)
174
- ActiveSupport::Notifications.instrument('batch_consumption.valid_records', {
175
- records: updater.mass_update(record_list),
176
- consumer: self.class
177
- })
169
+ replace_associations: self.replace_associations,
170
+ bulk_import_id_generator: self.bulk_import_id_generator,
171
+ save_associations_first: self.save_associations_first,
172
+ bulk_import_id_column: self.bulk_import_id_column)
173
+ Karafka.monitor.instrument('deimos.batch_consumption.valid_records', {
174
+ records: updater.mass_update(record_list),
175
+ consumer: self.class
176
+ })
178
177
  end
179
178
 
180
179
  # @param record_list [BatchRecordList]
@@ -205,14 +204,14 @@ module Deimos
205
204
  attrs = attrs.merge(record_key(m.key))
206
205
  next unless attrs
207
206
 
208
- col = if @klass.column_names.include?(self.class.bulk_import_id_column.to_s)
209
- self.class.bulk_import_id_column
207
+ col = if @klass.column_names.include?(self.bulk_import_id_column.to_s)
208
+ self.bulk_import_id_column
210
209
  end
211
210
 
212
211
  BatchRecord.new(klass: @klass,
213
212
  attributes: attrs,
214
213
  bulk_import_column: col,
215
- bulk_import_id_generator: self.class.bulk_import_id_generator)
214
+ bulk_import_id_generator: self.bulk_import_id_generator)
216
215
  end
217
216
  BatchRecordList.new(records.compact)
218
217
  end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'deimos/consume/message_consumption'
4
+
3
5
  module Deimos
4
6
  module ActiveRecordConsume
5
7
  # Methods for consuming individual messages and saving them to the database
6
8
  # as ActiveRecord instances.
7
9
  module MessageConsumption
10
+ include Deimos::Consume::MessageConsumption
8
11
  # Find the record specified by the given payload and key.
9
12
  # Default is to use the primary key column and the value of the first
10
13
  # field in the key.
@@ -26,38 +29,29 @@ module Deimos
26
29
  record[record.class.primary_key] = key
27
30
  end
28
31
 
29
- # @param payload [Hash,Deimos::SchemaClass::Record] Decoded payloads
30
- # @param metadata [Hash] Information about batch, including keys.
31
- # @return [void]
32
- def consume(payload, metadata)
33
- unless self.process_message?(payload)
34
- Deimos.config.logger.debug(
35
- message: 'Skipping processing of message',
36
- payload: payload,
37
- metadata: metadata
38
- )
32
+ # @param message [Karafka::Messages::Message]
33
+ def consume_message(message)
34
+ unless self.process_message?(message)
35
+ Deimos::Logging.log_debug(
36
+ message: 'Skipping processing of message',
37
+ payload: message.payload.to_h,
38
+ metadata: Deimos::Logging.metadata_log_text(message.metadata)
39
+ )
39
40
  return
40
41
  end
41
42
 
42
- key = metadata.with_indifferent_access[:key]
43
43
  klass = self.class.config[:record_class]
44
- record = fetch_record(klass, (payload || {}).with_indifferent_access, key)
45
- if payload.nil?
44
+ record = fetch_record(klass, message.payload.to_h.with_indifferent_access, message.key)
45
+ if message.payload.nil?
46
46
  destroy_record(record)
47
47
  return
48
48
  end
49
49
  if record.blank?
50
50
  record = klass.new
51
- assign_key(record, payload, key)
51
+ assign_key(record, message.payload, message.key)
52
52
  end
53
53
 
54
- # for backwards compatibility
55
- # TODO next major release we should deprecate this
56
- attrs = if self.method(:record_attributes).parameters.size == 2
57
- record_attributes(payload.with_indifferent_access, key)
58
- else
59
- record_attributes(payload.with_indifferent_access)
60
- end
54
+ attrs = record_attributes((message.payload || {}).with_indifferent_access, message.key)
61
55
  # don't use attributes= - bypass Rails < 5 attr_protected
62
56
  attrs.each do |k, v|
63
57
  record.send("#{k}=", v)
@@ -30,26 +30,6 @@ module Deimos
30
30
  config[:record_class] = klass
31
31
  end
32
32
 
33
- # @return [String,nil]
34
- def bulk_import_id_column
35
- config[:bulk_import_id_column]
36
- end
37
-
38
- # @return [Proc]
39
- def bulk_import_id_generator
40
- config[:bulk_import_id_generator]
41
- end
42
-
43
- # @return [Boolean]
44
- def replace_associations
45
- config[:replace_associations]
46
- end
47
-
48
- # @return [Boolean]
49
- def save_associations_first
50
- config[:save_associations_first]
51
- end
52
-
53
33
  # @param val [Boolean] Turn pre-compaction of the batch on or off. If true,
54
34
  # only the last message for each unique key in a batch is processed.
55
35
  # @return [void]
@@ -62,18 +42,48 @@ module Deimos
62
42
  def max_db_batch_size(limit)
63
43
  config[:max_db_batch_size] = limit
64
44
  end
45
+
46
+ end
47
+
48
+ # @return [Boolean]
49
+ def replace_associations
50
+ self.topic.replace_associations
51
+ end
52
+
53
+ # @return [String,nil]
54
+ def bulk_import_id_column
55
+ self.topic.bulk_import_id_column
56
+ end
57
+
58
+ # @return [Proc]
59
+ def bulk_import_id_generator
60
+ topic.bulk_import_id_generator
61
+ end
62
+
63
+ # @return [Boolean]
64
+ def save_associations_first
65
+ topic.save_associations_first
66
+ end
67
+
68
+ def key_decoder
69
+ self.topic.serializers[:key]&.backend
65
70
  end
66
71
 
67
72
  # Setup
68
73
  def initialize
69
74
  @klass = self.class.config[:record_class]
70
- @converter = ActiveRecordConsume::SchemaModelConverter.new(self.class.decoder, @klass)
75
+ @compacted = self.class.config[:compacted] != false
76
+ end
71
77
 
72
- if self.class.config[:key_schema]
73
- @key_converter = ActiveRecordConsume::SchemaModelConverter.new(self.class.key_decoder, @klass)
74
- end
78
+ def converter
79
+ decoder = self.topic.deserializers[:payload].backend
80
+ @converter ||= ActiveRecordConsume::SchemaModelConverter.new(decoder, @klass)
81
+ end
75
82
 
76
- @compacted = self.class.config[:compacted] != false
83
+ def key_converter
84
+ decoder = self.topic.deserializers[:key]&.backend
85
+ return nil if decoder.nil?
86
+ @key_converter ||= ActiveRecordConsume::SchemaModelConverter.new(decoder, @klass)
77
87
  end
78
88
 
79
89
  # Override this method (with `super`) if you want to add/change the default
@@ -82,7 +92,7 @@ module Deimos
82
92
  # @param _key [String]
83
93
  # @return [Hash]
84
94
  def record_attributes(payload, _key=nil)
85
- @converter.convert(payload)
95
+ self.converter.convert(payload)
86
96
  end
87
97
 
88
98
  # Override this message to conditionally save records
@@ -18,9 +18,11 @@ module Deimos
18
18
  # a record object, refetch the record to pass into the `generate_payload`
19
19
  # method.
20
20
  # @return [void]
21
- def record_class(klass, refetch: true)
22
- config[:record_class] = klass
23
- config[:refetch_record] = refetch
21
+ def record_class(klass=nil, refetch: true)
22
+ return @record_class if klass.nil?
23
+
24
+ @record_class = klass
25
+ @refetch_record = refetch
24
26
  end
25
27
 
26
28
  # @param record [ActiveRecord::Base]
@@ -34,14 +36,14 @@ module Deimos
34
36
  # @param force_send [Boolean]
35
37
  # @return [void]
36
38
  def send_events(records, force_send: false)
37
- primary_key = config[:record_class]&.primary_key
39
+ primary_key = @record_class&.primary_key
38
40
  messages = records.map do |record|
39
41
  if record.respond_to?(:attributes)
40
42
  attrs = record.attributes.with_indifferent_access
41
43
  else
42
44
  attrs = record.with_indifferent_access
43
- if config[:refetch_record] && attrs[primary_key]
44
- record = config[:record_class].find(attrs[primary_key])
45
+ if @refetch_record && attrs[primary_key]
46
+ record = @record_class.find(attrs[primary_key])
45
47
  end
46
48
  end
47
49
  generate_payload(attrs, record).with_indifferent_access
@@ -50,6 +52,15 @@ module Deimos
50
52
  self.post_process(records)
51
53
  end
52
54
 
55
+ def config
56
+ Deimos.karafka_configs.find { |t| t.producer_class == self }
57
+ end
58
+
59
+ def encoder
60
+ raise "No schema or namespace configured for #{self.name}" if config.nil?
61
+ config.deserializers[:payload].backend
62
+ end
63
+
53
64
  # Generate the payload, given a list of attributes or a record..
54
65
  # Can be overridden or added to by subclasses.
55
66
  # @param attributes [Hash]
@@ -62,9 +73,9 @@ module Deimos
62
73
  payload.delete_if do |k, _|
63
74
  k.to_sym != :payload_key && !fields.map(&:name).include?(k)
64
75
  end
65
- return payload unless Utils::SchemaClass.use?(config.to_h)
76
+ return payload unless self.config.use_schema_classes
66
77
 
67
- Utils::SchemaClass.instance(payload, config[:schema], config[:namespace])
78
+ Utils::SchemaClass.instance(payload, encoder.schema, encoder.namespace)
68
79
  end
69
80
 
70
81
  # Query to use when polling the database with the DbPoller. Add
@@ -76,7 +87,7 @@ module Deimos
76
87
  # than this value).
77
88
  # @return [ActiveRecord::Relation]
78
89
  def poll_query(time_from:, time_to:, column_name: :updated_at, min_id:)
79
- klass = config[:record_class]
90
+ klass = @record_class
80
91
  table = ActiveRecord::Base.connection.quote_table_name(klass.table_name)
81
92
  column = ActiveRecord::Base.connection.quote_column_name(column_name)
82
93
  primary = ActiveRecord::Base.connection.quote_column_name(klass.primary_key)
@@ -95,6 +106,14 @@ module Deimos
95
106
  def post_process(_records)
96
107
  end
97
108
 
109
+ # Override this in active record producers to add
110
+ # non-schema fields to check for updates
111
+ # @param _record [ActiveRecord::Base]
112
+ # @return [Array<String>] fields to check for updates
113
+ def watched_attributes(_record)
114
+ self.encoder.schema_fields.map(&:name)
115
+ end
116
+
98
117
  end
99
118
  end
100
119
  end
@@ -6,10 +6,11 @@ module Deimos
6
6
  class Base
7
7
  class << self
8
8
  # @param producer_class [Class<Deimos::Producer>]
9
- # @param messages [Array<Deimos::Message>]
9
+ # @param messages [Array<Hash>]
10
10
  # @return [void]
11
11
  def publish(producer_class:, messages:)
12
- Deimos.config.logger.info(log_message(messages))
12
+ message = ::Deimos::Logging.messages_log_text(producer_class.karafka_config.payload_log, messages)
13
+ Deimos::Logging.log_info({message: 'Publishing Messages:'}.merge(message))
13
14
  execute(producer_class: producer_class, messages: messages)
14
15
  end
15
16
 
@@ -17,43 +18,11 @@ module Deimos
17
18
  # @param messages [Array<Deimos::Message>]
18
19
  # @return [void]
19
20
  def execute(producer_class:, messages:)
20
- raise NotImplementedError
21
+ raise MissingImplementationError
21
22
  end
22
23
 
23
24
  private
24
25
 
25
- def log_message(messages)
26
- log_message = {
27
- message: 'Publishing messages',
28
- topic: messages.first&.topic
29
- }
30
-
31
- case Deimos.config.payload_log
32
- when :keys
33
- log_message.merge!(
34
- payload_keys: messages.map(&:key)
35
- )
36
- when :count
37
- log_message.merge!(
38
- payloads_count: messages.count
39
- )
40
- when :headers
41
- log_message.merge!(
42
- payload_headers: messages.map(&:headers)
43
- )
44
- else
45
- log_message.merge!(
46
- payloads: messages.map do |message|
47
- {
48
- payload: message.payload,
49
- key: message.key
50
- }
51
- end
52
- )
53
- end
54
-
55
- log_message
56
- end
57
26
  end
58
27
  end
59
28
  end