Build1 distinct publisher3 min readPublished
A settlement engineer trades the transactional outbox for producer-side causality plus consumer-side dedup. The reasoning holds up, and the half that carries it is the half not shown.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The two directions of failure are not symmetric, and the first layer only closes one of them. Publishing from `afterCommit()` rules out the message that describes work which never happened: if the business transaction rolls back, nothing is ever published, so no consumer needs an unpublish path or compensating logic [13]. The outbox closes the opposite direction, the event that should exist and does not, by making the intent to publish atomic with the state change and handing delivery to a process that retries until the broker acks [7]. Filing both under "reliability" hides that they are different products.
The remaining hole has two halves and only one has an owner. Spring Kafka's producer retries a send when no acknowledgment comes back [16], which covers a stalled broker or network. The consumer-side layer absorbs the duplicates those retries create [15]. Neither emits a message that was never sent because the process died after the commit [18]. The design therefore rests on the assertion that this case is rare and cheap to detect after the fact [11], and detection is not recovery.
The author's own counterexample is the most useful thing in the piece. He ran an outbox on the Open Finance Brasil report service because audit correctness on consent events was not negotiable [8], and declined it here [12]. The property separating the two is whether the record left behind is enough to reconstruct what downstream never heard, and in this system the batch row and the order statuses are both written in the same transaction [14]. That is the concrete version of "cheap to detect": the evidence of a missed publish is already persisted.
One line in the cost accounting deserves pressure. The outbox is charged as a second scheduler plus a poller or CDC pipeline plus a relay, each with its own monitoring and failure handling [10]. But this system already runs a cold-path scheduler, because the endpoint was reduced to write-and-forget and all the window semantics moved there [1]. The genuinely new machinery is a poller job on a tier that already exists, plus the relay's error handling [20]. That does not make the trade wrong. It makes it smaller than argued, which cuts against the operational-surface case being the decisive one.
It is worth being clear about where the published text stops: it breaks off mid-sentence just after introducing producer retry, before the consumer deduplication mechanism is specified [19]. The idempotency key, and how long the consumer remembers it, is the part doing the real work, and it is the part not on the page. Anyone lifting this pattern is lifting an argument, not an implementation.
"Use an outbox" persists as the default [4] because it is safe counsel for a team that has priced neither side. This write-up prices one side honestly. The teams that can skip the outbox are the ones that can state the detection path before the incident, and that is exactly the section still missing.
Ranked by verification strength, evidence, and original report placement.
The author's earlier write-up describes inverting the control flow of a settlement API: the endpoint became a pure write-and-forget operation and the coordination logic (batching, cutoffs, retries) moved to a cold-path scheduler.
A responding engineer pressed on what happens to the message between the moment the database transaction commits and the moment the broker acknowledges it.
The author's answer was a two-layer idempotency model instead of a transactional outbox, described as an under-discussed trade-off.
Most write-ups on reliable event publishing jump straight to "use an outbox", treat it as a solved problem and move on, according to the author.
Publishing the event inside the database transaction risks publishing an event for work that later rolls back.
Publishing after the commit, in something like Spring's afterCommit(), closes the rollback hole but opens a window in which the process can crash or the network can fail between the commit finishing and the broker acknowledging, leaving the database saying the work happened while nothing downstream knows.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
First-hand code, no measurement
The mechanism claims are unusually well grounded for a blog post: two code blocks show the producer's TransactionSynchronization/afterCommit path and the consumer's SHA-256-into-Redis dedup, and the outbox description is standard and uncontested. But the load-bearing decision premises are bare assertions: no incident data for 'rare', no mechanism for 'cheap to detect after the fact', no producer or consumer configuration, and no latency or throughput numbers against the settlement window. One ledger claim about the text is also contradicted by the supplied body, and there is exactly one source with no independent corroboration.
Two self-reported deployments
Adoption evidence is limited to one engineer's disclosures: the two-layer design running in an STR-based settlement system and the outbox running on an Open Finance Brasil report service. No third-party deployments, no user counts, no maintainer or vendor telemetry, and no release or benchmark artifacts. The underlying components (Spring Kafka, Redis, the outbox pattern) are widely used, but this cluster supplies no evidence of the specific two-layer approach being adopted beyond the author's own systems.
Mildly overstated
The post is deliberately modest in register: it praises the outbox, concedes the correspondent's latency point, and frames the decision as a trade-off rather than a recipe. The overstatement is narrow but real. The title generalises from one unverified system to 'not every settlement system needs an outbox'; 'effectively-once processing' rests on a Redis check whose TTL, availability and collision behaviour are undiscussed; the rare-and-cheap-to-detect premise carries the argument without evidence; the residue where a process dies after commit before any successful send is never named; and the 'second scheduler' cost is counted even though the system already runs a cold-path scheduling tier.
Author defending own design
The disclosed incentive is professional: a named engineer publishing on dev.to defends an architecture decision he made and answers a critic in public, which favours framings where the chosen design comes out justified. There is no vendor, sponsor, product, funding or commercial relationship disclosed or visible, no tool being sold, and the post recommends a competing pattern for a different workload, all of which caps the distortion pressure well below promotional levels.
Low-moderate
Mechanism-level confidence is fair because the code is shown and matches well-known Spring and Kafka behaviour, but cluster-level confidence is limited by one source from one publisher, entirely self-reported, with no numbers behind the decision premises, no external commentary, a body that truncates mid-sentence before its conclusion, and one ledger claim that the body contradicts.
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
build
Four control planes, one Postgres: a team's case against polyglot persistence1 distinct publisher
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
build
The /userinfo fallback that quietly made Auth0 a hard dependency on every request1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 25, 2026