Add outbox module: Transactional Outbox with the Spring Modulith Event Publication Registry
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
$ mvn -pl outbox -am test -Dtest=DualWriteProblemTest
|
||||
(NaiveBillingService: save the Invoice, then separately call kafka.send() -- no
|
||||
coordinator between the two. spring.kafka.bootstrap-servers points at localhost:19999,
|
||||
nothing listening there)
|
||||
|
||||
2026-10-04T00:52:56.429+05:30 ERROR 5271 --- [outbox] [ main] o.s.k.support.LoggingProducerListener : Exception thrown when sending a message with key='1' and payload='WIDGET-1:1999' to topic invoices-naive:
|
||||
|
||||
org.apache.kafka.common.errors.TimeoutException: Topic invoices-naive not present in metadata after 2000 ms.
|
||||
|
||||
Exception thrown synchronously from issueInvoiceTheNaiveWay(): org.springframework.kafka.KafkaException: Send failed
|
||||
Invoice count regardless: 1 (was 0). The database write already committed inside saveInvoiceOnly() before this exception was ever thrown -- the exception came from the Kafka call, several lines later, on an already-committed row.
|
||||
|
||||
2026-10-04T00:52:58.495+05:30 ERROR 5271 --- [outbox] [ main] o.s.k.support.LoggingProducerListener : Exception thrown when sending a message with key='2' and payload='WIDGET-1:2999' to topic invoices-naive:
|
||||
|
||||
org.apache.kafka.common.errors.TimeoutException: Topic invoices-naive not present in metadata after 2000 ms.
|
||||
|
||||
Invoice count after the blocked, failed send: 2 (was 1). The row committed before the send was even attempted.
|
||||
|
||||
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
|
||||
|
||||
Two real findings here, not one. First, the obvious one: the invoice exists in the
|
||||
database in both tests, and Kafka never received either message -- that's the dual-write
|
||||
problem, reproduced with nothing faked beyond pointing Kafka at a port with no listener.
|
||||
|
||||
Second, a more specific one that contradicts the usual "fire-and-forget send() fails
|
||||
silently" framing: when the broker can't be reached to fetch topic metadata at all,
|
||||
Spring's KafkaTemplate.send() throws synchronously, wrapping the Kafka client's own
|
||||
TimeoutException in a KafkaException, even though the method's declared return type is
|
||||
a CompletableFuture. The "silent" version of this bug is real, but it is a narrower
|
||||
case than total broker unavailability -- it's what happens when the broker is reachable
|
||||
and the topic's metadata is known, but the specific produce request times out waiting
|
||||
for an acknowledgment afterward. That failure mode surfaces only through the returned
|
||||
future or a callback, never as a synchronous exception, and is not reproduced in this
|
||||
transcript (it needs a broker that accepts the connection and then stops responding
|
||||
mid-request, not one that was never there to begin with).
|
||||
Reference in New Issue
Block a user