Skip to content

Commit

Permalink
Add support for specifying a subject for the avro schema when buildin…
Browse files Browse the repository at this point in the history
…g an Avromatic model (#142)
  • Loading branch information
will89 authored Feb 7, 2022
1 parent 459e64a commit ca067d4
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# avromatic changelog

## 4.1.0
- Add support for specifying a subject for the avro schema when building an Avromatic model

## 4.0.0
- Drop support for Ruby 2.6.
- Drop support for Avro 1.9.
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ The Avro schema can be specified by name and loaded using the schema store:

```ruby
class MyModel
include Avromatic::Model.build(schema_name :my_model)
include Avromatic::Model.build(schema_name: :my_model)
end

# Construct instances by passing in a hash of attributes
Expand Down Expand Up @@ -156,12 +156,20 @@ class MyModel
end
```

A specific subject name can be associated with the schema:
```ruby
class MyModel
include Avromatic::Model.build(schema_name: 'my_model',
schema_subject: 'my_model-value')
end
```

Models are generated as immutable value
objects by default, but can optionally be defined as mutable:

```ruby
class MyModel
include Avromatic::Model.build(schema_name :my_model, mutable: true)
include Avromatic::Model.build(schema_name: :my_model, mutable: true)
end
```

Expand Down Expand Up @@ -195,6 +203,16 @@ class MyTopic
end
```

A specific subject name can be associated with both the value and key schemas:
```ruby
class MyTopic
include Avromatic::Model.build(value_schema_name: :topic_value,
value_schema_subject: 'topic_value-value',
key_schema_name: :topic_key,
key_schema_subject: 'topic_key-value')
end
```

A model can also be generated as an anonymous class that can be assigned to a
constant:

Expand Down
4 changes: 3 additions & 1 deletion lib/avromatic/model/configurable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def initialize(name)
end

module ClassMethods
delegate :avro_schema, :value_avro_schema, :key_avro_schema, :mutable?, :immutable?, to: :config
delegate :avro_schema, :value_avro_schema, :key_avro_schema, :mutable?, :immutable?,
:avro_schema_subject, :value_avro_schema_subject, :key_avro_schema_subject, to: :config

def value_avro_field_names
@value_avro_field_names ||= value_avro_schema.fields.map(&:name).map(&:to_sym).freeze
Expand Down Expand Up @@ -68,6 +69,7 @@ def mapped_by_name(schema)
end

delegate :avro_schema, :value_avro_schema, :key_avro_schema,
:avro_schema_subject, :value_avro_schema_subject, :key_avro_schema_subject,
:value_avro_field_names, :key_avro_field_names,
:value_avro_field_references, :key_avro_field_references,
:mutable?, :immutable?,
Expand Down
8 changes: 7 additions & 1 deletion lib/avromatic/model/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Model
class Configuration

attr_reader :avro_schema, :key_avro_schema, :nested_models, :mutable,
:allow_optional_key_fields
:allow_optional_key_fields, :avro_schema_subject, :key_avro_schema_subject
alias_method :mutable?, :mutable
delegate :schema_store, to: Avromatic

Expand All @@ -17,24 +17,30 @@ class Configuration
# @param options [Hash]
# @option options [Avro::Schema] :schema
# @option options [String, Symbol] :schema_name
# @option options [String, Symbol] :schema_subject
# @option options [Avro::Schema] :value_schema
# @option options [String, Symbol] :value_schema_name
# @option options [String, Symbol] :value_schema_subject
# @option options [Avro::Schema] :key_schema
# @option options [String, Symbol] :key_schema_name
# @option options [String, Symbol] :key_schema_subject
# @option options [Avromatic::ModelRegistry] :nested_models
# @option options [Boolean] :mutable, default false
# @option options [Boolean] :allow_optional_key_fields, default false
def initialize(**options)
@avro_schema = find_avro_schema(**options)
@avro_schema_subject = options[:schema_subject] || options[:value_schema_subject]
raise ArgumentError.new('value_schema(_name) or schema(_name) must be specified') unless avro_schema

@key_avro_schema = find_schema_by_option(:key_schema, **options)
@key_avro_schema_subject = options[:key_schema_subject]
@nested_models = options[:nested_models]
@mutable = options.fetch(:mutable, false)
@allow_optional_key_fields = options.fetch(:allow_optional_key_fields, false)
end

alias_method :value_avro_schema, :avro_schema
alias_method :value_avro_schema_subject, :avro_schema_subject

def immutable?
!mutable?
Expand Down
14 changes: 8 additions & 6 deletions lib/avromatic/model/messaging_serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module Encode
def avro_message_value
avro_messaging.encode(
value_attributes_for_avro,
schema_name: value_avro_schema.fullname
schema_name: value_avro_schema.fullname,
subject: value_avro_schema_subject
)
end

Expand All @@ -25,7 +26,8 @@ def avro_message_key

avro_messaging.encode(
key_attributes_for_avro,
schema_name: key_avro_schema.fullname
schema_name: key_avro_schema.fullname,
subject: key_avro_schema_subject
)
end
end
Expand Down Expand Up @@ -56,15 +58,15 @@ def avro_message_attributes(*args)

module Registration
def register_schemas!
register_schema(key_avro_schema) if key_avro_schema
register_schema(value_avro_schema)
register_schema(key_avro_schema, subject: key_avro_schema_subject) if key_avro_schema
register_schema(value_avro_schema, subject: value_avro_schema_subject)
nil
end

private

def register_schema(schema)
avro_messaging.registry.register(schema.fullname, schema)
def register_schema(schema, subject: nil)
avro_messaging.registry.register(subject || schema.fullname, schema)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/avromatic/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Avromatic
VERSION = '4.0.0'
VERSION = '4.1.0'
end
41 changes: 41 additions & 0 deletions spec/avromatic/model/builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@
end

it_behaves_like "a generated model"

context "with a specified schema subject" do
let(:schema_subject) { 'test.primitive_types-subject' }
let(:test_class) do
Avromatic::Model.model(schema_name: schema_name,
schema_subject: schema_subject)
end
let(:instance) { test_class.new }

it_behaves_like "a generated model"

it "returns the specified schema_subject" do
expect(instance.avro_schema_subject).to eq(schema_subject)
end
end
end

context "named fields" do
Expand Down Expand Up @@ -267,6 +282,32 @@
end
end

context "with a specified value and key subjects" do
let(:value_schema_subject) { 'test.value-subject' }
let(:key_schema_subject) { 'test.key-subject' }
let(:test_class) do
Avromatic::Model.model(value_schema_name: schema_name,
value_schema_subject: value_schema_subject,
key_schema_name: key_schema_name,
key_schema_subject: key_schema_subject)
end

let(:instance) { test_class.new }

it "defines a model with attributes for the key and value" do
expect(attribute_names)
.to match_array(schema.fields.map(&:name) | key_schema.fields.map(&:name))
end

it "returns the specified value_schema_subject" do
expect(instance.value_avro_schema_subject).to eq(value_schema_subject)
end

it "returns the specified key_schema_subject" do
expect(instance.key_avro_schema_subject).to eq(key_schema_subject)
end
end

context "when the key and value have conflicting fields" do
let(:key_schema_name) { 'test.key_conflict' }

Expand Down
58 changes: 58 additions & 0 deletions spec/avromatic/model/messaging_serialization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
let(:avro_message_value) { instance.avro_message_value }
let(:avro_message_key) { instance.avro_message_key }

before do
allow(Avromatic.schema_registry).to receive(:register).and_call_original
end

describe "#avro_message_value" do
let(:test_class) do
Avromatic::Model.model(value_schema_name: 'test.encode_value')
Expand All @@ -18,6 +22,25 @@
message_value = instance.avro_message_value
decoded = test_class.avro_message_decode(message_value)
expect(decoded).to eq(instance)
expect(Avromatic.schema_registry).to have_received(:register)
.with('test.encode_value', instance_of(Avro::Schema::RecordSchema))
end

context "with a specified value subject" do
let(:test_class) do
Avromatic::Model.model(value_schema_name: 'test.encode_value',
value_schema_subject: 'test.encode_value-subject')
end
let(:values) { { str1: 'a', str2: 'b' } }

it "encodes the value for the model" do
message_value = instance.avro_message_value
decoded = test_class.avro_message_decode(message_value)
expect(decoded).to eq(instance)
expect(Avromatic.schema_registry).to have_received(:register)
.with('test.encode_value-subject',
instance_of(Avro::Schema::RecordSchema))
end
end

context "with a nested record" do
Expand Down Expand Up @@ -156,6 +179,25 @@
message_key = instance.avro_message_key
decoded = test_class.avro_message_decode(message_key, message_value)
expect(decoded).to eq(instance)
expect(Avromatic.schema_registry).to have_received(:register)
.with('test.encode_key', instance_of(Avro::Schema::RecordSchema))
end

context "with a specified key subject" do
let(:test_class) do
Avromatic::Model.model(value_schema_name: 'test.encode_value',
key_schema_name: 'test.encode_key',
key_schema_subject: 'test.encode_key-subject')
end

it "encodes the key for the model" do
message_value = instance.avro_message_value
message_key = instance.avro_message_key
decoded = test_class.avro_message_decode(message_key, message_value)
expect(decoded).to eq(instance)
expect(Avromatic.schema_registry).to have_received(:register)
.with('test.encode_key-subject', instance_of(Avro::Schema::RecordSchema))
end
end

context "when a model does not have a key schema" do
Expand Down Expand Up @@ -408,6 +450,22 @@ def self.to_avro(value)
it_behaves_like "value schema registration"
end

context "a model with a specified subject" do
let(:test_class) do
Avromatic::Model.model(value_schema_name: 'test.encode_value',
value_schema_subject: 'test.encode_value-subject')
end

it "registers the value schema with the specified subject" do
expect(test_class.register_schemas!).to be_nil
registered = registry.subject_version('test.encode_value-subject')
aggregate_failures do
expect(registered['version']).to eq(1)
expect(registered['schema']).to eq(test_class.value_avro_schema.to_s)
end
end
end

context "a model with a key and value" do
let(:test_class) do
Avromatic::Model.model(value_schema_name: 'test.encode_value',
Expand Down

0 comments on commit ca067d4

Please sign in to comment.