This is interesting, but you've not actually solved the problem, just moved it. You still need cross-service transactions to publish the event only-once. Consider the case of "Publish the event to the queue. Fail to update / delete the entry in the event-buffer table." This is the "bad" pattern of push-then-store (with one exception - if you are fine with at-least-once message delivery instead of only-once). Likewise the "good" pattern of store-then-push has the same failure mode "Delete the entry from the buffer. Fail to publish the entry to the queue".
That said, this does decouple the two operations which allows you to scale the publish side of the service separately from produce side (which can help when your architecture can produce multiple messages per storage event)
Maybe you are using different terminology, because IMO you don't need "cross-service transactions to publish the event only-once"
"Exactly-once" message processing is what you typically want in business operations, and that is straightforward to achieve with "at-least-once" message delivery:
1. send(message with unique .id):
a. insert into DB queue where not exists message.id
2. dispatcher:
a. fetch messages from DB queue where unpublished = 1
b. publish message to integration queue [at-least-once-delivery]
c. update message set unpublished = 0 (and ideally record some audit information e.g. the sent timestamp + Kafka message partition/offset or IBM MQ message ID)
3. receive(message):
a. check if message.id has been processed [exactly-once-processing]
b. perform operation, including linking it back to message.id, in a single DB transaction
If failure occurs:
1. between 2.a and 2.b, you fetch messages again and nothing is lost
2. between 2.b and 2.c, you republish the same message; 3.a ensures exactly-once *processing*
3. between 3.a and 3.b, you retry the message (with IBM MQ, you use transactions or peek then read; with Kafka, you don't update offsets until 3.b completes)
4. on the integration queue: set unpublished = 1 on all messages published sent in the timeframe where integration queue lost data
In practice: SQL DBs will surprise you with lock and concurrency semantics; I suggest using something battle tested under high load, or an event store database.
In a sense, this is a "cross-service transaction", but it is not a "transaction protocol", rather it is an eventually consistent design.
> You still need cross-service transactions to publish the event only-once. Consider the case of "Publish the event to the queue. Fail to update / delete the entry in the event-buffer table."
I'm not sure this is the problem the author has set out to solve. Exactly once dispatching is really hard and requires much more than what's written about here. Even though the system the author wrote about is at-least-once, it guarantees an event isn't dispatched for data that's not stored or that data is stored for an event that's never dispatched.
That said, this does decouple the two operations which allows you to scale the publish side of the service separately from produce side (which can help when your architecture can produce multiple messages per storage event)