The Mediator Pattern one of the Behavioral Patterns defined by the Gang of Four in Design Patterns: Elements of Reusable Object-Oriented Software
Mediator pattern is used to reduce communication complexity between multiple objects or classes. The pattern provides a mediator class which handles all the communications between different classes and supports easy maintenance of the code by loose coupling.
A mediator sits between method callers and receivers creating a configurable layer of abstraction that determines how callers and receivers get wired up.
Design Patterns
Elements of Reusable Object-Oriented Software
23 patterns allow designers to create more flexible, elegant, and ultimately reusable designs without having to rediscover the design solutions themselves
Benefits of the Mediator Pattern
- Increases the reusability of the objects supported by the Mediator by decoupling them from the system.
- Simplifies maintenance of the system by centralizing control logic
- Simplifies and reduces the variety of messages sent between objects in the system.
The Mediator is most commonly used to co-ordinate related GUI components. However, it is also gaining popularity amongst C# developers when developing web API's and Microservices to enable typical Request & Response messaging.
A drawback of the Mediator pattern is that without a proper design the Mediator object itself can become overly complex.
Fortunately, in C# Jimmy Bogard, of Automapper fame has also developed MediatR - Simple mediator implementation in .NET. MediatR supports request/response, commands, queries, notifications and events, synchronous and async with intelligent dispatching via C# generic variance.
I prefer to use MediatR as my implementation of choice of the Mediator Pattern, because of its ease of use and versatility. I also learned a lot about code by reading the MediatR source code. It is well worth cloning the repository and spending sometime just going through the implementation, it will provide one with a good sense of how to implement the pattern and also an appreciation of some of the finer points of the C# language.
Why use the Mediator pattern
When working with Domain Classes where multiple functions are used to modify state and implement domain rules it usually becomes difficult to debug, extend and review the implementation. Often business rule functions implement multiple sub-rules that are repeatedly required elsewhere in the domain. This may lead to Multiple layers of complex abstraction required to share functionality or multiple strategies from several developers advocating different patterns and practices.
A common challenge in a codebase, is dealing with the constant flux of ever changing domain requirements and business rules.
Typically software services start out as simple CRUD applications, but gradually evolve to become complex as more rules and changes are introduced.
Developers may initially try to solve this problem, by implementing a Manager class to centralise the logic to contain every action possible to modify the state of an entity or to modify the state of another manager class.
Testing often becomes convoluted, messy, complex and usually incomplete. Typically the problems occur when in order to test the class there are a number of dependencies to mock and configure. Just to test a new function.
Typically no validation of the inbound request is implemented, and the request is allowed to pass through the middle layer, API layer and only validated within the Manager class functions. This often causes bloated action functions with complicated validation rules before the implementation logic!
How does the Mediator pattern solve issues
The mediator pattern promotes loose coupling, by implementing a mediator object to enable objects to communicate with it rather than each other.
Things to look out for when using the Mediator pattern
I have seen developers complain about mediator and a restriction on reuse, or even mediator pattern takes them down a path of violating the DRY principle.
DRY is an acronym for Don't Repeat Yourself , which developers often simplify as trying to avoid duplicating code.
DRY more accurately means that we want to avoid duplicating our system behaviour and knowledge
Building Microservices : Designing Fine Grained Systems
The DRY principle ultimately guides developers down a path to creating code that can be re-used. Often trying to pull code into abstractions that can be used and called from multiple places.
However, as Sam Newman warns in Building Microservices : Designing Fine Grained Systems in DRY and the Perils of Code Reuse in a Microservice world this can be deceptively dangerous in a microservice architecture.
The important aspect to consider when using the Mediator pattern is that by using separate models we most commonly mean different object models, probably running in different logical processes, perhaps on separate hardware.
A REST API example would mean a Post request, would run on possibly a completely different server or container to GET request. So this is where Shared Code my potentially leak across service boundaries thus introducing a form of unintentional coupling.
In Monolith Architectures, it is often tempting to attempt to implement Reusable or Generic Models or even Data Transformation Objects (DTO) which developers will try reuse throughout the stack, in order to make methods all follow a consistent data contract. However, this can also be a cause an increased probability of bugs and undesirable features and potentially cause objects to become overly complex.
Implementing the Mediator pattern, like so many things in Software Development, is a trade off on where to manage your complexity and minimize coupling.
Building Microservices
Designing Fine-Grained Systems
Microservice technologies are moving quickly. Author Sam Newman provides you with a firm grounding in the concepts while diving into current solutions for modeling, integrating, testing, deploying, and monitoring your own autonomous services.
Example of the mediator pattern using MediatR
In Developing Api using Http Endpoints I discussed some of the problems relating to using MVC pattern to developing Web API projects and how to overcome by making use of the Adralis API Endpoints and I even created a API Template project to help you get started.
In this very simplified example we are going to make use of the template to create a basic project structure to illustrate how to use the Mediator Pattern, and we will also be making use of MediatR, primarily because the template project comes pre-configured to use it.
Once you have installed the template we can create a new project using the dotnet new command
dotnet new apiproject -n mediator
Once the project has been generated, we will have have all that is required done for us to provide the most simplistic example of the Mediator pattern.
It is important that the basis of the Mediator pattern, is Request & Response mediation or what is more commonly known as the CQRS (Command Query Responsibility Segregation) .
The rationale is that for many problems, particularly in more complicated domains, having the same conceptual model for commands and queries leads to a more complex model that does neither well, this also often leads to inadvertent coupling within your stack, leading to the Perils of Dry in microservices .
Mediator promotes loose coupling by keeping objects from referring to each other explicitly and it lets you vary their interaction independently
Design Patterns: Elements of Reusable Object-Oriented Software
In order to speed things up even more, I am also going to use a few other products I have developed to quickly build out the other infrastructure details. Hence, I will be making use of the ThreeNine.Data - Generic Repository which I discussed in more detail in Using the Repository and Unit Of Work Pattern in .net core and also the Code First Database library Nuget package from our Headless CMS we are currently in the process of developing.
I will not discuss the whole process of setting up the project to use these libraries because the source code is for this example is available. For the purpose of this article we will only discuss the Mediator pattern implementation.
We will implement functionality to Create a new Salutation, which is essentially a polite expression of greeting, which covers things like Mr., Mrs. , Prof. etc. Our simplistic API endpoint is simply going to enable adding new items to the list.
In our application configuration in the Startup.cs
we will first enable MediatR to essentially scan our assembly and automatically wire up all Mediator Handlers it finds ready to be used. We'll do this by simply adding the services.AddMediatR(typeof(Startup));
in our ConfigureServices
method.
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "mediator", Version = "v1"}); c.EnableAnnotations(); }); services.AddAutoMapper(typeof(Startup)); services.AddMediatR(typeof(Startup)); services.AddDbContext<BoleynContext>(options => options.UseNpgsql(Configuration.GetConnectionString("mediator")) ).AddUnitOfWork<BoleynContext>(); }
We'll create a new Api Endpoint as Post
, the most important details are that we simply dependency inject IMediator
object into our class, which has been made possible because of the step we carried out earlier of instructing MediatR to scan our assembly and wire up all the handlers.
You notice that we have configured our endpoint to Accept a request, which will contain a command, and that our endpoint will not provide a Response. Although it is still important to remember our Endpoint will still provide an ActionResult response, but in this instance it means that endpoint will not be providing an Object response.
[Route(RouteNames.Salutations)] public class Post : BaseAsyncEndpoint.WithRequest<CreateSalutationCommand>.WithoutResponse { private readonly IMediator _mediator; public Post(IMediator mediator) { _mediator = mediator; } [HttpPost] [SwaggerOperation( Summary = "Create a new salutation", Description = "", OperationId = "AA440D51-75A5-4975-8875-C1799B58D4EB", Tags = new []{RouteNames.Salutations} )] [ProducesResponseType(StatusCodes.Status201Created)] public override async Task<ActionResult> HandleAsync([FromBody] CreateSalutationCommand request, CancellationToken cancellationToken = new()) { var result = await _mediator.Send(request, cancellationToken); return new CreatedResult( new Uri(RouteNames.Salutations, UriKind.Relative), new { id = result }); } }
Our endpoint handler accepts a CreateSalutationCommand
object, which in theory is an implementation of CQRS, We need to create our CreateSalutationCommand
object as follows.
public class CreateSalutationCommand : IRequest<int> { public string Abbreviation { get; set; } public string FullWord { get; set; } }
You will notice our Command inherits from the IRequest
essentially stipulate the Type that our object will request. This type can be either a Primitive or Complex type, however in this case we are simply requesting an Integer return value containing the ID of the created object.
We can now configure our Handler object, you will notice that we create a class
which inherits from the IRequestHandler
interface and accepts the CreateSalutationCommand
and provides an int
response type.
We can inject whatever dependencies we may need into this class, in our case it will be the IMapper
and IUnitOfWork from our generic repository.
public class PostHandler : IRequestHandler<CreateSalutationCommand, int> { private readonly IUnitOfWork _unitOfWork; private readonly IMapper _mapper; public PostHandler(IUnitOfWork unitOfWork, IMapper mapper) { _unitOfWork = unitOfWork; _mapper = mapper; } public async Task<int> Handle(CreateSalutationCommand request, CancellationToken cancellationToken) { var salutation = _mapper.Map<Salutation>(request); var repo = _unitOfWork.GetRepositoryAsync<Salutation>(); await repo.InsertAsync(salutation, cancellationToken); await _unitOfWork.CommitAsync(); return salutation.Id; } } }
In this very simple example, you'll notice that we have placed our business logic all with Handle
method, which I would be the first to admit is not great, but it also serves to illustrate that we have separated it from our Endpoint logic.
In later, articles based on MediatR, I will provide further examples of how this can further be improved enabling you to take further advantage of MediatR using MediatR Pipeline Behaviours, but for now I just want to illustrate the Mediator Pattern at work.
The take away from this example, is that you will notice that at no point do we explicitly define a reference to our Handler anywhere, in our Post endpoint, we simply make use of the Send
function available on the IMediator
to send our request to whichever object has been defined to satisfy the Request and Response pairing. The Mediator, then ensures it sends it to which ever it has been configured too.
Conclusion
The most simplest definition of the Mediator pattern is that it is an object that encapsulates how objects interact. So it can obviously handle passing Request and Response between objects.
The Mediator pattern promotes loose coupling by not having objects refer to each other, but instead refer to the mediator. So they pass the messages to the mediator, who will pass it on to the object that has been defined to handle them.
The CQRS pattern defined actually just an implementation of the mediator pattern. As long as we are promoting loose coupling through a mediator
class that can pass data back and forth so that the caller doesn't need to know how things are being handled, then we can say we are implementing the Mediator Pattern.
- What is this Directory.Packages.props file all about? - January 25, 2024
- How to add Tailwind CSS to Blazor website - November 20, 2023
- How to deploy a Blazor site to Netlify - November 17, 2023