en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en en cs it en en en en en en en en en en en en en en en en en en en en en pt en en en en en en en az tr en es es tr en cs en en en en en en en en en en en en en en en en en en en en en en en en sv en es en tr en es en en en en en
Mediator itself is a pretty old and well-known design pattern. It promotes loosely-coupled communication between objects which reduces chaotic dependencies and forces the communication through a single mediator object.
In the following examples, I will be using an open-source MediatR library that implements mediator pattern in .NET (github link). In MediatR, each request requires a definition of input, output, and handler that does the actual logic. So, for example, let’s say we need to implement a request that obtains some elements from the database based on the provided filter. In order to do so, we need to create an input class for a mediator that implements IRequest  interface where T is the type of request output; in this case a collection of elements.
To define a handler for this request, we need to create a class that implements IRequestHandler  interface where Tin is the type of the request and Tout is the type of output.
The IRequestHandler interface requires the method Handle to be implemented in a derived class, which is, in fact, the place where your logic of the request is. The Handle method has two arguments. The first argument is of the type Tin — the place where metadata of the request are stored, in this case, a filter. The second argument is the standard cancellation token.
As you can see from the code, it is extremely clean and simple, honoring the KISS (keep it stupid simple) principle. More importantly, it also honors the single responsibility principle because each single handler has only one specific purpose. Every developer that looks at this code should know in a few seconds what the code does (if he has some knowledge of Entity Framework, in this case). As you might have noticed, the database context is injected into the constructor using dependency injection. For this to work, MediatR needs to be registered into the service provider of your choice. For Microsoft’s service provider it is as easy as calling one line of code in the Startup file of your ASP.NET Core application.
public void ConfigureServices(IServiceCollection services) { . . . services.AddMediatR(typeof(GetElements)); . . . }
The only parameter of this method is an Assembly or a type in a given assembly where your handlers are located, registration of each individual handler is then done for you by using reflection in the background. Apart from ASP.NET Core service provider, other supported containers are Autofac, Castle Windsor, DryIoc, Lamar, LightInject, Ninject, Simple Injector, StructureMap, and Unity.
So far, we have defined and implemented request GetElements and we also have it registered into our service provider, so the only thing left is… to use it!
To use the mediator object, simply inject IMediator into the constructor and let it be resolved by your service provider. IMediator object has two methods: Send and Publish. Send method is used for the command type of requests, the Publish method serves more as an event system. In this article, we will focus on the Send method.
To create a new request, simply call the Send method and provide an instance of the desired request.
private readonly IMediator _mediator; public ElementsController(IMediator mediator) { _mediator = mediator; } [HttpGet(nameof(GetElements))] public async Task  GetElements([FromQuery] ElementFilter filter) { IEnumerable  response = await _mediator.Send(new GetElements {Filter = filter}); return Ok(response); }
The mediator acquires the request, finds an appropriate handler, instantiates it using the service provider (so all dependencies are met), calls the Handle method, and returns the result to the caller (in our example it is the GetElements method in the controller). It is good to know that the reflection for handler registration is done only at the start, so you do not need to worry about performance at all.
In a case where the return value of request is not needed (e.g., delete, update, or create operations), the handler class should inherit from the abstract class AsyncRequestHandler  where T is the type of the request and the request class should implement plain IRequest interface (without any generics).
public class DeleteElementById : IRequest { public int Id { get; set; } } public class DeleteElementByIdHandler : AsyncRequestHandler  { private readonly ElementsContext _elementsContext; public DeleteElementByIdHandler(ElementsContext elementsContext) { _elementsContext = elementsContext; } protected override async Task Handle(DeleteElementById request, CancellationToken cancellationToken) { var element = await _elementsContext.ScreenElements.FindAsync(request.Id); if (element == null) { return; } _elementsContext.ScreenElements.Remove(element); await _elementsContext.SaveChangesAsync(cancellationToken); } }
Although consistent naming of requests is not required, it is nevertheless recommended. The name of your request should start with the type of operation (e.g. Delete), followed by the domain (Element) and optionally some other specification (by ID), so, for example, UpdateUser, GetPrintJobById, CreateUniverse. I think you get the point. If you keep the naming consistent, it will be easier to navigate through your code and it will also greatly improve the code readability.
Another example of the tradeoff of a mediator is when a mediator is used inside of the request handler. The mediator object is injected into the constructor and the Handle method is used to call some other request. In the example below, we need to check first when creating a new element if an element with the same metadata does not already exist. In order to do so, we can call another request using the mediator object, which is perfectly fine; however, it violates the Inversion of Control pattern because it is not transparent from the constructor definition that the GetElementByFilter request would be called. In my opinion, this is a minor tradeoff worth mentioning.
public class CreateElementHandler : AsyncRequestHandler  { private readonly ElementsContext _elementsContext; private readonly IMediator _mediator; public CreateElementHandler(ElementsContext elementsContext, IMediator mediator) { _elementsContext = elementsContext; _mediator = mediator; } protected override async Task Handle(CreateElement request, CancellationToken cancellationToken) { var element = request.Element; var filter = new ElementFilter(){. . .}; var response = await _mediator.Send(new GetElementByFilter() { Filter = filter }, cancellationToken); if (response.Element != null) { throw new ElementAlreadyExistException(element.Name); } _elementsContext.ScreenElements.Add(element); await _elementsContext.SaveChangesAsync(cancellationToken); } }
Easily maintainable, testable and readable code
Sometimes antipattern to IoC
Requires a lot of classes
In my experience, this approach is perfect for applications that handle CRUD operations of a few domains, e.g., microservices with 1-3 domains. For larger applications or monoliths, I would be careful when using the Mediator because you could easily end up creating a God object.
Senior Software Developer in Y Soft
LEAVE A REPLY Cancel reply
Please enter your comment!
Please enter your name here
You have entered an incorrect email address!
Please enter your email address here
Save my name, email, and website in this browser for the next time I comment.
Leave this field empty please:
Functional Programming in Common Lisp
Books Manfred Bergmann - 22 September 2022 0
Intro Functional programming (FP) has again been getting popular in recent years. Why again? Because the paradigm of FP...
Y Soft LeSS transformation
Events Bruno Veronesi - 23 July 2020 0
Because at Y Soft we believe that sharing is caring, we have decided to organize a public meetup and share...
Mediator design pattern in modern applications
Engineering Václav Novotný - 16 July 2020 0
Mediator itself is a pretty old and well-known design pattern. It promotes loosely-coupled communication between objects which reduces chaotic dependencies and forces...
Y Soft is a Czech based global company helping businesses with digital transformation. We constantly innovate, collaborate and support each other.
© 2020 Y Soft Corporation. All rights reserved.
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish.AcceptRead More