Write the domain event to an outbox table in the same database transaction as the business entity write. A separate relay service polls the outbox and publishes unpublished events to Kafka, then marks them as published. This guarantees the event is eventually published even if the app crashes between the write and the publish.
The event and business entity are written atomically — no event is lost even if the process crashes after the DB write.
The relay is idempotent — if it crashes mid-relay, unpublished events are picked up on the next poll.
Use a distributed lock (Redis SETNX) if multiple relay instances run concurrently to prevent duplicate publishes.
Mark events as published after Kafka confirms receipt — not before.
Add a dead-letter column for events that fail repeatedly so operators can inspect and replay them.