rebus-org / Rebus.ServiceProvider

:bus: Microsoft Extensions Dependency Injection container adapter for Rebus
https://mookid.dk/category/rebus
Other
65 stars 32 forks source link

Delayed startup #36

Closed axelgenus closed 3 years ago

axelgenus commented 3 years ago

It would be great to have support for Rebus delayed startup of Rebus with the .NET Core service provider.

I found the DelayedStartupConfigurationExtensions static class which seems to work that way. Is it possible to use something like that with .NET Core dependency injection extensions?

EDIT: Actually, I found out that the bus is started by means of the DelayedStartupConfigurationExtensions.Create method. The problem is that UseRebus extension method start the Rebus engine immediately. It is a bummer when working with docker-compose (service's dependencies usually takes longer to start than the service itself and the app throws a ResolutionExpection).

mookid8000 commented 3 years ago

Any reason why you not just delaying the call to serviceProvider.UseRebus()?

axelgenus commented 3 years ago

Any reason why you not just delaying the call to serviceProvider.UseRebus()?

According to the documentation serviceProvider.UseRebus() should be called in the Startup.Configure method. It should possible to initialize Rebus engine from a small middleware so that the connection to RabbitMQ is initialized only when the first request is received but... what is supposed to be the "best practice" here?

mookid8000 commented 3 years ago

It should possible to initialize Rebus engine from a small middleware so that the connection to RabbitMQ is initialized only when the first request is received but... what is supposed to be the "best practice" here

Well, it's entirely possible to do that if you want by taking over the configuration of Rebus yourself. The existing .AddRebus(....)/.UseRebus(...) methods are only there to encourage you to follow that pattern, but if your hosting environment insists on starting your service before it can connect to stuff, you'll probably need to work around that somehow.

One way could be to leverage Lazy<T> (available since .NET 4), wrapping the entire Rebus configuration in that:

var lazyBus = new Lazy<IBus>(() =>
    Configure.With(...)
        .Transport(t => t.(...))
        .Start()
);

// pass lazyBus around, get bus via
var bus = lazyBus.Value; //< will initialize the bus the first time it is accessed

(but please remember to dispose the bus somehow, when your program shuts down)

Do you think something like that would work?

axelgenus commented 3 years ago

Sorry for the delay. I was out-of-office and did not see your answer.

Currently we just make docker-compose restart the service on failure and this did the trick although I don't really like this approach. The major problem with the Lazy<IBus> you are suggesting is that we use Rebus as a service bus (pub-sub domain events) so we would end up processing queued domain events after the first call to lazyBus.Value. We are probably going not to use Rebus.ServiceProvider after all and handle the bus creation before the ASP.NET Core application is even started. In this way we can create the IBus instance when the backend is ready and then start the application injecting it in the service collection.

Thank you anyway. I'll publish a Gist or a small sample repo showing how we are going to solve this.

K3llr commented 3 years ago

I found myself in a similar situation. My service relies on an input queue and needs to be connected for incoming events. For this reason, I opted for a simple retry strategy (using Polly), rather than a lazy init.

Policy
.Handle<Rebus.Injection.ResolutionException>()
.WaitAndRetryForever(
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),    
    (exception, timespan, context) =>
    {
        // Add logic to be executed before each retry, such as logging
    })
.Execute(() =>
{
    app.ApplicationServices.UseRebus();    
});