Serialization and schemas

Apache Kafka · Practical · 14 min

Topics outlive the code that writes them, so event formats must evolve without breaking existing consumers.

Kafka stores bytes; the meaning of those bytes is a contract between producers and consumers. Common formats:

  • JSON: readable and easy, but verbose, untyped and with nothing stopping a producer from changing a field's type
  • Avro: compact binary with a schema; strong support for schema evolution, popular in data pipelines
  • Protobuf: compact, typed, familiar to gRPC users

A schema registry (Confluent Schema Registry, Apicurio, AWS Glue Schema Registry) stores versioned schemas per topic. Serializers register or look up the schema and prefix each message with a small schema id; deserializers fetch the schema by id. The registry enforces a compatibility mode on new versions:

  • BACKWARD (Confluent default): new consumers can read old data, e.g. adding a field with a default or deleting a field
  • FORWARD: old consumers can read new data
  • FULL: both

Upgrade order follows the mode: with BACKWARD, upgrade consumers first.

Design events as facts with a stable identity: include an event id, event type, timestamp and the entity key. Never repurpose a field. Keep the key format simple and stable, because it drives partitioning and compaction.

Example

import json
from dataclasses import dataclass, asdict

@dataclass
class OrderCreated:
    event_id: str
    order_id: int
    customer: str
    total_cents: int
    schema_version: int = 1

def serialize(evt) -> bytes:
    return json.dumps(asdict(evt), separators=(",", ":")).encode()

def deserialize(data: bytes) -> dict:
    d = json.loads(data)
    d.setdefault("currency", "USD")     # tolerate older events (field added in v2)
    return d

raw = serialize(OrderCreated("e-1", 17, "c1", 3590))
print(raw)               # b'{"event_id":"e-1","order_id":17,"customer":"c1","total_cents":3590,"schema_version":1}'
print(deserialize(raw)["currency"])   # USD

Remember

  • Kafka stores bytes; the format is a producer/consumer contract
  • Avro and Protobuf with a schema registry give typed, evolvable events
  • Compatibility modes (BACKWARD, FORWARD, FULL) gate schema changes
  • Include event id, type, timestamp and key in every event

Watch out for

  • Changing a JSON field's type in the producer and breaking every consumer at once.

Check yourself

  1. Under BACKWARD compatibility, which change is allowed?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.confluent.io/platform/current/schema-registry/fundamentals/schema-evolution.html