deimos-ruby 2.3.0.pre.beta4 → 2.3.0
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.yml +1 -1
- data/CHANGELOG.md +3 -1
- data/README.md +8 -0
- data/deimos-ruby.gemspec +2 -2
- data/lib/deimos/backends/kafka.rb +1 -1
- data/lib/deimos/backends/kafka_async.rb +2 -1
- data/lib/deimos/config/configuration.rb +1 -1
- data/lib/deimos/ext/producer_middleware.rb +2 -2
- data/lib/deimos/kafka_source.rb +1 -1
- data/lib/deimos/metrics/datadog.rb +3 -1
- data/lib/deimos/schema_backends/avro_base.rb +6 -4
- data/lib/deimos/schema_backends/avro_local.rb +12 -13
- data/lib/deimos/schema_backends/avro_schema_registry.rb +15 -14
- data/lib/deimos/schema_backends/avro_validation.rb +1 -1
- data/lib/deimos/schema_backends/base.rb +4 -5
- data/lib/deimos/schema_backends/mock.rb +1 -1
- data/lib/deimos/schema_backends/plain.rb +1 -1
- data/lib/deimos/schema_backends/proto_base.rb +11 -36
- data/lib/deimos/schema_backends/proto_local.rb +5 -5
- data/lib/deimos/schema_backends/proto_schema_registry.rb +7 -32
- data/lib/deimos/test_helpers.rb +8 -0
- data/lib/deimos/transcoder.rb +1 -1
- data/lib/deimos/utils/outbox_producer.rb +2 -2
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +35 -15
- data/lib/generators/deimos/active_record_generator.rb +1 -1
- data/lib/generators/deimos/schema_class_generator.rb +3 -3
- data/lib/generators/deimos/v2_generator.rb +2 -2
- data/spec/deimos_spec.rb +32 -0
- data/spec/generators/schema_class_generator_spec.rb +4 -5
- data/spec/schema_backends/avro_base_shared.rb +1 -1
- data/spec/schema_backends/avro_local_spec.rb +8 -1
- data/spec/schema_backends/avro_schema_registry_spec.rb +7 -7
- data/spec/schema_backends/base_spec.rb +2 -2
- data/spec/schema_backends/proto_schema_registry_spec.rb +19 -222
- data/spec/snapshots/consumers-no-nest.snap +7 -7
- data/spec/snapshots/consumers.snap +7 -7
- data/spec/snapshots/consumers_and_producers-no-nest.snap +7 -7
- data/spec/snapshots/consumers_and_producers.snap +7 -7
- data/spec/snapshots/consumers_circular-no-nest.snap +7 -7
- data/spec/snapshots/consumers_circular.snap +7 -7
- data/spec/snapshots/consumers_complex_types-no-nest.snap +7 -7
- data/spec/snapshots/consumers_complex_types.snap +7 -7
- data/spec/snapshots/consumers_nested-no-nest.snap +7 -7
- data/spec/snapshots/consumers_nested.snap +7 -7
- data/spec/snapshots/namespace_folders.snap +7 -7
- data/spec/snapshots/namespace_map.snap +7 -7
- data/spec/snapshots/producers_with_key-no-nest.snap +7 -7
- data/spec/snapshots/producers_with_key.snap +7 -7
- data/spec/spec_helper.rb +1 -1
- metadata +35 -32
- data/CLAUDE.md +0 -270
- data/spec/gen/sample/v1/sample_key_pb.rb +0 -17
- data/spec/protos/sample/v1/sample_key.proto +0 -7
|
@@ -217,6 +217,13 @@ module Schemas
|
|
|
217
217
|
'com.my-namespace'
|
|
218
218
|
end
|
|
219
219
|
|
|
220
|
+
def self.tombstone(key)
|
|
221
|
+
record = self.allocate
|
|
222
|
+
record.tombstone_key = key
|
|
223
|
+
record.a_string = key
|
|
224
|
+
record
|
|
225
|
+
end
|
|
226
|
+
|
|
220
227
|
# @override
|
|
221
228
|
def as_json(_opts={})
|
|
222
229
|
{
|
|
@@ -437,13 +444,6 @@ module Schemas
|
|
|
437
444
|
'com.my-namespace'
|
|
438
445
|
end
|
|
439
446
|
|
|
440
|
-
def self.tombstone(key)
|
|
441
|
-
record = self.allocate
|
|
442
|
-
record.tombstone_key = key
|
|
443
|
-
record.test_id = key
|
|
444
|
-
record
|
|
445
|
-
end
|
|
446
|
-
|
|
447
447
|
# @override
|
|
448
448
|
def as_json(_opts={})
|
|
449
449
|
{
|
|
@@ -177,6 +177,13 @@ module Schemas
|
|
|
177
177
|
'com.my-namespace'
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
+
def self.tombstone(key)
|
|
181
|
+
record = self.allocate
|
|
182
|
+
record.tombstone_key = key
|
|
183
|
+
record.a_string = key
|
|
184
|
+
record
|
|
185
|
+
end
|
|
186
|
+
|
|
180
187
|
# @override
|
|
181
188
|
def as_json(_opts={})
|
|
182
189
|
{
|
|
@@ -391,13 +398,6 @@ module Schemas
|
|
|
391
398
|
'com.my-namespace'
|
|
392
399
|
end
|
|
393
400
|
|
|
394
|
-
def self.tombstone(key)
|
|
395
|
-
record = self.allocate
|
|
396
|
-
record.tombstone_key = key
|
|
397
|
-
record.test_id = key
|
|
398
|
-
record
|
|
399
|
-
end
|
|
400
|
-
|
|
401
401
|
# @override
|
|
402
402
|
def as_json(_opts={})
|
|
403
403
|
{
|
|
@@ -137,6 +137,13 @@ module Schemas; module Com; module MyNamespace
|
|
|
137
137
|
'com.my-namespace'
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
def self.tombstone(key)
|
|
141
|
+
record = self.allocate
|
|
142
|
+
record.tombstone_key = key
|
|
143
|
+
record.a_string = key
|
|
144
|
+
record
|
|
145
|
+
end
|
|
146
|
+
|
|
140
147
|
# @override
|
|
141
148
|
def as_json(_opts={})
|
|
142
149
|
{
|
|
@@ -266,13 +273,6 @@ module Schemas; module Com; module MyNamespace
|
|
|
266
273
|
'com.my-namespace'
|
|
267
274
|
end
|
|
268
275
|
|
|
269
|
-
def self.tombstone(key)
|
|
270
|
-
record = self.allocate
|
|
271
|
-
record.tombstone_key = key
|
|
272
|
-
record.test_id = key
|
|
273
|
-
record
|
|
274
|
-
end
|
|
275
|
-
|
|
276
276
|
# @override
|
|
277
277
|
def as_json(_opts={})
|
|
278
278
|
{
|
|
@@ -137,6 +137,13 @@ module Schemas; module MyNamespace
|
|
|
137
137
|
'com.my-namespace'
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
def self.tombstone(key)
|
|
141
|
+
record = self.allocate
|
|
142
|
+
record.tombstone_key = key
|
|
143
|
+
record.a_string = key
|
|
144
|
+
record
|
|
145
|
+
end
|
|
146
|
+
|
|
140
147
|
# @override
|
|
141
148
|
def as_json(_opts={})
|
|
142
149
|
{
|
|
@@ -311,13 +318,6 @@ module Schemas; module MyNamespace
|
|
|
311
318
|
'com.my-namespace'
|
|
312
319
|
end
|
|
313
320
|
|
|
314
|
-
def self.tombstone(key)
|
|
315
|
-
record = self.allocate
|
|
316
|
-
record.tombstone_key = key
|
|
317
|
-
record.test_id = key
|
|
318
|
-
record
|
|
319
|
-
end
|
|
320
|
-
|
|
321
321
|
# @override
|
|
322
322
|
def as_json(_opts={})
|
|
323
323
|
{
|
|
@@ -217,6 +217,13 @@ module Schemas
|
|
|
217
217
|
'com.my-namespace'
|
|
218
218
|
end
|
|
219
219
|
|
|
220
|
+
def self.tombstone(key)
|
|
221
|
+
record = self.allocate
|
|
222
|
+
record.tombstone_key = key
|
|
223
|
+
record.a_string = key
|
|
224
|
+
record
|
|
225
|
+
end
|
|
226
|
+
|
|
220
227
|
# @override
|
|
221
228
|
def as_json(_opts={})
|
|
222
229
|
{
|
|
@@ -437,13 +444,6 @@ module Schemas
|
|
|
437
444
|
'com.my-namespace'
|
|
438
445
|
end
|
|
439
446
|
|
|
440
|
-
def self.tombstone(key)
|
|
441
|
-
record = self.allocate
|
|
442
|
-
record.tombstone_key = key
|
|
443
|
-
record.test_id = key
|
|
444
|
-
record
|
|
445
|
-
end
|
|
446
|
-
|
|
447
447
|
# @override
|
|
448
448
|
def as_json(_opts={})
|
|
449
449
|
{
|
|
@@ -177,6 +177,13 @@ module Schemas
|
|
|
177
177
|
'com.my-namespace'
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
+
def self.tombstone(key)
|
|
181
|
+
record = self.allocate
|
|
182
|
+
record.tombstone_key = key
|
|
183
|
+
record.a_string = key
|
|
184
|
+
record
|
|
185
|
+
end
|
|
186
|
+
|
|
180
187
|
# @override
|
|
181
188
|
def as_json(_opts={})
|
|
182
189
|
{
|
|
@@ -391,13 +398,6 @@ module Schemas
|
|
|
391
398
|
'com.my-namespace'
|
|
392
399
|
end
|
|
393
400
|
|
|
394
|
-
def self.tombstone(key)
|
|
395
|
-
record = self.allocate
|
|
396
|
-
record.tombstone_key = key
|
|
397
|
-
record.test_id = key
|
|
398
|
-
record
|
|
399
|
-
end
|
|
400
|
-
|
|
401
401
|
# @override
|
|
402
402
|
def as_json(_opts={})
|
|
403
403
|
{
|
data/spec/spec_helper.rb
CHANGED
|
@@ -16,7 +16,7 @@ require 'handlers/my_consumer'
|
|
|
16
16
|
require 'rspec/rails'
|
|
17
17
|
require 'rspec/snapshot'
|
|
18
18
|
require 'karafka/testing/rspec/helpers'
|
|
19
|
-
Dir['./spec/schemas/**/*.rb'].each { |f| require f }
|
|
19
|
+
Dir['./spec/schemas/**/*.rb'].sort.each { |f| require f }
|
|
20
20
|
|
|
21
21
|
# Constants used for consumer specs
|
|
22
22
|
SCHEMA_CLASS_SETTINGS = { off: false, on: true }.freeze
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: deimos-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.3.0
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Orner
|
|
@@ -9,6 +9,26 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: avro_turf
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.4'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.4'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2'
|
|
12
32
|
- !ruby/object:Gem::Dependency
|
|
13
33
|
name: benchmark
|
|
14
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,20 +71,6 @@ dependencies:
|
|
|
51
71
|
- - "~>"
|
|
52
72
|
- !ruby/object:Gem::Version
|
|
53
73
|
version: '2.0'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: schema_registry_client
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - ">="
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0'
|
|
61
|
-
type: :runtime
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - ">="
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0'
|
|
68
74
|
- !ruby/object:Gem::Dependency
|
|
69
75
|
name: sigurd
|
|
70
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -225,6 +231,20 @@ dependencies:
|
|
|
225
231
|
- - "~>"
|
|
226
232
|
- !ruby/object:Gem::Version
|
|
227
233
|
version: '1.1'
|
|
234
|
+
- !ruby/object:Gem::Dependency
|
|
235
|
+
name: proto_turf
|
|
236
|
+
requirement: !ruby/object:Gem::Requirement
|
|
237
|
+
requirements:
|
|
238
|
+
- - ">="
|
|
239
|
+
- !ruby/object:Gem::Version
|
|
240
|
+
version: '0'
|
|
241
|
+
type: :development
|
|
242
|
+
prerelease: false
|
|
243
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
244
|
+
requirements:
|
|
245
|
+
- - ">="
|
|
246
|
+
- !ruby/object:Gem::Version
|
|
247
|
+
version: '0'
|
|
228
248
|
- !ruby/object:Gem::Dependency
|
|
229
249
|
name: rails
|
|
230
250
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -337,20 +357,6 @@ dependencies:
|
|
|
337
357
|
- - '='
|
|
338
358
|
- !ruby/object:Gem::Version
|
|
339
359
|
version: '3.8'
|
|
340
|
-
- !ruby/object:Gem::Dependency
|
|
341
|
-
name: schema_registry_client
|
|
342
|
-
requirement: !ruby/object:Gem::Requirement
|
|
343
|
-
requirements:
|
|
344
|
-
- - ">="
|
|
345
|
-
- !ruby/object:Gem::Version
|
|
346
|
-
version: '0'
|
|
347
|
-
type: :development
|
|
348
|
-
prerelease: false
|
|
349
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
350
|
-
requirements:
|
|
351
|
-
- - ">="
|
|
352
|
-
- !ruby/object:Gem::Version
|
|
353
|
-
version: '0'
|
|
354
360
|
- !ruby/object:Gem::Dependency
|
|
355
361
|
name: sord
|
|
356
362
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -452,7 +458,6 @@ files:
|
|
|
452
458
|
- ".rubocop_todo.yml"
|
|
453
459
|
- ".tool-versions"
|
|
454
460
|
- CHANGELOG.md
|
|
455
|
-
- CLAUDE.md
|
|
456
461
|
- CODE_OF_CONDUCT.md
|
|
457
462
|
- Dockerfile
|
|
458
463
|
- Gemfile
|
|
@@ -578,7 +583,6 @@ files:
|
|
|
578
583
|
- spec/batch_consumer_spec.rb
|
|
579
584
|
- spec/consumer_spec.rb
|
|
580
585
|
- spec/deimos_spec.rb
|
|
581
|
-
- spec/gen/sample/v1/sample_key_pb.rb
|
|
582
586
|
- spec/gen/sample/v1/sample_pb.rb
|
|
583
587
|
- spec/generators/active_record_generator_spec.rb
|
|
584
588
|
- spec/generators/schema_class/my_schema_spec.rb
|
|
@@ -595,7 +599,6 @@ files:
|
|
|
595
599
|
- spec/message_spec.rb
|
|
596
600
|
- spec/producer_spec.rb
|
|
597
601
|
- spec/protos/sample/v1/sample.proto
|
|
598
|
-
- spec/protos/sample/v1/sample_key.proto
|
|
599
602
|
- spec/rake_spec.rb
|
|
600
603
|
- spec/schema_backends/avro_base_shared.rb
|
|
601
604
|
- spec/schema_backends/avro_local_spec.rb
|
data/CLAUDE.md
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Deimos is a Ruby framework for Kafka development that marries Kafka with schema definitions (Avro/Protobuf), ActiveRecord, and provides a comprehensive toolbox. Built on top of Karafka (which itself builds on RDKafka), it provides schema encoding/decoding, database integration, metrics, tracing, and test helpers.
|
|
8
|
-
|
|
9
|
-
## Development Commands
|
|
10
|
-
|
|
11
|
-
### Testing
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
# Run all tests
|
|
15
|
-
bundle exec rspec
|
|
16
|
-
|
|
17
|
-
# Run a single test file
|
|
18
|
-
bundle exec rspec spec/path/to/file_spec.rb
|
|
19
|
-
|
|
20
|
-
# Run a specific test
|
|
21
|
-
bundle exec rspec spec/path/to/file_spec.rb:LINE_NUMBER
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Linting
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
# Run Rubocop linter
|
|
28
|
-
bundle exec rubocop
|
|
29
|
-
|
|
30
|
-
# Auto-correct issues
|
|
31
|
-
bundle exec rubocop -a
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Schema Class Generation
|
|
35
|
-
|
|
36
|
-
When modifying schema-related code, you may need to regenerate test schema classes:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
# Regenerate test schema classes (used after schema changes)
|
|
40
|
-
bundle exec ./regenerate_test_schema_classes.rb
|
|
41
|
-
|
|
42
|
-
# Generate Protobuf classes
|
|
43
|
-
protoc -I spec/protos --ruby_out=spec/gen --ruby_opt=paths=source_relative spec/protos/**/*.proto
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Rake Tasks
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# Start Deimos consumer (in Rails environment)
|
|
50
|
-
rake deimos:start
|
|
51
|
-
|
|
52
|
-
# Start outbox backend producer
|
|
53
|
-
rake deimos:outbox
|
|
54
|
-
|
|
55
|
-
# Start database poller
|
|
56
|
-
rake deimos:db_poller
|
|
57
|
-
|
|
58
|
-
# Generate schema classes
|
|
59
|
-
rake deimos:generate_schema_classes
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Architecture
|
|
63
|
-
|
|
64
|
-
### Layer Structure
|
|
65
|
-
|
|
66
|
-
Deimos is built in layers:
|
|
67
|
-
1. **RDKafka** - Low-level Kafka client providing producer/consumer APIs
|
|
68
|
-
2. **Karafka** - Threaded consumers wrapper with lifecycle management
|
|
69
|
-
3. **Deimos** - Full framework with schema integration, database features, metrics, and utilities
|
|
70
|
-
|
|
71
|
-
### Key Directories
|
|
72
|
-
|
|
73
|
-
- `lib/deimos/` - Core Deimos code
|
|
74
|
-
- `lib/deimos/backends/` - Producer backends (kafka, kafka_async, outbox, test)
|
|
75
|
-
- `lib/deimos/schema_backends/` - Schema handlers (Avro local/registry/validation, Protobuf, plain, mock)
|
|
76
|
-
- `lib/deimos/metrics/` - Metrics providers (DataDog, mock)
|
|
77
|
-
- `lib/deimos/tracing/` - Tracing providers (DataDog, mock)
|
|
78
|
-
- `lib/deimos/utils/` - Utilities (DB poller, outbox producer, etc.)
|
|
79
|
-
- `lib/deimos/config/` - Configuration classes
|
|
80
|
-
- `lib/deimos/ext/` - Extensions to Karafka routing
|
|
81
|
-
- `lib/generators/` - Rails generators for migrations
|
|
82
|
-
- `lib/tasks/` - Rake tasks
|
|
83
|
-
|
|
84
|
-
### Core Concepts
|
|
85
|
-
|
|
86
|
-
#### Schema Backends
|
|
87
|
-
|
|
88
|
-
Schema backends encode/decode payloads. All backends must implement:
|
|
89
|
-
- `encode(payload, topic:)` - Encode payload to binary/string format
|
|
90
|
-
- `decode(payload)` - Decode binary/string to hash
|
|
91
|
-
- `validate(payload)` - Validate payload against schema
|
|
92
|
-
- `coerce(payload)` - Coerce payload to match schema types
|
|
93
|
-
- `schema_fields` - List fields in schema (used with ActiveRecord)
|
|
94
|
-
- Define a `mock` backend for testing
|
|
95
|
-
|
|
96
|
-
Available backends: `:avro_local`, `:avro_schema_registry`, `:avro_validation`, `:proto_schema_registry`, `:proto_local`, `:mock`, `:plain`
|
|
97
|
-
|
|
98
|
-
#### Producer Backends
|
|
99
|
-
|
|
100
|
-
Producer backends determine how messages are sent. All backends inherit from `Deimos::Backends::Base` and implement `execute(messages)`.
|
|
101
|
-
|
|
102
|
-
Available backends:
|
|
103
|
-
- `:kafka` - Send directly to Kafka (default)
|
|
104
|
-
- `:kafka_async` - Async variant of kafka backend
|
|
105
|
-
- `:outbox` - Transactional outbox pattern (save to DB, send async)
|
|
106
|
-
- `:test` - For testing (stores messages in memory)
|
|
107
|
-
|
|
108
|
-
#### Consumer Types
|
|
109
|
-
|
|
110
|
-
- `Deimos::Consumer` - Base consumer class
|
|
111
|
-
- Per-message: Override `consume_message(message)` and set `each_message true`
|
|
112
|
-
- Batch: Override `consume_batch` (receives `messages` collection)
|
|
113
|
-
|
|
114
|
-
- `Deimos::ActiveRecordConsumer` - Automatically saves/updates ActiveRecord models
|
|
115
|
-
- Per-message mode: Uses `fetch_record`, `assign_key`, `destroy_record`
|
|
116
|
-
- Batch mode: Uses `activerecord-import` for bulk operations
|
|
117
|
-
- Override `record_attributes(payload, key)` to customize attributes
|
|
118
|
-
|
|
119
|
-
#### Producers
|
|
120
|
-
|
|
121
|
-
- `Deimos::Producer` - Base producer class
|
|
122
|
-
- Call `self.produce([{payload: ..., key: ..., topic: ...}])`
|
|
123
|
-
- Override `partition_key(payload)` for custom partitioning
|
|
124
|
-
|
|
125
|
-
- `Deimos::ActiveRecordProducer` - Produces from ActiveRecord models
|
|
126
|
-
- Set `record_class Widget, refetch: false`
|
|
127
|
-
- Override `generate_payload(attributes, record)` to customize payload
|
|
128
|
-
- Override `watched_attributes(record)` to add non-schema fields
|
|
129
|
-
|
|
130
|
-
#### Key Configuration
|
|
131
|
-
|
|
132
|
-
Every producer must define `key_config`:
|
|
133
|
-
- `key_config none: true` - No keys (events)
|
|
134
|
-
- `key_config plain: true` - Unencoded keys (legacy)
|
|
135
|
-
- `key_config schema: 'MySchema-key'` - Use existing key schema
|
|
136
|
-
- `key_config field: 'my_id'` - Auto-generate key schema from value field
|
|
137
|
-
|
|
138
|
-
#### KafkaSource Mixin
|
|
139
|
-
|
|
140
|
-
The `Deimos::KafkaSource` mixin adds callbacks to ActiveRecord models to automatically send Kafka messages on save/destroy:
|
|
141
|
-
|
|
142
|
-
```ruby
|
|
143
|
-
class Widget < ActiveRecord::Base
|
|
144
|
-
include Deimos::KafkaSource
|
|
145
|
-
|
|
146
|
-
def self.kafka_producers
|
|
147
|
-
[MyProducer]
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def self.kafka_config
|
|
151
|
-
{ update: true, delete: true, import: true, create: true }
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
#### Outbox Pattern (Database Backend)
|
|
157
|
-
|
|
158
|
-
The outbox pattern provides transactional guarantees:
|
|
159
|
-
1. Messages are validated, encoded, and saved to `kafka_messages` table
|
|
160
|
-
2. Separate thread pool (via `Deimos::Utils::OutboxProducer`) reads from DB and sends to Kafka
|
|
161
|
-
3. Uses `kafka_topic_info` table for topic-level locking
|
|
162
|
-
4. Runs via `rake deimos:outbox` or `Deimos.start_outbox_backend!(thread_count: N)`
|
|
163
|
-
|
|
164
|
-
#### Database Poller
|
|
165
|
-
|
|
166
|
-
Polls database tables and produces messages:
|
|
167
|
-
1. Configure with `Deimos.configure { db_poller { producer_class 'MyProducer' } }`
|
|
168
|
-
2. Two modes:
|
|
169
|
-
- Time-based (default): Uses `updated_at` and `id` columns
|
|
170
|
-
- State-based: Updates state column after publishing
|
|
171
|
-
3. Tracks progress in `poll_info` table
|
|
172
|
-
4. Runs via `rake deimos:db_poller`
|
|
173
|
-
|
|
174
|
-
### Configuration
|
|
175
|
-
|
|
176
|
-
Configuration uses the `fig_tree` gem. See `lib/deimos/config/configuration.rb` for the schema. Configure via:
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
Deimos.configure do |config|
|
|
180
|
-
config.producers.backend = :outbox
|
|
181
|
-
config.schema.backend = :avro_schema_registry
|
|
182
|
-
config.schema.registry_url = 'http://localhost:8081'
|
|
183
|
-
end
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Karafka Integration
|
|
187
|
-
|
|
188
|
-
Deimos extends Karafka routing with schema configuration:
|
|
189
|
-
|
|
190
|
-
```ruby
|
|
191
|
-
Karafka::App.routes.draw do
|
|
192
|
-
topic 'my-topic' do
|
|
193
|
-
consumer MyConsumer
|
|
194
|
-
schema 'MySchema'
|
|
195
|
-
namespace 'com.my-namespace'
|
|
196
|
-
key_config field: 'test_id'
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
The `lib/deimos/ext/` directory contains the routing extensions that add schema-aware capabilities to Karafka's routing DSL.
|
|
202
|
-
|
|
203
|
-
### Shared Configuration
|
|
204
|
-
|
|
205
|
-
Both producers and consumers use the `SharedConfig` module to standardize schema settings, topic configuration, key handling, etc.
|
|
206
|
-
|
|
207
|
-
### Testing
|
|
208
|
-
|
|
209
|
-
Test helpers in `lib/deimos/test_helpers.rb` provide:
|
|
210
|
-
- `test_consume_message(consumer, payload)` - Test message consumption
|
|
211
|
-
- `test_consume_batch(consumer, payloads)` - Test batch consumption
|
|
212
|
-
- `expect(topic).to have_sent(payload, key, partition_key, headers)` - Assert messages sent
|
|
213
|
-
- `Deimos::TestHelpers.sent_messages` - Inspect sent messages
|
|
214
|
-
|
|
215
|
-
Configure for tests:
|
|
216
|
-
```ruby
|
|
217
|
-
Deimos.config.schema.backend = :avro_validation # Validates but doesn't encode
|
|
218
|
-
Deimos.config.producers.backend = :test # Stores in memory
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Instrumentation
|
|
222
|
-
|
|
223
|
-
Deimos sends events through Karafka's instrumentation monitor:
|
|
224
|
-
- `deimos.encode_message` - Schema encoding
|
|
225
|
-
- `deimos.outbox.produce` - Outbox messages processed
|
|
226
|
-
- `deimos.ar_consumer.consume_batch` - ActiveRecord batch consumption
|
|
227
|
-
- `deimos.batch_consumption.valid_records` - Valid records upserted
|
|
228
|
-
- `deimos.batch_consumption.invalid_records` - Invalid records rejected
|
|
229
|
-
|
|
230
|
-
Subscribe with: `Karafka.monitor.subscribe('event.name') { |event| ... }`
|
|
231
|
-
|
|
232
|
-
## Database Integration
|
|
233
|
-
|
|
234
|
-
Deimos uses two primary database tables (created via generators):
|
|
235
|
-
|
|
236
|
-
- `kafka_messages` - Stores outbox messages (topic, key, payload, partition_key)
|
|
237
|
-
- `kafka_topic_info` - Locks topics for outbox processing
|
|
238
|
-
- `poll_info` - Tracks database poller progress
|
|
239
|
-
|
|
240
|
-
## Special Considerations
|
|
241
|
-
|
|
242
|
-
### Schema Changes
|
|
243
|
-
|
|
244
|
-
When adding/modifying schemas:
|
|
245
|
-
1. Update schema files in configured schema path
|
|
246
|
-
2. Run `rake deimos:generate_schema_classes` if using schema classes
|
|
247
|
-
3. Regenerate test schemas with `./regenerate_test_schema_classes.rb`
|
|
248
|
-
|
|
249
|
-
### Protobuf
|
|
250
|
-
|
|
251
|
-
- Requires `schema_registry_client` gem in Gemfile
|
|
252
|
-
- Protobuf payloads must be Protobuf message objects, not hashes
|
|
253
|
-
- Protobuf should NOT be used for keys (unstable binary encoding)
|
|
254
|
-
|
|
255
|
-
### ActiveRecord Batch Consumers
|
|
256
|
-
|
|
257
|
-
- Only supports primary keys as identifiers by default
|
|
258
|
-
- Skips ActiveRecord callbacks (uses `activerecord-import` for bulk SQL)
|
|
259
|
-
- Set `compacted false` to process all messages vs. only last per key
|
|
260
|
-
- Requires `bulk_import_id_column` config when saving to multiple tables
|
|
261
|
-
|
|
262
|
-
### Error Handling
|
|
263
|
-
|
|
264
|
-
- Set `config.consumers.reraise_errors = false` to swallow non-fatal errors
|
|
265
|
-
- Define fatal errors via `config.fatal_error` (global) or `fatal_error?(exception, payload, metadata)` (per-consumer)
|
|
266
|
-
- Prevents consumer from getting stuck on bad messages
|
|
267
|
-
|
|
268
|
-
## Integration Tests
|
|
269
|
-
|
|
270
|
-
Integration tests run against real databases (PostgreSQL, MySQL/Trilogy, SQLite). They are marked with `:integration` metadata and use different database configs from `DbConfigs::DB_OPTIONS`.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: sample/v1/sample_key.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x1asample/v1/sample_key.proto\x12\tsample.v1\"\x1f\n\x10SampleMessageKey\x12\x0b\n\x03str\x18\x01 \x01(\tb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Sample
|
|
14
|
-
module V1
|
|
15
|
-
SampleMessageKey = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("sample.v1.SampleMessageKey").msgclass
|
|
16
|
-
end
|
|
17
|
-
end
|