rebus-org / Rebus.Autofac

:bus: Autofac container adapter for Rebus
https://mookid.dk/category/rebus
Other
12 stars 11 forks source link

Autofac deprecation of ContainerBuilder.Update #4

Closed matt-psaltis closed 6 years ago

matt-psaltis commented 6 years ago

The Autofac team are currently discussing alternatives to the use of ContainerBuilder.Update and this package will become incompatible at some stage in the future if the removal of the deprecated methods goes ahead.

The discussion can be found here: https://github.com/autofac/Autofac/issues/811

mookid8000 commented 6 years ago

I am foreseeing the need to re-structure the way Rebus is integrated with Autofac because of this.... there was a similar problen with the "Service Provider" container available in ASP.NET Core, where the Rebus configuration ended up being an extension method on the container with a callback.

The callback is then an Action<RebusConfigurer> which can then be used to continue the usual Rebus configuration spell, e.g. like this:

services.AddRebus(configure => configure
    .Logging(l => l.ColoredConsole())
    .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "Messages"))
    .Routing(r => r.TypeBased().MapAssemblyOf<Message1>("Messages")));

I am thinking that the same trick could be applied with Autofac too.

mookid8000 commented 6 years ago

I made an attempt at creating a new API – this test was used to check that it works... its usage could look like this:

// (1)
var builder = new ContainerBuilder();

// set up Rebus configuration callback
builder.AddRebus(configure => configure
    .Logging(l => l.Console(minLevel: LogLevel.Debug))
    .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "ioc-test")));

// build the container instance
var container = builder.Build();

// start the bus when you feel like it
container.UseRebus();

// remember to dispose the container to stop the bus
container.Dispose();

Looking at this now, I wonder why I followed the naming from Microsoft's ServiceProvider (Add*** followed by Use***) – I think it would be better if the naming was more idiomatic considering the selected container, e.g. more like this with Autofac:

// (2)
var builder = new ContainerBuilder();

// set up Rebus configuration callback
builder.RegisterRebus(configure => configure
    .Logging(l => l.Console(minLevel: LogLevel.Debug))
    .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "ioc-test")));

// build the container instance
var container = builder.Build();

// start the bus when you feel like it
container.StartBus();

// remember to dispose the container to stop the bus
container.Dispose();

Since Autofac provides a callback, which gets invoked when the container is built, the bus could easily be started automatically on builder.Build() – wouldn't that actually be preferable?

If that was done, the code could be reduced to this:

// (3)
var builder = new ContainerBuilder();

// set up Rebus configuration callback
builder.RegisterRebus(configure => configure
    .Logging(l => l.Console(minLevel: LogLevel.Debug))
    .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "ioc-test")));

// build the container instance and start the bus
var container = builder.Build();

// remember to dispose the container to stop the bus
container.Dispose();

I think I prefer option (3) – what do you think?

matt-psaltis commented 6 years ago

Definitely like the simplicity of option 3. Is the auto start of the container (edit: I mean bus!) using Autofac's AutoActivate feature? If so, the auto start of the container can be controlled via the builder.Build overload. I have some integration tests that selectively disable certain containers so having a way to do that is useful for my use case. That's the only question I have really, its tidy and simple - Nice work!

ronnyek commented 6 years ago

I think this makes sense too... this usage pattern isnt in the latest rebus.autofac, correct?

mookid8000 commented 6 years ago

I haven't got around to finishing it up yet, but I think it's actually only a matter of fixing the tests

mookid8000 commented 6 years ago

turns out I had a couple of hours this evening 😁

5.0.0-b01 is out in a few minutes with the new syntax:

var builder = new ContainerBuilder();

builder.RegisterRebus(configure => configure
    .Logging(l => l.Console(minLevel: LogLevel.Debug))
    .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "ioc-test")));

var container = builder.Build();

// program is running now
Console.WriteLine("Press ENTER to quit");
Console.ReadLine();

// always dispose container when program shuts down
container.Dispose();

I would be happy if someone well-versed in The Ways Of Autofac would try it out, and then I'll release 5.0.0 when someone says it's good

matt-psaltis commented 6 years ago

Very nice! Thanks @mookid8000. Not sure I've read 'The Ways Of Autofac' scroll yet, but I'll give the changes a try anyways! Thanks again

ronnyek commented 6 years ago

I just updated, and confirmed this is good to go.

mookid8000 commented 6 years ago

unless someone objects within the next 30 s I will release 5.0.0 of Rebus.Autofac.....

mookid8000 commented 6 years ago

too late, it's out now 😄

runes83 commented 6 years ago

A little late to the game but updated my nuget packages and got a new Rebus Autofac. Is there a new way to register the handlers ?

This was how I did it before: //_builder.RegisterAssemblyTypes(typeof(HeartBeatCommandHandler).Assembly) // .Where(x => typeof(IHandleMessages).IsAssignableFrom(x)) // .AsClosedTypesOf(typeof(IHandleMessages<>)) // .AsImplementedInterfaces() // .InstancePerDependency() // .PropertiesAutowired();

But now I'm getting ...could not be dispatched to any handlers.

mookid8000 commented 6 years ago

Oh, I didn't realize that the old package provided these extensions.

You should register your handlers as implementations of IHandleMessages<>, closing it with whichever message they handle – e.g. you would register HeartBeatCommandHandler as an implementation of IHandleMessages<HeartBeatCommand>

I am not an expert on Autofac, so I don't know if special configuration is required in order to have it resolved when the activator tries to resolve an IEnumerable<IHandleMessages<HeartBeatCommand>> (because that's what happens underneath the covers).

runes83 commented 6 years ago

Do I have to register each handler separate? That is a little backwards. This have always worked but after the upgrade to Rebus 4.1 and the newest Rebus.Autofac it went south :-(

It could also be that I've forgot something or dones something wrong in the new way to register Rebus this is my code:

`` private ContainerBuilder _builder; private void RegisterBus() { string queueAddress = Constants.SignatureQueueAddress; int numberOfWorkes = _appSettingsReader.NumberOfWorkers > 0 ? _appSettingsReader.NumberOfWorkers : 10; int maxParallelismFactor = 3;

if DEBUG

        queueAddress = queueAddress.ToDebugQueueAddress();
        numberOfWorkes = 1;
        maxParallelismFactor = 1;

endif

        if (_appSettingsReader.BetaSite)
            queueAddress += "-beta";

        var errorQueueAddress = $"{queueAddress}-error";

        _builder.RegisterAssemblyTypes(typeof(HeartBeatCommandHandler).Assembly)
            .Where(x => typeof(IHandleMessages).IsAssignableFrom(x))
            .AsClosedTypesOf(typeof(IHandleMessages<>))                
            .AsImplementedInterfaces()                
            .InstancePerDependency()
            .PropertiesAutowired();

        var cosmosSettings = _appSettingsReader.CosmosSettings;

        _builder.RegisterRebus(configure =>
            configure                
                .Transport(t => t.UseAzureServiceBus(_appSettingsReader.ServicebusConnectionString, queueAddress))
                .Sagas(
                    s =>
                        s.StoreInCosmosDb(new DocumentClient(new Uri(cosmosSettings.CollectionUrl), cosmosSettings.AuthKey, new ConnectionPolicy() { EnableEndpointDiscovery = false }),
                            _appSettingsReader.CosmosSettings.DataBaseId,
                            _appSettingsReader.CosmosSettings.CollectionId, true)
                )
                .Routing(r => r.TypeBased().MapAssemblyOf<HeartBeatCommand>(queueAddress))

                .Logging(l => l.Serilog())
                .Options(o =>
                {
                    o.SimpleRetryStrategy(secondLevelRetriesEnabled: true, maxDeliveryAttempts: 5,errorQueueAddress: errorQueueAddress);
                    o.EnableAutoScaling(numberOfWorkes, (numberOfWorkes * maxParallelismFactor));
                    o.EnableCompression( );

                o.EnableEncryption(_appSettingsReader.RebusEncryption);

                }));

    }

``

runes83 commented 6 years ago

@mookid8000 It looks the problem was sometinh else the Routing in Rebus does not take all messages in an assembly longer. Have two folders (and to subnamespaces) and then I have to map both like this:

.Routing(r => r.TypeBased().MapAssemblyOf(queueAddress).MapAssemblyOf(queueAddress))

Earlier the first mapping was sufficient.