AvroTurf

repository·master·Indexed 41 days ago

https://github.com/dasch/avro_turf

A Ruby library for encoding and decoding data using the Apache Avro format. It provides an idiomatic Ruby interface with support for inter-schema referencing, data validation via `valid?`, and integration with the Confluent Schema Registry through `AvroTurf::ConfluentSchemaRegistry` and `AvroTurf::CachedConfluentSchemaRegistry`.

Tokens
7.9K
Snippets
29
Records
37
Agent score
52%

What's inside AvroTurf

  1. How inter-schema references work

    master

    Unlike the official Avro library, AvroTurf allows schemas to reference other schemas defined in separate files. This allows you to define a type once (e.g., address.avsc) and reference it in multiple other schemas (e.g., person.avsc).

    Important Note on Nested Schemas: Since version 1.0.0, AvroTurf::SchemaStore#find only resolves top-level schemas that have their own .avsc file. If a schema is defined purely as a nested object inside another .avsc file, it cannot be looked up directly via find unless it is moved to its own file.

  2. Use the Messaging API with Confluent Schema Registry

    master

    If you need to reduce output size (e.g., for Kafka or databases), use the AvroTurf::Messaging API. Instead of prepending the full schema to every message, it uses a schema ID. This requires a running Schema Registry service.

    Note: You must explicitly require 'avro_turf/messaging' to use this API. The Messaging format is not compatible with the standard Avro data file API.

    Key Methods:

    • encode: Automatically registers the schema if it's new. You can also specify a subject and version, or a schema_id directly.
    • decode: Fetches the schema from the registry using the ID embedded in the message and caches it.
    • decode_message: Returns a result object containing both the message and the schema_id used.
    require 'avro_turf/messaging'
    
    # Initialize with your registry URL
    avro = AvroTurf::Messaging.new(registry_url: "http://my-registry:8081/")
    
    # 1. Automatic registration
    data = avro.encode({ "title" => "hello" }, schema_name: "greeting")
    
    # 2. Encode using specific subject and version
    data = avro.encode({ "title" => "hello" }, subject: 'greeting', version: 1)
    
    # 3. Encode using a specific schema_id
    data = avro.encode({ "title" => "hello" }, schema_id: 2)
    
    # 4. Decode a message
    decoded_data = avro.decode(data)
    
    # 5. Decode and inspect metadata
    result = avro.decode_message(data)
    puts result.message     # The decoded hash
    puts result.schema_id   # The ID used for encoding
  3. Install AvroTurf

    master

    To use AvroTurf in your Ruby application, add it to your Gemfile:

    gem 'avro_turf'

    Then run bundle install, or install it directly via the command line:

    gem install avro_turf
    gem 'avro_turf'
  4. Basic Usage: Encode and Decode with Local Schemas

    master

    You can use AvroTurf.new to manage encoding and decoding using local .avsc files. Schemas are looked up from the directory specified by schemas_path. Each schema file must follow the naming convention <name>.avsc.

    Key features:

    • Validation: Pass validate: true to the encode method to catch errors early. This raises an Avro::SchemaValidator::ValidationError if the data does not match the schema.
    • Schema Registration: By default, encoding may attempt to register schemas. Use register_schemas: false to prevent this behavior.
    # Initialize with the directory containing your .avsc files
    avro = AvroTurf.new(schemas_path: "app/schemas/")
    
    # Decode data using a named schema
    decoded = avro.decode(encoded_data, schema_name: "person")
    
    # Encode data using a named schema
    encoded = avro.encode({ "name" => "Jane", "age" => 28 }, schema_name: "person")
    
    # Encode with validation enabled
    avro.encode({ "titl" => "hello" }, schema_name: "person", validate: true)
    
    # Encode without registering the schema
    avro.encode({ "name" => "Jane" }, schema_name: "person", register_schemas: false)
  5. Test with FakeConfluentSchemaRegistryServer

    master

    AvroTurf provides FakeConfluentSchemaRegistryServer for testing. Because of recent security updates in Sinatra, you must inherit from this class and configure host_authorization to permit your test host.

    Setup Example (RSpec):

    1. Require avro_turf/test/fake_confluent_schema_registry_server.
    2. Create an authorized subclass.
    3. Stub the request to your registry URL using WebMock.
    require 'avro_turf/test/fake_confluent_schema_registry_server'
    require 'webmock/rspec'
    
    class AuthorizedFakeConfluentSchemaRegistryServer < FakeConfluentSchemaRegistryServer
      set :host_authorization, permitted_hosts: ['registry.example.com']
    end
    
    # In your RSpec test:
    let(:registry_url) { "http://registry.example.com" }
    
    before do
      stub_request(:any, /^#{registry_url}/).to_rack(AuthorizedFakeConfluentSchemaRegistryServer)
      AuthorizedFakeConfluentSchemaRegistryServer.clear
    end
  6. Use the AvroTurf::Messaging API to encode and decode messages

    master

    The AvroTurf::Messaging class provides a way to encode and decode Avro messages without embedding the full schema in the encoded data. It uses Confluent's Schema Registry to manage schemas. When encoding, a schema ID is retrieved from the registry and prepended to the message. When decoding, the schema ID is read from the data to look up the writer's schema from the registry.

    Key Methods

    • encode(message, options): Encodes a message using a schema. Returns the encoded data as a String.
    • decode(data, options): Decodes data into the original message. Returns the decoded message object.
    • decode_message(data, options): Decodes data and returns a DecodedMessage object containing metadata about the schema used.
    # Example usage of Messaging
    # Assuming a registry_url is configured
    messaging = AvroTurf::Messaging.new(registry_url: 'http://localhost:8081')
    
    # Encoding
    encoded_data = messaging.encode(my_hash, schema_name: 'User', subject: 'user-topic')
    
    # Decoding
    # Returns just the message
    decoded_hash = messaging.decode(encoded_data, schema_name: 'User')
    
    # Decoding with metadata
    # Returns an AvroTurf::Messaging::DecodedMessage object
    decoded_msg = messaging.decode_message(encoded_data, schema_name: 'User')
    puts decoded_msg.schema_id
    puts decoded_msg.message
  7. Use the ConfluentSchemaRegistry client directly

    master

    You can interact with the Schema Registry directly using AvroTurf::ConfluentSchemaRegistry to check for compatibility or manage configurations without encoding/decoding data.

    require 'avro_turf'
    require 'avro_turf/confluent_schema_registry'
    
    registry = AvroTurf::ConfluentSchemaRegistry.new("http://my-registry:8081/")
    
    # Check if a schema is compatible with a subject
    registry.compatible?("person", schema_json)
    
    # Get list of breaking changes
    registry.compatibility_issues("person", schema_json)
    
    # Update global or subject-specific compatibility levels
    registry.update_global_config(compatibility: 'FULL')
    registry.update_subject_config("person", compatibility: 'NONE')
  8. Configure AvroTurf::Messaging

    master

    Instantiate AvroTurf::Messaging with a configuration hash to connect to a Schema Registry and manage local schemas.

    Configuration Options

    OptionTypeDescription
    registryObjectA schema registry object responding to AvroTurf::ConfluentSchemaRegistry interface.
    registry_urlStringThe URL of the schema registry.
    schema_contextStringSchema registry context name (optional).
    schema_storeObjectA schema store object responding to #find(schema_name, namespace).
    schemas_pathStringFile system path where local schemas are stored.
    namespaceStringDefault schema namespace.
    registry_path_prefixStringURL path prefix for namespacing registry requests.
    loggerLoggerLogger instance for information logging.
    proxyStringProxy URL.
    userStringBasic auth username.
    passwordStringBasic auth password.
    ssl_ca_fileStringPath to CA certificate file.
    client_certStringPath to client certificate file.
    client_keyStringPath to client private key file.
    client_key_passStringPassword for client_key.
    connect_timeoutIntegerConnection timeout for the registry.
    retry_limitIntegerNumber of retries for registry requests.
  9. Avoid registering sub-schemas in MutableSchemaStore

    master

    When using AvroTurf::MutableSchemaStore, you should only register top-level schemas that have their own corresponding .avsc files on disk.

    Do not attempt to register sub-schemas (nested schemas) manually. If you register a sub-schema that is also part of another schema being parsed, the underlying avro gem will raise an error because the name is already in use.

    Error to watch for: <Avro::SchemaParseError: The name "foo.bar" is already in use.>

  10. Initialize AvroTurf::CachedConfluentSchemaRegistry

    master

    Use AvroTurf::CachedConfluentSchemaRegistry to wrap a standard AvroTurf::ConfluentSchemaRegistry instance. This adds a caching layer to prevent repeated network calls to the upstream registry for registrations and lookups.

    Parameters

    • upstream: An object that implements the AvroTurf::ConfluentSchemaRegistry interface.
    • cache: (Optional) A user-provided cache object. If not provided, it defaults to AvroTurf::InMemoryCache. The provided cache must implement the AvroTurf::InMemoryCache interface.
    # Example initialization
    registry = AvroTurf::CachedConfluentSchemaRegistry.new(upstream_registry, cache: custom_cache)