Go architectures · 3 MIN READ
Event-driven services: delivery and idempotency
Treat duplicate delivery as normal and define the durable boundary.
Treat duplicate delivery as normal and define the durable boundary. Event-driven systems decouple a producer from consumers through notifications or durable messages. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Architecture · A MAP OF THE IDEA
- Producer commits an event or outbox record
- Broker may redeliver
- Consumer atomically records its effect
Useful when
- Independent reactions should not block a request
- Bursty work benefits from durable buffering
Watch out
- Delivery duplicates and reordering need policies
- Debugging spans queues and consumers
Implementation & operation cost: High
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
Event-driven systems decouple a producer from consumers through notifications or durable messages. Consumers should know what happened and decide their own reaction. Delivery can repeat, so the design needs event identities, ordering rules and a durable acknowledgment strategy.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"fmt"
)
type Consumer struct {
seen map[string]bool
count int
}
func (c *Consumer) Handle(id string) {
if c.seen[id] {
return
}
c.seen[id] = true
c.count++
}
func main() {
c := Consumer{seen: make(map[string]bool)}
c.Handle("event-1")
c.Handle("event-1")
c.Handle("event-2")
fmt.Println(c.count)
}
// Output:
// 2
Two deliveries of event-1 create one effect. This single-goroutine memory map demonstrates deduplication only. Production deduplication must commit the event identity and business effect atomically; otherwise a crash can lose work or repeat the effect.
Tradeoffs and the simpler alternative
Buffering can isolate timing, but it does not guarantee business completion. Define dead-letter handling, retry limits and observability. A transactional outbox can bridge a database commit and later publication; a plain goroutine cannot replace durable messaging.
Specify the delivery contract
A producer commits a business change and an outbox record in one transaction. A relay publishes the record and may publish it again after a crash. The consumer therefore needs a stable event identifier and an idempotent effect. Acknowledge only after the required durable work. At-least-once delivery is not exactly-once business processing: a payment gateway, email API and local store do not share one magical commit.
Choose ordering, retries and a poison-message path
Order events only where the business needs it, often per aggregate rather than globally. Bound retries with backoff and jitter, and route repeated failures to a monitored dead-letter path with a replay procedure. Distinguish invalid events from temporary infrastructure outages. A permanently malformed message should not block unrelated work forever. Version event schemas compatibly and include correlation identifiers without leaking tokens or personal payloads into logs.
Exercise the crash windows
Test a crash after database commit but before publish, after publish but before marking the outbox sent, and after the consumer effect but before acknowledgement. Verify duplicates do not repeat the effect and that lag is visible in metrics. Monitor oldest unprocessed age, backlog and failure count, not only whether the worker process is alive. Use events for meaningful decoupling; a synchronous local call is clearer when a caller needs an immediate answer and no durable handoff.
TAKE THIS WITH YOU
Repeating a message does not repeat its business effect. Buffering can isolate timing, but it does not guarantee business completion. Define dead-letter handling, retry limits and observability. A transactional outbox can bridge a database commit and later publication; a plain goroutine cannot replace durable messaging.
Check your understanding
Which statement best describes Event-driven services: delivery and idempotency?
Go to the source
Progress stays in this browser. No account needed.