C# Cloud Application Architecture – Commanding via a Mediator (Part 3)

If you're looking for help with C#, .NET, Azure, Architecture, or would simply value an independent opinion then please get in touch here or over on Twitter.

In the previous post we simplified our controllers by having them accept commands directly and configured ASP.Net Core so that consumers of our API couldn’t insert data into sensitive properties. However we’ve not yet set up any validation so, for example, it is possible for a user to add a product to a cart with a negative quantity. We’ll address that in this post and take a look at an alternative to the attribute model of validation that comes out the box with ASP.Net and ASP.Net Core.

The source code for this post can be found on GitHub:

https://github.com/JamesRandall/CommandMessagePatternTutorial/tree/master/Part3

The built in ASP.Net Core approach to validation, like previous versions, relies on a model being annotated with attributes but there are some significant drawbacks with this approach:

  1. You need access to the source code to add the attributes – sometimes this isn’t possible and so you end up maintaining multiple sets of models just to express validation rules.
  2. It requires you to tightly couple your model to a specific validation system and set of validation rules and include additional dependencies that may not be appropriate everywhere you want to use the model.
  3. It’s extensible but not particularly elegantly and complicated rules are hard to implement.

Depending on your application architecture maintaining multiple sets of model can be a good solution to some of these issues, and might be the right thing to do for your architecture in any case (for example you certainly don’t want Entity Framework models bleeding out through an API for example) but in our scenario there’s little to be gained by maintaining multiple representations of commands, at least in the general sense.

An excellent alternative framework for validation is Jeremy Skinner’s Fluent Validation package set. This allows validation rules to be expressed separately from models, hooks into the ASP.Net Core pipeline to support the standard ModelState approach, and allows validations to be run easily from anywhere.

We’re going to continue to follow our application encapsulation model for adding validations and so we will add a validator package to each domain, for example ShoppingCart.Validation. Each of these assemblies will consist of private classes for the validators that are registered in the IoC container using an installer. Below is an example of the validator for our AddToCartCommand command:

internal class AddToCartCommandValidator : AbstractValidator<AddToCartCommand>
{
    public AddToCartCommandValidator()
    {
        RuleFor(c => c.ProductId).NotEqual(Guid.Empty);
        RuleFor(c => c.Quantity).GreaterThan(0);
    }
}

This validator makes sure that we have a none-empty product ID and specify a quantity of 1 or more. We register this in our installer as follows:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection RegisterValidators(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddTransient<IValidator<AddToCartCommand>, AddToCartCommandValidator>();
        return serviceCollection;
    }
}

The ShoppingCart.Validation assembly is referenced only from the ShoppingCart.Application installer as shown below:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection UseShoppingCart(this IServiceCollection serviceCollection,
        ICommandRegistry commandRegistry)
    {
        serviceCollection.AddSingleton<IShoppingCartRepository, ShoppingCartRepository>();

        commandRegistry.Register<GetCartQueryHandler>();
        commandRegistry.Register<AddToCartCommandHandler>();

        serviceCollection.RegisterValidators();

        return serviceCollection;
    }
}

And finally in our ASP.Net Core project, OnlineStore.Api, in the Startup class we register the Fluent Validation framework – however it doesn’t need to know anything about the specific validators:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(c =>
    {
        c.Filters.Add<AssignAuthenticatedUserIdActionFilter>();
        c.AddAuthenticatedUserIdAwareBodyModelBinderProvider();
    }).AddFluentValidation();

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "Online Store API", Version = "v1" });
        c.SchemaFilter<SwaggerAuthenticatedUserIdFilter>();
        c.OperationFilter<SwaggerAuthenticatedUserIdOperationFilter>();
    });

    CommandingDependencyResolver = new MicrosoftDependencyInjectionCommandingResolver(services);
    ICommandRegistry registry = CommandingDependencyResolver.UseCommanding();

    services
        .UseShoppingCart(registry)
        .UseStore(registry)
        .UseCheckout(registry);
    services.Replace(new ServiceDescriptor(typeof(ICommandDispatcher), typeof(LoggingCommandDispatcher),
        ServiceLifetime.Transient));
}

We can roll that approach out over the rest of our commands and with the validator configured in ASP.Net can add model state checks into our AbstractCommandController, for example:

public abstract class AbstractCommandController : Controller
{
    protected AbstractCommandController(ICommandDispatcher dispatcher)
    {
        Dispatcher = dispatcher;
    }

    protected ICommandDispatcher Dispatcher { get; }

    protected async Task<IActionResult> ExecuteCommand<TResult>(ICommand<TResult> command)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        TResult response = await Dispatcher.DispatchAsync(command);
        return Ok(response);
    }
        
    // ...
}

At this point we can enforce simple entry point validation but what if we need to make validation decisions based on state we only know about in a handler? For example in our AddToCartCommand example we want to make sure the product really does exist before we add it and perhaps return an error if it does not (or, for example, if its not in stock).

To achieve this we’ll make some small changes to our CommandResponse class so that instead of simply returning a string it returns a collection of keys (property names) and values (error messages). This might sound like it should be a dictionary but the ModelState design of ASP.Net supports multiple error messages per key and so a dictionary won’t work, instead we’ll use an IReadOnlyCollection backed by an array. Here’s the changes made to the result-free implementation (the changes are the same for the typed variant):

public class CommandResponse
{
    protected CommandResponse()
    {
        Errors = new CommandError[0];
    }

    public bool IsSuccess => Errors.Count==0;

    public IReadOnlyCollection<CommandError> Errors { get; set; }

    public static CommandResponse Ok() {  return new CommandResponse();}

    public static CommandResponse WithError(string error)
    {
        return new CommandResponse
        {
            Errors = new []{new CommandError(error)}
        };
    }

    public static CommandResponse WithError(string key, string error)
    {
        return new CommandResponse
        {
            Errors = new[] { new CommandError(key, error) }
        };
    }

    public static CommandResponse WithErrors(IReadOnlyCollection<CommandError> errors)
    {
        return new CommandResponse
        {
            Errors = errors
        };
    }
}

We’ll add the errors from our command response into model state through an extension method in the API assembly – that way the syntax will stay clean but we won’t need to add a reference to ASP.Net in our shared assembly. We want to keep that as dependency free as possible. The extension method is pretty simple and looks like this:

public static class CommandResponseExtensions
{
    public static void AddToModelState(
        this CommandResponse commandResponse,
        ModelStateDictionary modelStateDictionary)
    {
        foreach (CommandError error in commandResponse.Errors)
        {
            modelStateDictionary.AddModelError(error.Key ?? "", error.Message);
        }
    }
}

And finally another update to the methods within our AbstractCommandController to wire up the response to the model state:

protected async Task<IActionResult> ExecuteCommand<TCommand, TResult>() where TCommand : class, ICommand<CommandResponse<TResult>>, new()
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    TCommand command = CreateCommand<TCommand>();
    CommandResponse<TResult> response = await Dispatcher.DispatchAsync(command);
    if (response.IsSuccess)
    {
        return Ok(response.Result);
    }
    response.AddToModelState(ModelState);
    return BadRequest(ModelState);
}

With that we can now return simple model level validation errors to clients and more complex validations from our business domains. An example from our AddToCartHandler that makes use of this looks as follows:

public async Task<CommandResponse> ExecuteAsync(AddToCartCommand command, CommandResponse previousResult)
{
    Model.ShoppingCart cart = await _repository.GetActualOrDefaultAsync(command.AuthenticatedUserId);

    StoreProduct product = (await _dispatcher.DispatchAsync(new GetStoreProductQuery{ProductId = command.ProductId})).Result;

    if (product == null)
    {
        _logger.LogWarning("Product {0} can not be added to cart for user {1} as it does not exist", command.ProductId, command.AuthenticatedUserId);
        return CommandResponse.WithError($"Product {command.ProductId} does not exist");
    }
    List<ShoppingCartItem> cartItems = new List<ShoppingCartItem>(cart.Items);
    cartItems.Add(new ShoppingCartItem
    {
        Product = product,
        Quantity = command.Quantity
    });
    cart.Items = cartItems;
    await _repository.UpdateAsync(cart);
    _logger.LogInformation("Updated basket for user {0}", command.AuthenticatedUserId);
    return CommandResponse.Ok();
}

Hopefully that’s a good concrete example of how this loosely coupled state and convention based approach to an architecture can reap benefits. We’ve added validation to all of our controllers and actions without having to revisit each one to add boilerplate and we don’t have to worry about unit testing each and every one to ensure validation is enforced and to be sure this worked we simply updated the unit tests for our AbstractCommandController, and our validations are clearly separated from our models and testable. And we’ve done all this through simple POCO (plain old C# object) type models that are easily serializable – something we’ll make good use of later.

In the next part we’ll move on to our handlers and clean them up so that they become purely focused on business domain concerns and at the same time improve the consistency of the logging we’ve got scattered around the handlers at the moment. We’ll also add some basic telemetry to the system using Application Insights so that we can measure how often and for how long each of our handlers take to run.

Other Parts in the Series

Part 5
Part 4
Part 2
Part 1

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact

  • If you're looking for help with C#, .NET, Azure, Architecture, or would simply value an independent opinion then please get in touch here or over on Twitter.

Recent Posts

Recent Tweets

Recent Comments

Archives

Categories

Meta

GiottoPress by Enrique Chavez