Product Events
Shipping code is half of contributing to a product flow. The other half is the data it emits, which is how anyone finds out whether the change did what you intended.
The schema is a published interface
An event name and its field types are an API. The moment an event reaches the stream, something downstream depends on its shape: a dashboard, an alert, an experiment readout, or a model feature.
This makes ordinary-looking edits into breaking changes:
| Change | Safe? | Why |
|---|---|---|
| Add a new optional field | Yes | Existing consumers ignore it |
| Add a new event type | Yes | Nothing depends on it yet |
| Rename a field | No | Every consumer referencing it silently loses the column |
| Change a field’s type | No | Parsing fails, or values coerce wrong and stay plausible |
| Remove a field | No | Same as a rename, without even a new name to migrate to |
| Change what a field means | No, and worst of all | Nothing fails; history becomes quietly incomparable |
That last row deserves the emphasis. Redefining signup_completed from “account
created” to “account created and email verified” breaks no check anywhere, and every
trend line that crosses your deploy becomes a lie. If the meaning changes, emit a new
event.
Validation happens at collection, not at query time
The schema registry checks each event against its registered contract before it is accepted into the stream. This is a deliberate choice about where to pay the cost.
Validating on read would mean the bad data is already in the log, already in the raw tables, already in yesterday’s dashboard — and every consumer has to defend against it independently, forever. Validating on write means one system rejects it once.
Rejected is not lost
Events that fail validation go to quarantine with their full payload and the reason they were rejected. The recovery is:
- Fix the emit site — usually a field type or a missing required field.
- Deploy the fix.
- Replay the quarantine window.
Where your events end up
- Raw tables are append-only and never edited in place. They are the audit trail: if a transform is wrong, the raw layer is what you recompute from.
- Modelled marts are the tested transform layer. Analysts and dashboards build here, not on raw, because raw has no guarantees about naming, deduplication, or late arrivals.
- Experiment readouts join your events against assignment data. This is where a feature rollout is actually judged — which is why the event has to exist before the rollout starts, not after someone asks how it went.
A practical checklist
- Register the schema before the code that emits it ships.
- Emit from the server where you can; client events are subject to ad blockers, offline buffering, and clock skew.
- Include the identifiers a join will need. An event nobody can join to a user or a session is close to unusable.
- Name events after what happened (
checkout_submitted), not after where the code is (handler_v2_called).