.NET Backend Get Noticed 2017

Using interface explicit imlementation for creating a builder class

Last week, I finally started working on my „Get noticed” project called gifty. If you have absolutely no idea what I’m writing about you can go read my introduction to the project which I link here 😉 Anyway, one of the first things I wanted to do was to create some kind of ServiceBuilder for every microservice, since all of them (or at least majority) will have to go through some steps before they actually run. The steps are:

  1. Initialize Kestrel with Startup class
  2. Set proper port for service
  3. Register dependencies using Autofac
  4. Instantiate and connect to RabbitMq queue
  5. Subscribe to proper commands/events in RMQ queue
  6. Build
  7. Run

Now, for that kind of job, using some kind of a builder seems a natural solution (well, at least for me). What is important, is the fact that some steps need to be in the proper order to make the whole builder work. For instance, we can’t subscribe to the commands first and then instantiate the queue because that would cause null reference exception, and so on. One of the most common solutions for that kind of problems is using nested classed which are not accessible rather than via builder. But honestly, I’m not a big fan of nested classes (even though they’re handy in some cases). For my builder, I used interfaces and the „C# mechanism” which I described a few months ago – explicit implementation.If you have never heard about it, please read it since it’s important to understand the later code. Now, just to be clear, I’m not writing that my approach is better than others. It’s just different and for me personally – more clear. Ok, enough writing for now, let’s see the implementation 🙂

 

Implementing interfaces

So, I divided all above steps into three major groups, which are:

  1. ASP.NET Core related (steps: 1, 2, 6, 7)
  2. Autofac related (steps: 3)
  3. RabbitMq related (steps: 4, 5)

Then, I implemented three interfaces for each group as follows:

 


    public interface IServiceBuilder
    {
         IAutofacServiceBuilder WithPort(int port);
         void Run();
    }

 


    public interface IAutofacServiceBuilder
    {
         IRabbitMqServiceBuilder WithAutofac(Func<ContainerBuilder, ContainerBuilder> registrations);
    }

 


   public interface IRabbitMqServiceBuilder
    {
         IRabbitMqServiceBuilder WithRabbitMq(string queueName, string username, string password, int port);
         IRabbitMqServiceBuilder SubscribeToCommand<TCommand>() where TCommand : ICommand;
         IRabbitMqServiceBuilder SubscribeToEvent<TEvent>() where TEvent : IEvent;
         IServiceBuilder Build();
    }

 

 

Implementing ServiceBuilder

Having this done, I created a ServiceBuilder class which implements explicitly all three above interfaces:

 

    public class ServiceBuilder : IServiceBuilder, IAutofacServiceBuilder, IRabbitMqServiceBuilder
    {
        private IWebHostBuilder _webHostBuilder;

        private IWebHost _webHost;
        private ContainerBuilder _containerBuilder;
        private ICustomDependencyResolver _customDependencyResolver;
        private IBusClient _busClient;
        private string _queueName;

        public ServiceBuilder(IWebHostBuilder webHostBuilder)
        {
            _webHostBuilder = webHostBuilder;
        }

        public static IServiceBuilder CreateDefault<TStartup>() where TStartup : class
        {
            var webHostBuilder  = new WebHostBuilder()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseKestrel()
                .UseStartup<TStartup>();

            return new ServiceBuilder(webHostBuilder);
        }

        IAutofacServiceBuilder IServiceBuilder.WithPort(int port)
        {
            _webHostBuilder.UseUrls($"http://*:{port}");
            return this;
        }

        IRabbitMqServiceBuilder IAutofacServiceBuilder.WithAutofac(Func<ContainerBuilder,ContainerBuilder> registrations)
        {
            _containerBuilder = registrations(new ContainerBuilder());
            return this;
        }

        IRabbitMqServiceBuilder IRabbitMqServiceBuilder.WithRabbitMq(string queueName, string username, string password, int port)
        {
            _containerBuilder.RegisterRawRabbit(new RawRabbitConfiguration
            {
                Username = username,
                Password = username,
                Port = port,
                VirtualHost = "/", 
                Queue = new GeneralQueueConfiguration()
                {
                    Durable = true,
                },
                RequestTimeout = new TimeSpan(0, 10, 0)
            });

            _customDependencyResolver = new CustomDependencyResolver(_containerBuilder.Build());

            _busClient = BusClientFactory.CreateDefault();
            _queueName = queueName;

            return this;
        }

        IRabbitMqServiceBuilder IRabbitMqServiceBuilder.SubscribeToCommand<TCommand>()
        {
            _busClient.SubscribeAsync<TCommand>(async (command, context) => 
            {
                var commandHandler = _customDependencyResolver.Resolve<ICommandHandler<TCommand>>();
                await commandHandler.HandleAsync(command);
            }, cfg => cfg.WithQueue(q => q.WithName(_queueName)));

            return this;
        }

        IRabbitMqServiceBuilder IRabbitMqServiceBuilder.SubscribeToEvent<TEvent>()
        {
            _busClient.SubscribeAsync<TEvent>(async (@event, context) => 
            {
                var commandHandler = _customDependencyResolver.Resolve<IEventHandler<TEvent>>();
                await commandHandler.HandleAsync(@event);
            }, cfg => cfg.WithQueue(q => q.WithName(_queueName)));

            return this;
        }

        IServiceBuilder IRabbitMqServiceBuilder.Build()
        {
            _webHost = _webHostBuilder.Build();
            return this;
        }

        void IServiceBuilder.Run()
        {
            _webHost.Run();
        }
    }

 

The trick is simple. After each phase, we implicitly cast builder to another interface so its methods can be visible for us. That guarantees that we can’t, for instance, subscribe to command if autofac registrations were not specified in the previous phase, and so on. One thing that some of you might spot is that Build method is placed inside IRabbitMqServiceBuilder. Well, that because subscribes methods return their own interfaces which cause circular invoking. That’s why I decided to do this that way. Yet, another solution might be doing some „marker” method for moving to next phase like:

 


IServiceBus IRabbitMqServiceBus.EndSubscribe() => this;

 

So, that’s all for today. As mentioned before don’t treat this as the proper way of creating the builders. It’s just my way 😉

  • Pingback: dotnetomaniak.pl()

  • Another approach that can also frequently spotted is accepting an ‚Action’ in a method. This allows scoping all the configuration related to one feature and removes a lot of dots. Also, because it uses lamdba, you can capture it and execute later on when needed, which is not possible (without additional work) with fluent/dotted notation.


    builder.UseRabbit ( rabbit =>
    {
    rabbit.SubscribeToCommand();
    });

Don’t miss new posts!

If you enjoy reading my blog, follow me on Twitter or leave a like on Facebook. It costs nothing and will let you be up to date with new posts 🙂