Search code examples
c#asp.net-coreautofacextension-methodschain

Extension method to chain type registrations in Autofac as options


I am trying to implement some syntactic sugar around the registration of multiple things with Autofac but I'm stuck and I don't know whether this has some design pattern name where I could search some information for.

I have something like this in my main project:

protected override void Load(ContainerBuilder builder)
{
    var busBuilder =
        new BusBuilder()
            .RegisterHandler<EventOne, DomainEventHandlerAdapter<EventOne>>()
            .RegisterHandler<EventTwo, DomainEventHandlerAdapter<EventTwo>>()
            .RegisterHandler<EventThree, DomainEventHandlerAdapter<EventThree>>();

    builder
        .RegisterMicroBus(busBuilder);

    builder
        .RegisterType<MicroBusDomainEventBus>()
        .As<IDomainEventBus>();
}

I want to refactor this, because I don't want my main project to have a dependency to that specific NuGet package (the Enexure Microbus package). So I would like to have in my main project something like this:

protected override void Load(ContainerBuilder builder)
{
    builder.RegisterMyMicrobus(eventOptions => {
        eventOptions
            .RegisterDomainHandler<EventOne>()
            .RegisterDomainHandler<EventTwo>()
            .RegisterDomainHandler<EventThree>();
    });
}

so that my main project knows nothing about BusBuilder, DomainEventHandlerAdapter or RegisterMicroBus and has no dependency on that specific technology (Enexure Microbus). All the Autofac registration would be handled by my other project that is the only one with a dependency to the specific technology.

Here is what I have done so far on my other project:

I have created an extension method on Autofac's ContainerBuilder where I will pass the eventOptions, which would be a delegate (lambda expression) Action<EventOption>

public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
    var busBuilder = new BusBuilder();

    if (eventOptions != null)
    {
        // TODO, how to implement EventOption and the chain registration?
    }

    builder
        .RegisterMicroBus(busBuilder);

    builder
        .RegisterType<MicroBusDomainEventBus>()
        .As<IDomainEventBus>();
}

Any link or help would be greatly appreciated. This pattern must have a name, but no idea how is it called and I don't have experience implementing chain methods.

UPDATE 1: Following the advice of Erndob I had a look at BusBuilder. But again I reached a point where I am stuck.

I have created EventOption as follows:

public class EventOption
{
    public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    private readonly List<IDomainEvent> _domainEvents= new List<IDomainEvent>();

    public EventOption RegisterDomainHandler(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
        return this;
    }
}

but now the problem is with the extension method, because I can retrieve the list of events

public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
    var busBuilder = new BusBuilder();

    // use busBuilder to register each one of the domain events in options

    var eventOption = new EventOption();
    eventOptions?.Invoke(eventOption);

    foreach (var domainEvent in eventOption.DomainEvents)
    {
         var eventType = domainEvent.GetType();

         // HERE is the problem. How to RegisterHandler using this generic method?
         busBuilder
                .RegisterHandler<eventType, DomainEventHandlerAdapter<eventType>>();
        }

    builder
        .RegisterMicroBus(busBuilder);

    builder
        .RegisterType<MicroBusDomainEventBus>()
        .As<IDomainEventBus>();
}

It won't compile because I am trying to use a variable as a Type (RegisterHandler is a generic). I can't find a way to convert my variable into a type to satisfy that call. Any idea or a different way to achieve the same? Thanks again

UPDATE 2: I have changed the approach and it seems it could work. It compiles and it registers everything, but I have to test further.

I have modified EventOption to store Types:

public class EventOption
{
    public IReadOnlyList<Type> Types => _types.AsReadOnly();

    private readonly List<Type> _types = new List<Type>();

    public DomainCommandOptions RegisterCommandHandler<TDomainCommand>()
        where TDomainCommand : IDomainCommand
    {
        var domainCommandType = typeof(TDomainCommand);
        _types.Add(domainCommandType);
        return this;
    }
}

and then the extension method is now different, because it iterates over the types and at runtime it creates a generic type. I discovered there is a HandlerRegistration from Microbus I can use directly.

public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
    var busBuilder = new BusBuilder();

    // use busBuilder to register each one of the domain events in options

    var eventOption = new EventOption();
    eventOptions?.Invoke(eventOption);

    foreach (var type in eventOption.Types)
    {
         var handlerRegistration = new HandlerRegistration(type, typeof(DomainEventHandlerAdapter<>).MakeGenericType(type));
         busBuilder.RegisterMessage(handlerRegistration);
        }

    builder
        .RegisterMicroBus(busBuilder);

    builder
        .RegisterType<MicroBusDomainEventBus>()
        .As<IDomainEventBus>();
}

Solution

  • Have a look at the source code of the project you are using. The BusBuilder, the thing that chains, has a pattern name in it's name. A builder. The repo itself says:

    MicroBus is a simple in process mediator for .NET

    So MicroBus is a Mediator. Have a look at a mediator pattern.

    Here's how the BusBuilder.cs looks: https://github.com/Lavinski/Enexure.MicroBus/blob/master/src/Enexure.MicroBus/BusBuilder.cs

    The building itself is essentially done like this:

    public BusBuilder RegisterHandler<TMessage, TMessageHandler>() 
                                      where TMessageHandler : IMessageHandler<TMessage, Unit>
            {
                registrations.Add(HandlerRegistration.New<TMessage, TMessageHandler>());
                return this;
            }
    

    As you can see, the instance has a list of registrations, then when you want to register something new, it just adds to the list and then returns itself, letting you to chain a new registration.