The best way to ensure that the event is eventually published to the message queuing system

Please imagine you have a method similar to the following:

public void PlaceOrder(Order order)
{
     this.SaveOrderToDataBase(order);
     this.bus.Publish(new OrderPlaced(Order));    
}

      

After the order is stored in the database, an event is posted to the message queuing system so that other subsystems on the same or a different computer can process it.

But what happens if the call this.bus.Publish(new OrderPlaced(Order))

fails? Or will the machine work right after the order is saved in the database? The event is not published and other subsystems cannot process it. This is unacceptable. If this happens, I need to make sure the event is eventually posted.

What are the acceptable strategies I can use? Which one is better?

NOTE. I don't want to use distributed transactions.

EDIT:

Paul Sasik is very close and I think I can reach 100%. Here's what I thought:

first create the Events table in the database like this:

CREATE TABLE Events (EventId int PRIMARY KEY)

      

You can use hints instead of int, or you can use sequences or identifiers.

Then execute the following pseudocode:

open transaction
save order and event via A SINGLE transaction
in case of failure, report error and return
place order in message queue
in case of failure, report error, roll back transaction and return
commit transaction

      

All events must include an EventId. When event subscribers receive an event, they first check for the existence of the EventId in the database.

So you get 100% likelihood, not just 99.999%

+3


source to share


2 answers


The correct way to ensure that an event is eventually posted to the message queuing system is explained in this video and on this blog post

Basically you need to store the menssage to post to the database in the same transaction you are doing the bussines boolean operation, then post the message to the bus asynchronously and delete the message from the database in another transaction:

public void PlaceOrder(Order order)
{
     BeginTransaction();
     Try 
     {
         SaveOrderToDataBase(order);
         ev = new OrderPlaced(Order);
         SaveEventToDataBase(ev);
         CommitTransaction();
     }
     Catch 
     {
          RollbackTransaction();
          return;
     }

     PublishEventAsync(ev);    
}

async Task PublishEventAsync(BussinesEvent ev) 
{
    BegintTransaction();
    try 
    {
         await DeleteEventAsync(ev);
         await bus.PublishAsync(ev);
         CommitTransaction();
    }
    catch 
    {
         RollbackTransaction();
    }

}

      



Since PublishEventAsync might not work, you will have to try again later, so you need a background process to retry failed submissions, something like this:

foreach (ev in eventsThatNeedsToBeSent) {
    await PublishEventAsync(ev);
}

      

0


source


You can make part of the call this.bus.Publish

for a database transaction this.SaveOrderToDataBase

. This means it this.SaveOrderToDataBase

is executed in the scope of the transaction, and if the db call fails, you never call mq, and if the mq call fails, you roll back the db transaction, leaving both systems in a consistent state. If both calls are successful, you commit db transaction.

pseudocode:



open transaction
save order via transaction
in case of failure, report error and return
place order in message queue
in case of failure, report error, roll back transaction and return
commit transaction

      

You haven't mentioned any specific db technology, so here's a link to the wiki article on transactions . Even if you're new to transactions, this is a good place to start. And some good news: they are not hard to implement.

+3


source







All Articles