C# Cloud Application Architecture: Commanding via a Mediator (Part 1)

Check out Part 2 here

When designing cloud based systems we often think about the benefits of loosely coupling systems, for example through queues and pub/sub mechanisms, but ultimately fall back on more traditional, layered patterns for the application architecture.

My aim over a series of posts is to show how combining the Command pattern with the Mediator pattern can yield a number of benefits including:

  • A better separation of concerns with a clear demarcation of domains
  • An internally decomposed application that is simple to work with early in its lifecycle, yet can easily evolve into a microservices approach
  • Able to reliably support none-transactional or distributed data storage operations
  • Less repetitive infrastructure boilerplate to write, such as logging and telemetry

To illustrate this, we’re going to refactor an example application over several stages, from a layered onion ring style to a decoupled in-process application and finally on through to a distributed application. An up-front warning, to get the most out of these posts a fair amount of code reading is encouraged. It doesn’t make sense for me to walk through each change and doing so would make the posts ridiculously long, but hopefully the sample code provided is clear and concise.

The application is a RESTful HTTP API supporting some basic, simplified, functionality for an online store. The core API operations are:

  • Shopping cart management: get the cart, add products to the cart, clear the cart
  • Checkout: convert a cart into an order, mark the order as paid
  • Product: get a product

To support the above there is also a sense of store product catalog, it’s not exposed through the API, but it is used internally.

I’m going to be making use of my own command mediation framework AzureFromTheTrenches.Commanding as it has some features that will be helpful to us as we progress towards a distributed application but alternatives are available such as the popular Mediatr and if this approach appeals to you I’d encourage you to look at those too.

Our Starting Point

The code for the onion ring application, our starting point, can be found on GitHub:

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

Firstly, lets define what I mean by onion ring or layered architecture. In the context of our example application it’s a series of layers built one on top of the other with calls up and down the chain, never skipping a layer: The Web API talks to the application services, the application services to the data access layer, and the data access layer to the storage. Each layer drives the one below. To support this each layer is broken out into its own assembly and a mixture of encapsulation (at the assembly and class level) and careful reference management ensures this remains the case.

In terms of our application this looks like this:


 

Concrete class coupling is avoided through using interfaces which are constructor injected to promote a testable approach. Normally in such an architecture each layer has its own set of models (data access models, application models etc.) with mapping taking place between them however for the sake of brevity, and as I’m just using mock data stores, I’ve ignored this and used a single set of models that can be found in the OnlineStore.Model assembly.

This is certainly the pattern I’ve come across most often over the last few years and so it is worth taking a look at what makes it good:

  1. Perhaps most importantly – it’s simple. You can draw out the architecture on a whiteboard with a set of circles or boxes in 30 seconds and it’s quite easy to explain what is going on and why
  2. It’s testable. Obviously things get more complicated in the real world, however above the data access layer, testing is really easy: everything is mockable and the major concrete external dependencies have been isolated in our data access layer
  3. A good example of the pattern is open for extension but closed for modification. As there are clear contractual interfaces decorators can be added to e.g. you can add telemetry without modifying business logic
  4. It’s very well supported in tooling. If you’re using Visual Studio its built-in refactoring tools or Resharper will have no problem crunching over this solution to support changes and help keep things consistent
  5. It can be used in a variety of different operating environments. I’ve used a Web API for this example but you can find onion / layer type architectures in all manner of solutions
  6. It’s what’s typically been done before. Most engineers are familiar with the pattern and it has become almost a default choice

 

I’ve built many successful systems myself using this pattern and it has served me well. However, I started looking for better approaches once I experienced some of the weaknesses:

  1. It’s not as loosely coupled and malleable as it seems. This might seem an odd statement to make given our example makes strong use of interfaces and composition via dependency injection, an approach often seen to promote loose coupling and flexibility. And it does to a point. However, we still tend to think of operations in such an architecture in quite a tightly coupled way: this controller calls this method on this interface which is implemented by this service class and on down through the layers. It’s a very explicit invocation process
  2. Interfaces and classes in the service layer often become “fat” – they tend to end up coupled around a specific part of the application domain (in our case, the shopping basket) and used to group together functionality
  3. As an onion ring application gets larger it is quite common for the Application Layer to begin to resemble spaghetti – repositories are referenced across the code base, services refer to services, and helpers to other helpers. You can mitigate this by decomposing the application layer into further assemblies and making use of various design patterns, but it only takes you so far. These are all rudimentary fixes for a fundamental problem with the onion ring approach: it’s designed to segregate at the layer level not at the domain or bounding context level (we’ll talk about domain driven design later). It is also common to see concerns start to bleed across the layer with what once were discrete boundaries
  4. In the event of fundamental change the architecture can be quite fragile and resistant to change. Let’s consider a situation where our online store has become wildly successful and to keep it serving the growing user-base we need to break it apart to handle the growing scale. This will probably involve breaking our API into multiple components and using patterns such as queues to asynchronously run and throttle some actions. With the onion ring architecture, we’re going to have to analyse what is probably a large codebase looking for how we can safely break it apart. Almost inevitably this will be more difficult than it first seems and it will probably seem quite difficult to begin with (I may have been here myself!) and we’ll uncover all manner of implicit tight coupling points

 

There are of course solutions to many of these problems, particularly the code organisation issues. However, at some point it is worth considering if the architecture is still proving helpful, or if it has become more of a hindrance and it is time to look for alternative approaches.

With that in mind, and as I said earlier, I’d like to introduce an alternative. It’s by no means the alternative, but it is one I’ve found tremendously helpful when building applications for the cloud.

The Command Pattern with a Mediator

The classic command pattern aims to encapsulate all the information needed to perform an action, typically within a class that contains properties (the data needed to perform the action), and an execute method (that undertakes the action). For example:

public class GetProductCommand : ICommand<Product>
{
    public Guid ProductId { get; set; }
    public Task<Product> Execute()
    {
         /// ... logic
    }
}

 

This can be a useful way of structuring an application but tightly couples state and actions in a manner that can prove limiting for example, it would be helpful for the command to participate in a pub/sub approach, take part in a command chain (step 1 – store state in undo, step 2 – execute command, step 3 – log command was executed), or operate across a process boundary.

A powerful improvement can be made to the approach by decoupling the execution from the command state via the mediator pattern. In this pattern, all commands – each of which consist of simple serializable state – are dispatched to the mediator and the mediator determines which handlers will execute the command:

Because the command is decoupled from execution it is possible to associate multiple handlers with a command and moving a handler to a different process space (for example splitting a Web API into multiple Web APIs) is simply a matter of changing the configuration of the mediator. And because the command is simple state we can easily serialize it into an event store.

The example below illustrates a mediator that can serialize commands to stores directly, but this can also be accomplished through additional handlers:

 

It’s perhaps worth also briefly touching on the CQRS pattern – while the Command Message pattern may facilitate a CQRS approach it neither requires it or has any opinion on it.

With that out the way let’s look at how this pattern impacts our applications architecture. Firstly, the code for our refactored solution can be found here:

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

And from our layered approach we now end up with something that looks like this:

 

 

It’s important to realise when looking at this code that we’re going to do more work to take advantage of the capabilities being introduced here. This is just a “least work” refactor to demonstrate some high-level differences – hopefully over the next few posts you’ll see how we can use these capabilities to simplify our solution. However even at this early stage there are some clear points to note about how this effects the solution structure and code:

  1. Rather than structure our application architecture around technology, we’ve restructured it around domains. Where previously we had a data access layer, an application layer and an API layer, we now have a shopping cart application, a checkout application, a product application and a Web API application. Rather than the focus being about technology boundaries it’s more about business domain boundaries
  2. Across boundaries (previously layers, now applications) we’ve shifted our invocation semantics from interfaces, methods and parameters to simple state that is serializable and persistable and communicated through a “black box” mediator or dispatcher. The instigator of an operation no longer has any knowledge of what will handle the operation and, as we’ll see later, that can even include the handler executing in a different process space such as another web application
  3. I’ve deliberately used the term application for these business domain implementations as, even though they are all operating in the same process space, it is best to think of them as self-contained units. Other than a simple wiring class to allow them to be configured in the host application (via a dependency injector), they interoperate with their host (the Web API) and between themselves entirely through the dispatch of commands
  4. The intent is that each application is fairly small in and of itself and that it can take the best approach required to solve its particular problem. In reality, it is quite typical that many of the applications follow the same conventions and patterns, as they do here, and when this is the case it is best to establish some code organisation conventions. In fact, it’s not uncommon to see a lightweight onion ring architecture used inside each of these applications – so for lovers of the onion ring – you don’t need to abandon it completely!

Let’s start by comparing the controllers. In our traditional layered architecture, our product controller looked like this:

[Route("api/[controller]")]
public class ProductController : Controller
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public async Task<StoreProduct> Get(Guid id)
    {
        return await _productService.GetAsync(id);
    }
}

As expected, this accepts an instance of a service, in this case of an IProductService, and it’s get method simply passes the ID on down to it. Although the controller is abstracted away from the implementation of the IProductService, it is clearly and directly linked to one type of service and a method on that service.

Now let’s look at its replacement:

[Route("api/[controller]")]
public class ProductController : Controller
{
    private readonly ICommandDispatcher _dispatcher;

    public ProductController(ICommandDispatcher dispatcher)
    {
        _dispatcher = dispatcher;
    }

    [HttpGet("{id}")]
    public async Task Get(Guid id)
    {
        return await _dispatcher.DispatchAsync(new GetStoreProductQuery
        {
            ProductId = id
        });
    }
}

In this implementation, the controller accepts an instance of ICommandDispatcher and then rather than invoke a method on a service, it calls DispatchAsync on that dispatcher supplying it with a command model. The controller no longer has any knowledge of what will handle this request and all our commands are executed by discrete handlers. In this case the GetStoreProductQuery command is handled the GetStoreProductQueryHandler in the Store.Application assembly:

internal class GetStoreProductQueryHandler : ICommandHandler<GetStoreProductQuery, StoreProduct>
{
    private readonly IStoreProductRepository _repository;

    public GetStoreProductQueryHandler(IStoreProductRepository repository)
    {
        _repository = repository;
    }

    public Task<StoreProduct> ExecuteAsync(GetStoreProductQuery command, StoreProduct previousResult)
    {
        return _repository.GetAsync(command.ProductId);
    }
}

Our initial application doesn’t do much more than the implementation of the ProductService, bar deriving from the ICommandHandler generic interface the type of the command it is handling and the result type.

Our command mediation framework takes care of routing the command to this handler and will ultimately call ExecuteAsync on it. The framework we are currently using allows commands to be chained, and so as well as being given the command state it is also given any previous result.

Handlers are registered against actors as follows (this can be seen in the IServiceCollectionExtenions.cs file of Store.Application):

commandRegistry.Register<GetStoreProductQueryHandler>();

The command mediation framework gets everything it needs from the definition of GetStoreProductQueryHandler to map the handler to the command.

I think it’s fair to say that is all loosely coupled! In fact, it is so loose that if we weren’t going to go on to make more of this pattern we might conclude that the loss of immediate traceability is not worth it.

In the next part, we’re going to visit some of our more complicated controllers and commands, such as the Put verb on the ShoppingCartController, to look at how we can massively simplify the code below:

[HttpPut("{productId}/{quantity}")]
public async Task<IActionResult> Put(Guid productId, int quantity)
{
    CommandResponse response = await _dispatcher.DispatchAsync(new AddToCartCommand
    {
        UserId = this.GetUserId(),
        ProductId = productId,
        Quantity = quantity
    });
    if (response.IsSuccess)
    {
        return Ok();
    }
    return BadRequest(response.ErrorMessage);
}

By way of a teaser, the aim is to end up with really thin controller action that, across the piece, looks like this:

[HttpPut("{productId}/{quantity}")]
public async Task<IActionResult> Put([FromRoute] AddToCartCommand command) => await ExecuteCommand(command);

We’ll then roll that approach out over the rest of the API. Then get into some really cool stuff!

Cloud Futures AI Business

Leave a Reply

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