Skip to content

One contract from browser to broker

At 09:17 on Monday, the dashboard displayed £0.00 for a payment that had settled for £42. The producer had renamed amount to value; the frontend still read yesterday’s shape.

That failure and a malformed event on a queue share one cause: a producer and consumer disagree about the data crossing between them.

The HTTP path

The payments microservice generates contracts/openapi.json from the Zod schemas it uses at runtime. A separately deployed frontend feeds that artifact to Orval and receives a typed fetch client. The frontend compiler now knows the request body, response body, error shapes, routes, and HTTP methods without importing backend source.

The compiler protects code written against the generated client. The microservice still validates each live request because browsers and older deployments can send values the current frontend would reject.

The event path

The same producer writes JSON Schemas to contracts/events/. A consumer validates the payload after its transport adapter removes the delivery envelope.

The adapter might read from a Kafka topic, subscribe to a NATS subject, poll an SQS queue, bind a RabbitMQ routing key, or receive an EventBridge event. Those systems disagree about routing, acknowledgements, retries, and envelopes. They can still carry the same payment.completed payload.

broker envelope -> transport adapter -> payment.completed payload -> JSON Schema

JSON Schema owns the payload contract. Broker configuration owns delivery. AsyncAPI can record channels and protocol bindings when the delivery contract also needs to be published.

The result

The frontend and event consumers use different generators, runtimes, and transports. They agree because each consumes a committed artifact rather than a copy of the producer’s code.

The next amount to value rename stops in CI. It never reaches the browser, and it never matters whether the event was waiting in Kafka, NATS, or SQS.