Skip to content

Events from Avro and protobuf

document:docs/events/avro-protobuf

Passive discovery over .avsc and .proto files, with the wire-format semantics kept intact.

View as Markdown

Both are read by ac scan with no configuration and no code execution:

Terminal window
ac scan . --out ./events

Every .avsc holding a record becomes an event. Only .avsc — an Avro schema is legal JSON, but so is everything else in a repository, and guessing which .json files are Avro is how a scan starts inventing events.

AvroJSON Schema
recordobject with properties
["null", "string"]string, and the field is optional
enumstring with enum
array / maparray / object with additionalProperties
long + logicalTypethe matching format (date-time, uuid)
fixed, bytesstring

A field is optional when it is a null union or carries a default. Everything else is required — Avro has no implicit default, so a field without one must be written. Note this differs from protobuf, where repeated fields default to empty.

Identity comes from the record name (com.acme.PaymentCompleted → payment.completed), the domain from the namespace. Declare either explicitly with an x-autocatalog block in the schema to clear needsReview.

Messages become events, with two deliberate exclusions:

  • Messages wired to an RPC. A request/response pair is a call, not a message anyone subscribes to. Publishing both halves of every gRPC method is how a catalog fills with things nobody emits.
  • Nested messages. They are the structure of their parent, not events.

Types follow proto3’s canonical JSON mapping, which is what actually travels on a bus — so int64 is a string, not a number, because JSON cannot hold it. google.protobuf.Timestamp becomes date-time.

Required-ness follows proto3 presence: a singular field without optional always decodes to a value, so it is always in the payload a consumer sees; optional, repeated and map fields may be absent or empty.

Declare identity with an option on the message:

message OrderPlaced {
option (autocatalog.id) = "order.placed";
option (autocatalog.version) = "2.0.0";
string order_id = 1;
}

Imports are not resolved off disk. A type from another file scans as an empty schema rather than a guess — the catalog says it does not know instead of pretending.

Owned by MaintainersLast changed 12 Oct 2026SourceEdit this page