rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.32k stars 363 forks source link

Outbox transaction always commits with second level retries enabled #1162

Closed xriabov closed 6 months ago

xriabov commented 6 months ago

Hi. When using an outbox with second-level retries changes to the database are committed if the handler fails but a second-level retry succeeds.

Here's an example

public class Example : IHandleMessages<ExampleMessage>, IHandleMessages<IFailed<ExampleMessage>>
    {
        public async Task Handle(ExampleMessage message)
        {
            // Connect the DB and do some work
            ...

            // unexpected error after .SaveChanges()
            throw new Exception();

            _bus.Reply(messageResponse);
        }

        public async Task Handle(IFailed<ExampleMessage> message)
        {
            // Some processing happens here
        }
    }

After the exception is thrown the second Handle method is executed, and after it's finished, the changes are present in the database.

This probably happens due to a transaction being shared between the two and the successful execution of a second-level handler commits it in DefaultRetryStep.cs

Is it an expected behavior or maybe I'm missing some configuration? Is there a workaround to both use an outbox and a second-level retries?

Thanks in advance!

mookid8000 commented 6 months ago

While this is a little bit un-intuitive, it's the expected behavior.

There is no way with Rebus' current outbox implementation to change this behavior, unfortunately.

I am planning on removing Rebus' outbox functionality again in a future version, because I have come to the realization that "outbox" is a big enough concern to warrant its own project. It also makes more sense that way, because "the outbox" as a concept is closer to your choice of model persistence (e.g. Microsoft SQL Server) than to your service bus needs, and an outbox can be used for other things besides sending messages with a service bus.

I have started Freakout as an implementation of an outbox that simply allows for queueing commands to be processed as part of your SQL transaction. It works for MSSQL and Postgres at the moment, but it should be easy to add additional providers.

Some time later I will of course build an integration (e.g. something like "Rebus.Freakout") that enables Rebus to automatically hook itself up with Freakout.

While this was probably a much longer answer than you expected/hoped for 😅 I hope you understand my reasoning.