Execution pipeline in plug-and-play architecture in Asp.NET 5

This blog post is the last of the series Plug-and-play architecture in Asp.NET 5 and we will see how we can actually implement the plug-and-play execution pipeline.

Execution context vs execution pipeline?

Execution context is the metadata that every request comes with. As we discussed in the previous blog post the context can hold data for a request id, principal, and everything else that we might need as part of each request.

source

Execution pipeline on the other hand is a series of steps that each request goes through. Depending on the configuration we usually see a pipeline in the MVC architecture as described in the image above. We need to implement a pipeline for both queries and commands that implicitly will do some things for us, like logging exceptions or logging commands/events.

First let's configure a pipeline interface that we will use to create the pipeline middlewares:

using PlugAndPlayExample.Infrastructure.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PlugAndPlayExample.Services.Infrastructure
{
    public interface IPipeline
    {
        TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response;
        IPipeline Next { get; set; }
    }
}

And lets create two different middlewares that we will use:

using PlugAndPlayExample.Infrastructure.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PlugAndPlayExample.Services.Infrastructure
{
    public class LoggingPipeline : IPipeline
    {
        public IPipeline Next { get; set; }

        private readonly ILogger logger;

        public LoggingPipeline(ILogger logger)
        {
            this.logger = logger;
        }

        public TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response
        {
            try
            {
                return Next.Execute(inner);
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message, ex);
                throw;
            }
        }
    }

    public class RunnerPipeline : IPipeline
    {
        public IPipeline Next { get; set; }

        public TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response
        {
            return inner();
        }
    }
}

The logging middleware executes a function and if it throws an exception, it logs it. The runner middleware just runs the function and we always use it as the last middleware with no Next property.

Now we need to implement the command and query middleware configuration and order:

using PlugAndPlayExample.Infrastructure.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PlugAndPlayExample.Services.Infrastructure
{
    public interface ICommandPipeline
    {
        TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response;
    }

    public interface IQueryPipeline
    {
        TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response;
    }

    public class CommandPipeline : ICommandPipeline
    {
        private List<IPipeline> pipelines;

        public CommandPipeline(IServiceProvider serviceProvider)
        {
            pipelines = new List<IPipeline>();
            pipelines.Add(serviceProvider.GetService(typeof(LoggingPipeline)) as LoggingPipeline);
            pipelines.Add(serviceProvider.GetService(typeof(RunnerPipeline)) as RunnerPipeline);

            for (int i = 0; i < pipelines.Count - 1; i++)
            {
                pipelines[i].Next = pipelines[i + 1];
            }
        }

        public TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response
        {
            return pipelines.FirstOrDefault()?.Execute(inner);
        }
    }

    public class QueryPipeline : IQueryPipeline
    {
        private List<IPipeline> pipelines;

        public QueryPipeline(IServiceProvider serviceProvider)
        {
            pipelines = new List<IPipeline>();
            pipelines.Add(serviceProvider.GetService(typeof(RunnerPipeline)) as RunnerPipeline);

            for (int i = 0; i < pipelines.Count - 1; i++)
            {
                pipelines[i].Next = pipelines[i + 1];
            }
        }

        public TResponse Execute<TResponse>(Func<TResponse> inner) where TResponse : Response
        {
            return pipelines.FirstOrDefault()?.Execute(inner);
        }
    }
}

We are finally ready to start using this middlewares in our mediator and have our plug and play architecture:

using System;

namespace PlugAndPlayExample.Services.Infrastructure
{
    public class Mediator
    {
        private readonly IServiceProvider serviceProvider;
        private readonly IQueryPipeline queryPipeline;
        private readonly ICommandPipeline commandPipeline;

        public Mediator(IServiceProvider serviceProvider, IQueryPipeline queryPipeline, ICommandPipeline commandPipeline)
        {
            this.serviceProvider = serviceProvider;
            this.queryPipeline = queryPipeline;
            this.commandPipeline = commandPipeline;
        }

        public TResult Dispatch<TCommand, TResult>(TCommand command)
            where TCommand : ICommand
            where TResult : Response
        {
            var commandHandler = serviceProvider.GetService(typeof(ICommandHandler<TCommand, TResult>)) as ICommandHandler<TCommand, TResult>;

            return commandPipeline.Execute(() => commandHandler.Handle(command));
        }

        public TResult Get<TQuery, TResult>(TQuery query)
            where TQuery : IQuery
            where TResult : Response
        {
            var queryHandler = serviceProvider.GetService(typeof(IQueryHandler<TQuery, TResult>)) as IQueryHandler<TQuery, TResult>;

            return queryPipeline.Execute(() => queryHandler.Handle(query));
        }
    }
}

Finally let's not forget to register everything in the service container:

using Microsoft.Extensions.DependencyInjection;
using PlugAndPlayExample.Infrastructure.Caching;
using PlugAndPlayExample.Infrastructure.Logging;
using PlugAndPlayExample.Services.Infrastructure;
using System.IO;

namespace PlugAndPlayExample.Configuration
{
    public static class RegisterInfrastructureAspectsExtension
    {
        public static IServiceCollection RegisterInfrastructureAspects(this IServiceCollection services)
        {
            services.AddSingleton<ICache, InMemoryCache>();
            services.AddSingleton<ILogger, FileLogger>(provider => new FileLogger(new FileInfo("my_log_file.log")));

            services.AddSingleton<IQueryPipeline, QueryPipeline>();
            services.AddSingleton<ICommandPipeline, CommandPipeline>();

            services.AddSingleton<RunnerPipeline>();
            services.AddSingleton<LoggingPipeline>();

            return services;
        }
    }
}

Hope you found this blog post helpful. 😄

Happy coding,
DotNetGuru

Sort:  

Congratulations @dotnetguru! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s):

You received more than 10 HP as payout for your posts and comments.
Your next payout target is 50 HP.
The unit is Hive Power equivalent because your rewards can be split into HP and HBD

You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Check out the last post from @hivebuzz:

Hive Power Up Month - Feedback from day 10
Support the HiveBuzz project. Vote for our proposal!

I see you are writing some pretty great and helpful posts. I suggest you leave some comments though, those are critical for long-term success here. I know you are having issues with resource credits right now, I see that you are currently at .50% which is tough but if you spend time on comments, people will be more willing to help you out with some delegation of credits so you can keep it up. Hopefully you can take that into consideration!

Thanks for the advice, I was on vacation for 6 days, but I will try to be more engaging with the community in the future. At this moment I'm preparing a post about the importance of algorithms and where to practice. I hope it will bring value to somebody. 😄

That's great, no worries! Vacation is critical in my opinion lol I'm glad you responded! If you run out of resource credits and the blockchain yells at you let me know and I can delegate you some more! It takes a few weeks to get enough credits to be able to post and comment consistently. Usually around 50 Hive Power is good enough for whatever you need to do.

Thanks a lot! I think I can manage and postpone my posts for a little bit until I get 50 HP. I'm still testing stuff on Hive to see what works best.

The thing I don't get is Reputation, I get it that if the poster has a negative reputation his posts are hidden, but if the reputation points are positive, does it make a difference if it is 20 or 70?

I wouldn’t pay too much attention to the reputation score. It’s a bit of a gamed system. It depends on the upvotes you get. If you get a lot of really strong upvotes worth like 10-20$ on your posts then your reputation goes up. Same is true for down votes, people who plagiarize stuff, harass people or beg for upvotes usually get down voted pretty fast and strong. This kills their “reputation” as it’s known. I would spend time on the content and the comments. The rest will come with time! Resource credits are a relatively new phenomenon but they are required. It’s a way to calculate the cost of a transaction on the chain and it helps a lot to reduce spam and stuff. You have to have a minimum amount of hive power to interact and if you are looking to be a jerk and spam all kinds of stuff you won’t get far unless you buy hive to do it. Most people won’t do that so they will spam for a little bit, get annoyed they can’t keep going and leave.

I’ve been here nearly 4 years and I’m still learning stuff so don’t sweat it! Lol

A good cadence if you're looking to post long stuff is once a week.

If you are out there looking to comment, the Ecency front end is the best. You can interact with people on the chain with votes and comments and things and that earns you points which you can use to "boost" (vote) on your posts. A boost can be anywhere from 1$ to like 5$ but the owners of Ecency manually review the boost requests. It's a really good way to help get yourself some hive power and it helps reward you for doing good things like commenting!