Series MapLesson 74 / 112
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Deepen PracticeOrdered learning track

Event Schema Governance

Schema Registry Avro JSON Schema Protobuf and Compatibility Mode

Schema governance untuk event-driven systems: schema registry, Avro, JSON Schema, Protobuf, compatibility mode, schema evolution, validation, producer/consumer compatibility, and production review

14 min read2702 words
PrevNext
Lesson 74112 lesson track62–92 Deepen Practice
#schema-registry#avro#json-schema#protobuf+4 more

Part 074 — Schema Registry, Avro, JSON Schema, Protobuf, and Compatibility Mode

Fokus part ini: memahami schema governance untuk event-driven systems. Kita akan membahas schema registry, Avro, JSON Schema, Protobuf, compatibility mode, schema evolution, validation, dan bagaimana mereview perubahan event agar producer/consumer tidak rusak.

Kafka hanya menyimpan bytes.

key: bytes
value: bytes
headers: key-value bytes

Kafka tidak tahu apakah payload adalah:

  • Avro
  • JSON
  • Protobuf
  • String
  • encrypted blob
  • corrupted bytes

Tanpa schema governance, event-driven system berubah menjadi kontrak informal yang rapuh.

Masalah biasanya tidak muncul saat producer dan consumer dikembangkan oleh orang yang sama. Masalah muncul ketika:

  • consumer lama masih berjalan
  • producer baru deploy lebih dulu
  • event field dihapus
  • enum value baru ditambahkan
  • numeric type berubah
  • JSON field rename tanpa compatibility plan
  • replay event lama dengan schema baru
  • generated class berbeda antar service

Schema governance adalah cara menjaga event sebagai contract, bukan side effect internal.


1. Core Mental Model

Event schema adalah kontrak antar waktu dan antar service.

producer version N writes event
consumer version N consumes event
consumer version N-1 may still consume event
consumer version N+1 may replay old event
analytics/backfill may read event months later

Karena itu compatibility harus dipikirkan dalam dua arah:

new consumer reads old event
old consumer reads new event

Schema registry membantu menjawab:

  • schema apa yang valid untuk topic/event ini?
  • apakah schema baru compatible dengan schema lama?
  • schema version mana yang digunakan payload ini?
  • siapa owner schema?
  • apakah producer boleh publish payload ini?
  • apakah consumer bisa deserialize payload lama?

2. Schema Registry Concept

Schema registry adalah service/repository untuk menyimpan schema dan compatibility rule.

producer
  -> register/lookup schema
  -> serialize payload with schema id/version
  -> publish bytes to Kafka

consumer
  -> read bytes
  -> extract schema id/version
  -> fetch schema if needed
  -> deserialize payload

Biasanya payload membawa schema ID dalam wire format atau header.

Kafka record value:
  magic byte + schema id + encoded payload

Atau:

headers:
  schema-id: 123
  schema-version: 4
  content-type: application/avro

Exact implementation bergantung library/platform.


3. Subject Naming Strategy

Schema registry biasanya menggunakan subject.

Subject menentukan compatibility scope.

Contoh subject:

quote.events-value
quote.events-key
QuoteApproved-value
tenant.quote.approved-value

Trade-off:

Topic-based subject

<topic>-value

Kelebihan:

  • sederhana
  • satu topic satu schema family
  • mudah dipahami tooling

Kekurangan:

  • sulit jika satu topic berisi banyak event type dengan schema berbeda

Record/event-based subject

<event-name>-value

Kelebihan:

  • cocok untuk event catalog
  • compatibility per event type

Kekurangan:

  • butuh routing/metadata lebih jelas
  • topic bisa memuat banyak schema ID

Topic + event subject

<topic>-<event-name>-value

Kelebihan:

  • eksplisit
  • mengurangi collision

Kekurangan:

  • lebih verbose
  • butuh governance naming

Internal standard harus diverifikasi.


4. Avro

Avro populer di Kafka karena compact binary encoding dan schema evolution support.

Karakteristik:

  • schema dalam JSON format
  • binary payload compact
  • mendukung reader schema vs writer schema
  • default value penting untuk compatibility
  • union type digunakan untuk nullable field
  • sering dipakai dengan schema registry

Example Avro schema:

{
  "type": "record",
  "name": "QuoteApproved",
  "namespace": "com.example.quote.events",
  "fields": [
    { "name": "eventId", "type": "string" },
    { "name": "quoteId", "type": "string" },
    { "name": "aggregateVersion", "type": "long" },
    { "name": "approvedAt", "type": { "type": "long", "logicalType": "timestamp-millis" } },
    { "name": "approvedBy", "type": ["null", "string"], "default": null }
  ]
}

Avro Compatibility Rules

Adding optional field with default is usually compatible.

{ "name": "reason", "type": ["null", "string"], "default": null }

Removing required field can break consumers.

Renaming field is usually breaking unless alias is used.

Changing type can break unless promotion is supported and safe.

Important Avro concerns:

  • default values are not optional detail; they are compatibility mechanism
  • logical types must be standardized
  • enum evolution must be reviewed carefully
  • generated class compatibility depends on build tooling

5. JSON Schema

JSON Schema is useful when payload is JSON and human-readable.

Characteristics:

  • easier to inspect manually
  • aligns with JSON APIs
  • flexible but can become too loose
  • compatibility depends on strict schema discipline

Example JSON Schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "QuoteApproved",
  "type": "object",
  "required": ["eventId", "quoteId", "aggregateVersion", "approvedAt"],
  "properties": {
    "eventId": { "type": "string" },
    "quoteId": { "type": "string" },
    "aggregateVersion": { "type": "integer", "minimum": 1 },
    "approvedAt": { "type": "string", "format": "date-time" },
    "approvedBy": { "type": ["string", "null"] }
  },
  "additionalProperties": false
}

JSON Schema Trade-Off

additionalProperties: false gives stricter validation but can hurt forward compatibility if old consumers reject new fields.

additionalProperties: true gives forward compatibility but can hide typos and contract drift.

Senior review question:

Should unknown fields be tolerated at event boundary?

For external/public contracts, tolerance can be useful. For internal event governance, strictness plus compatibility checks can catch mistakes earlier.


6. Protobuf

Protobuf is compact, strongly typed, and common for gRPC and event contracts.

Characteristics:

  • field numbers are part of contract
  • field names can change more safely than field numbers, but generated APIs are affected
  • removing fields requires reserving field numbers/names
  • unknown fields may be preserved depending on runtime
  • good for cross-language contracts

Example Protobuf:

syntax = "proto3";

package quote.events.v1;

message QuoteApproved {
  string event_id = 1;
  string quote_id = 2;
  int64 aggregate_version = 3;
  string approved_at = 4;
  optional string approved_by = 5;
}

Protobuf Compatibility Rules

Do not reuse field numbers.

Bad:

// old
string approved_by = 5;

// new, bad reuse
string sales_channel = 5;

Better:

reserved 5;
reserved "approved_by";
string sales_channel = 6;

Changing field type can be breaking. Changing meaning of field without changing schema is also breaking, even if binary compatibility passes.


7. Compatibility Modes

Common compatibility modes:

BACKWARD
FORWARD
FULL
NONE
BACKWARD_TRANSITIVE
FORWARD_TRANSITIVE
FULL_TRANSITIVE

Exact names may differ by registry implementation.

7.1 Backward Compatibility

New consumer can read old data.

consumer v2 reads event produced with schema v1

Useful for replay old data using new code.

7.2 Forward Compatibility

Old consumer can read new data.

consumer v1 reads event produced with schema v2

Useful during rolling deployment where producer upgrades before all consumers.

7.3 Full Compatibility

Both backward and forward.

consumer v2 reads v1
consumer v1 reads v2

Safer but more restrictive.

7.4 Transitive Compatibility

Schema vN must be compatible with all previous versions, not only vN-1.

This matters for long-lived topics and replay.

v4 compatible with v1, v2, v3

For enterprise event streams with replay requirement, transitive compatibility is often more defensible.


8. Schema Evolution Patterns

8.1 Add Optional Field

Usually safe if default exists or consumer tolerates missing value.

"discountReason": null

Review:

  • default defined?
  • old consumers tolerate unknown field?
  • generated classes updated?
  • analytics consumers aware?

8.2 Add Required Field

Usually breaking.

Old events do not have the field. Old producers cannot populate it.

Safer path:

  1. Add optional field.
  2. Deploy consumers that tolerate/populate default.
  3. Deploy producers.
  4. Backfill if needed.
  5. Later enforce required only if all historical/replay concerns resolved.

8.3 Rename Field

Usually breaking.

Safer path:

  1. Add new field.
  2. Produce both old and new field for transition if format allows.
  3. Migrate consumers.
  4. Deprecate old field.
  5. Remove only after policy window.

8.4 Remove Field

Can break old consumers.

Safer path:

  • mark deprecated
  • keep producing default/null
  • remove after consumer inventory confirms no usage

8.5 Change Type

High risk.

Example:

int -> long may be safe in some formats
string -> object is breaking
number -> string is semantic breaking

8.6 Add Enum Value

Often underestimated.

Old consumer may fail on unknown enum.

Safer strategy:

  • consumer default/UNKNOWN branch
  • schema evolution test
  • deploy consumers before producer emits new value

9. Event Envelope vs Event Payload Schema

Separate envelope fields from domain payload fields.

Envelope:

{
  "eventId": "evt-123",
  "eventType": "QuoteApproved",
  "eventVersion": 1,
  "aggregateId": "quote-456",
  "aggregateVersion": 17,
  "tenantId": "tenant-a",
  "occurredAt": "2026-07-10T04:00:00Z",
  "producer": "quote-service",
  "correlationId": "corr-789",
  "causationId": "cmd-555"
}

Payload:

{
  "approvedBy": "user-123",
  "approvalReason": "commercial-approved"
}

Why separate?

  • envelope supports routing, idempotency, ordering, observability
  • payload contains business fact
  • envelope evolves slower
  • payload evolves per event type

Possible designs:

  1. Envelope and payload in one schema.
  2. Envelope schema + payload schema nested.
  3. Metadata in Kafka headers + value contains payload.

Each has trade-off.

Headers are useful for routing/tracing but are easier to lose in some tooling/replay paths. Critical business metadata should usually be in payload/envelope, not only headers.


10. Producer Compatibility

Producer compatibility asks:

Can this producer publish records that all expected consumers can read?

Producer-side checks:

  • schema registered before publish
  • schema compatibility validated in CI
  • serializer uses correct subject
  • required envelope fields populated
  • event type/version matches schema
  • default/null semantics understood
  • generated class version matches schema artifact

Bad producer behavior:

  • publish raw JSON without schema validation
  • use Map<String,Object> without contract
  • add enum value without consumer rollout
  • change field meaning without schema change
  • publish event before DB transaction commits

11. Consumer Compatibility

Consumer compatibility asks:

Can this consumer read old and new event versions safely?

Consumer-side checks:

  • unknown field tolerance policy
  • missing field default policy
  • unknown enum handling
  • old event replay tested
  • event version branching if needed
  • schema ID resolution works offline/cache failure
  • poison message classification for incompatible schema

Consumer should not assume latest schema only.

Replay means old events can reappear.


12. Contract Compatibility Matrix

A compatibility matrix makes rollout risk explicit.

Example:

ProducerConsumerExpected Result
v1v1OK
v1v2backward compatibility required
v2v1forward compatibility required
v2v2OK
v3v1maybe unsupported after deprecation window

For event changes, also include:

ScenarioRequired?
new consumer reads old topic datayes/no
old consumer reads new producer event during rolling deployyes/no
DLQ replay after 90 daysyes/no
analytics reads historical eventsyes/no
cross-region replication preserves schema IDyes/no

Matrix should be part of architecture review for breaking changes.


13. Generated Code and Build Governance

Schema often generates Java classes.

Risks:

  • generated classes checked into repo vs generated during build
  • generator version drift
  • schema artifact version mismatch
  • incompatible generated API after field rename
  • consumer uses stale generated class

Build governance:

schema artifact -> code generation -> compile -> compatibility test -> publish artifact

Checklist:

  • generator version pinned
  • generated code reproducible
  • schema artifact versioned
  • compatibility tests run in CI
  • no manual edit to generated code
  • generated package naming stable

14. Schema Validation in CI/CD

Schema compatibility should fail before deployment.

Pipeline checks:

lint schema
validate naming
validate required envelope fields
validate compatibility mode
generate code
run unit tests
run consumer contract tests
publish schema artifact

Bad pattern:

producer registers schema for first time in production at startup

Risk:

  • production startup fails due to compatibility rejection
  • producer deploy partially succeeds
  • emergency rollback complicated

Better:

schema registration/validation happens in controlled pipeline stage

Runtime may still look up schema, but compatibility surprises should be caught earlier.


15. Runtime Failure Modes

15.1 Schema Not Found

Consumer receives schema ID but registry does not have it.

Possible causes:

  • wrong registry environment
  • replicated topic without schema registry replication
  • schema deleted
  • network/auth issue
  • subject mismatch

Expected handling:

  • classify as infrastructure/config failure
  • do not silently skip
  • alert platform/schema owner

15.2 Deserialization Failure

Possible causes:

  • corrupted payload
  • wrong serializer/deserializer
  • incompatible schema
  • wrong topic consumed
  • producer bug

Expected handling:

  • capture topic/partition/offset/key/schema ID
  • route to DLQ if policy allows
  • alert if systemic

15.3 Unknown Enum

Expected handling:

  • map to UNKNOWN if supported
  • preserve raw value if possible
  • alert if value indicates incompatible rollout

15.4 Registry Latency/Outage

Consumer may fail if it needs registry lookup at runtime.

Mitigation:

  • schema cache
  • startup warmup
  • bounded timeout
  • fallback only if safe
  • registry health monitoring

16. Schema Governance and Event Catalog

Schema registry stores schema. Event catalog explains meaning and ownership.

Event catalog should include:

  • event name
  • business meaning
  • producer owner
  • consumer inventory
  • topic
  • key strategy
  • schema subject
  • compatibility mode
  • retention
  • replay policy
  • deprecation status
  • PII/sensitive field classification
  • example payload

Without event catalog, schema exists but governance is incomplete.

Schema tells shape. Catalog tells intent.


17. Security and Data Governance

Schema governance must include data sensitivity.

Questions:

  • Does event contain PII?
  • Is tenant ID sensitive?
  • Are price/currency/tax fields confidential?
  • Should payload be encrypted?
  • Are logs redacting event fields?
  • Does DLQ store sensitive payload longer than allowed?
  • Does schema registry expose field names that reveal sensitive domain info?

Event retention and DLQ retention must align with data retention policy.

A schema change that adds PII is not just a technical change. It affects logging, retention, access control, and compliance.


18. Date, Time, Currency, and Numeric Compatibility

Event schema must be strict about temporal and numeric fields.

Bad:

{ "price": 10.1 }

Better:

{
  "amount": "10.10",
  "currency": "USD",
  "roundingMode": "HALF_UP"
}

For date/time:

{
  "occurredAt": "2026-07-10T04:00:00Z",
  "effectiveFrom": "2026-08-01T00:00:00Z"
}

Avoid ambiguous local time unless domain explicitly requires it and includes timezone.

For CPQ/order systems, schema evolution around pricing/catalog effective dates is high risk.

Review:

  • decimal precision
  • currency code
  • rounding policy
  • timezone
  • effective date semantics
  • validity window semantics

19. Schema Version vs Event Version

Do not confuse:

schema version: shape of payload
business event version: semantic version of event contract
aggregate version: state sequence for one aggregate

Example:

{
  "eventType": "QuoteApproved",
  "eventVersion": 2,
  "aggregateVersion": 17
}

Schema registry may assign schema ID/version independently.

Senior review question:

Are we changing syntax, semantics, or aggregate sequence?

A field meaning change might not change schema shape but still requires event version/deprecation plan.


20. Breaking Change Playbook

If breaking change is unavoidable:

  1. Create new event type or version.
  2. Keep old event for deprecation window.
  3. Publish both if necessary.
  4. Update event catalog.
  5. Notify consumers.
  6. Add compatibility matrix.
  7. Add migration/replay plan.
  8. Monitor old consumer usage.
  9. Remove only after approval.

Example:

QuoteApproved v1
  approvedBy: string

QuoteApproved v2
  approval: { userId, role, channel }

Do not silently change approvedBy meaning.


21. JAX-RS + Event Contract Boundary

JAX-RS DTO and Kafka event schema should not automatically be the same object.

Bad:

public class QuoteApprovalRequest { ... }
producer.send(request);

Problem:

  • API request fields are command input, not business fact
  • API compatibility and event compatibility differ
  • request may contain transient fields not suitable for event
  • event needs envelope, aggregate version, causation ID

Better:

HTTP Request DTO
  -> command object
  -> domain state transition
  -> event object/schema
  -> Kafka record

Separate contracts:

  • OpenAPI for HTTP
  • AsyncAPI/event catalog for events
  • schema registry for serialization compatibility

22. AsyncAPI and Documentation

AsyncAPI can document:

  • topics/channels
  • message schema
  • headers
  • producer/consumer relationship
  • examples
  • security
  • bindings

It complements schema registry.

Schema registry validates payload compatibility. AsyncAPI documents integration contract. Event catalog documents ownership and semantics.

For enterprise governance, all three may exist:

Schema Registry -> technical schema
AsyncAPI        -> integration contract
Event Catalog   -> ownership and business meaning

23. PR Review Checklist

Schema Format

  • Which format is used: Avro, JSON Schema, Protobuf, raw JSON?
  • Is schema registered/versioned?
  • Is generator version pinned?
  • Is schema artifact published?

Compatibility

  • What compatibility mode applies?
  • Is change backward compatible?
  • Is change forward compatible?
  • Is transitive compatibility required?
  • Are old events replay-tested?

Field Changes

  • Are new fields optional/defaulted?
  • Are required fields added safely?
  • Are fields renamed or removed?
  • Are enum values added safely?
  • Are numeric/date fields precise and unambiguous?

Event Governance

  • Is event owner identified?
  • Is event catalog updated?
  • Are consumers known?
  • Is deprecation policy followed?
  • Is replay policy updated?

Security/Data

  • Does schema add PII/sensitive data?
  • Are logs/DLQ safe?
  • Is retention policy affected?
  • Is tenant isolation preserved?

Build/Runtime

  • Does CI validate compatibility?
  • Does runtime handle schema registry outage?
  • Are serializer/deserializer configs correct?
  • Are DLQ failures observable?

24. Internal Verification Checklist

Untuk codebase/platform internal, verifikasi:

Registry and Format

  • Apakah schema registry digunakan?
  • Registry vendor/platform apa?
  • Schema format: Avro, JSON Schema, Protobuf, custom JSON?
  • Subject naming strategy
  • Compatibility mode default
  • Compatibility override per subject/topic

Build and CI

  • Schema stored di repo mana?
  • Schema registered via CI atau runtime?
  • API lint/schema lint tool
  • Generated Java class strategy
  • Generated client/server strategy jika ada
  • Artifact versioning

Kafka Serialization

  • Serializer/deserializer config
  • Schema ID location
  • Header/content-type convention
  • Error handling untuk deserialization failure
  • DLQ behavior for bad schema

Event Governance

  • Event catalog
  • Event owner
  • Consumer inventory
  • Deprecation process
  • Breaking change approval
  • Compatibility matrix

Security and Operations

  • PII classification in schema
  • DLQ retention and access
  • Registry authn/authz
  • Registry availability/SLO
  • Monitoring for schema errors
  • Replay policy for old schema

25. Anti-Patterns

Anti-pattern 1 — Raw JSON Without Schema

Producer publishes arbitrary JSON.
Consumer parses with Map<String,Object>.

Dampak:

  • no compatibility gate
  • field typo becomes runtime bug
  • consumer failure discovered late

Anti-pattern 2 — Schema Registry Without Governance

Registry exists but:

  • no owner
  • no event catalog
  • compatibility set to NONE
  • schema registered manually

Registry alone is not governance.

Anti-pattern 3 — Reusing Protobuf Field Number

Dampak:

  • old payload interpreted as new field
  • silent data corruption

Anti-pattern 4 — Adding Required Field Directly

Dampak:

  • old producers fail
  • old events cannot replay

Anti-pattern 5 — Changing Field Meaning Without Schema Change

Example:

price used to mean pre-tax price
price now means post-tax price

Schema compatibility check passes, business compatibility fails.

Anti-pattern 6 — Event Schema Equals API DTO

Dampak:

  • transport command leaks into event fact
  • compatibility policies conflict
  • missing event metadata

Anti-pattern 7 — DLQ Replay Without Schema Version Awareness

Dampak:

  • old payload deserialized with wrong assumptions
  • repair creates more inconsistent state

26. Senior Mental Model

Event schema governance is not about choosing Avro vs Protobuf.

It is about controlling change across:

time:
  old events, new code, replay

services:
  producer, consumer, analytics, operations

protocols:
  Kafka, HTTP, gRPC, CDC

data governance:
  PII, retention, tenant boundary

release:
  rolling deploy, rollback, canary, compatibility gate

A schema that compiles can still be an unsafe contract.

Senior engineer reviews both:

syntactic compatibility
semantic compatibility

27. Summary

Di part ini kita membahas:

  • Kafka stores bytes, so schema governance is external
  • schema registry concept dan subject naming
  • Avro, JSON Schema, dan Protobuf trade-off
  • compatibility modes: backward, forward, full, transitive
  • schema evolution patterns
  • envelope vs payload schema
  • producer/consumer compatibility
  • compatibility matrix
  • generated code governance
  • CI/CD schema validation
  • runtime schema failure modes
  • event catalog, AsyncAPI, and governance
  • security, PII, retention, and replay concerns

Part berikutnya membahas event naming, ownership, versioning, deprecation, dan event catalog sebagai lifecycle governance di luar schema shape.

Lesson Recap

You just completed lesson 74 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.