Best way to ensure an event is eventually published to a message queuing sytem
You can make the this.bus.Publish
call part of a database transaction of the this.SaveOrderToDataBase
. This means that this.SaveOrderToDataBase
executes in transaction scope and if the db call fails you never call the mq and if the mq call fails then you roll back the db transaction leaving both systems in a consistent state. If both calls succeed you commit the 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 didn't mention any specific db technology so here's a link to a wiki article on transactions. Even if you're new to transactions, it's a good place to start. And a bit of good news: They are not hard to implement.
The correct way to ensure the event is eventually published to the message queuing sytem is explained in this video and on this blog post
Basically you need to store the message to be sent into the database in the same transaction you perform the bussines logic operation, then send 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();
}
}
Because PublishEventAsync may fail you have to retry later, so you need a background process for retrying failed sendings, something like this:
foreach (ev in eventsThatNeedsToBeSent) {
await PublishEventAsync(ev);
}