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

As we discussed the CQRS architecture is made out of two distinct service handlers, the command handler, and the query handler. Although they have different functionality, they still have the same execution context usually having an active principal, a request ID, and all explicit and hybrid aspects that a command or a query might use.

In order to implement the execution context we need to keep in mind several things:

  • It might change
  • It might grow
  • Should not break old functionality

Command and query execution context implementation might look like this

using PlugAndPlayExample.Infrastructure.Caching;
using PlugAndPlayExample.Infrastructure.Logging;
using System;

namespace PlugAndPlayExample.Services.Infrastructure
{
    public class Context
    {
        public Context()
        {
            RequestId = Guid.NewGuid();
        }

        public Guid RequestId { get; set; }

        public Mediator Mediator { get; set; }

        public ICache Cache { get; set; }

        public ILogger logger { get; set; }
    }
}

We include the Mediator in the context in order to easily chain other commands or queries from the current one.

Now we need to modify our command to include the context and use the caching provider from it

using PlugAndPlayExample.Infrastructure.Caching;
using PlugAndPlayExample.Services.Infrastructure;

namespace PlugAndPlayExample.Services.Commands
{
    public class AddUser
    {
        public class Request: ICommand
        {
            public string UserName { get; set; }
            public string Password { get; set; }
        }

        public class Handler : ICommandHandler<Request, Response<int>>
        {
            private readonly Context context;

            public Handler(Context context)
            {
                this.context = context;
            }

            public Response<int> Handle(Request command)
            {
                var user = // User registration code...

                context.Cache.Set<User>($"user-{user.Id}", user);

                return user.Id;
            }
        }
    }
}

For all of this to work we need to register the context itself like this: services.AddScoped<Context>();

Why is the context scoped?
The context for a command or a query should have a single instance per request and that way we can guarantee that it will have a reliably consistent RequestId.

Why do we need RequestId?
The RequestId helps us keep track of execution chains. When we log errors, log audit trails, or log events we need to know what is the execution path of the request, which commands and queries the request passed through and help us easily detect a runtime bug.

In the next blog post we will discuss the execution pipeline that every query and command go through and where every implicit and hybrid infrastructure lives.

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 300 upvotes.
Your next target is to reach 400 upvotes.

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 7
Support the HiveBuzz project. Vote for our proposal!