C
C#β€’14mo ago
Thinker

βœ… What's the point of MediatR?

I haven't done a lot of web/API dev, so this might just be because I've never been in a situation where I've actually needed it, but what is the point of MediatR? I don't really understand it too well, so if someone could give me a rundown, that would be incredible.
81 Replies
Pobiega
Pobiegaβ€’14mo ago
Clarification: Do you truly mean the mediator pattern, or do you mean libraries like MediatR etc?
Thinker
Thinkerβ€’14mo ago
Isn't MediatR an implementation or the mediator pattern?
Pobiega
Pobiegaβ€’14mo ago
See, that would make sense! But no, its not πŸ™‚
Despite the name, MediatR does not implement the Mediator Pattern at all: it’s a Command Dispatcher.
Thinker
Thinkerβ€’14mo ago
ah yeah okay I had it backwards then, my actual question would be why do people use MediatR?
Pobiega
Pobiegaβ€’14mo ago
Well, there are a few benefits and downsides First, having a single handler for a single command hierarchy makes it more focused than an entire service class, while still letting it be mocked out for tests Easy to find what you are looking for and it's not going to be a thousand line service class, so that's good. Also resolves it's own dependencies, so you don't need to do that in the endpoint or whatever caller you are using Third, not common in asp but sometimes you might have a command type hierarchy and not know the concrete implementation. An example is a paser for a command line app, where you just parse and fire away Downsides? Well it's just a glorified method invocation It does add complexity where it might not be needed Oh and most dispatchers use a pipeline, so you can register middleware This lets you handle transactions
rodrigovaz
rodrigovazβ€’14mo ago
Another point, a lot of people use MediatR to implement CQRS and not necessarily the Mediator pattern in itself
Thinker
Thinkerβ€’14mo ago
sorry, what's a "command hierarchy"?
Pobiega
Pobiegaβ€’14mo ago
Commands related to eachother, either by nature of what they do (two ways to add something) or by actual inheritance (all commands are ICommands) Why is that a benefit of MediatR?
rodrigovaz
rodrigovazβ€’14mo ago
Not reinventing the wheel
Pobiega
Pobiegaβ€’14mo ago
... what?
Thinker
Thinkerβ€’14mo ago
And a command is just some kind of "action"? Like... send an email or update a db or whatever
Pobiega
Pobiegaβ€’14mo ago
So because it can be used to implement CQRS, that counts as a plus ? Wtf Yep It's a glorified method invocation:)
Thinker
Thinkerβ€’14mo ago
right catsip
Pobiega
Pobiegaβ€’14mo ago
It does allow your method calls to be serialized thou. Useful for things like undo
rodrigovaz
rodrigovazβ€’14mo ago
I meant that you will see the MediatR library in the wild a lot of times because people wanted to implement CQRS (which benefits from using the mediator pattern) and the internet is filled with "implementing CQRS with MediatR" tutorials
Pobiega
Pobiegaβ€’14mo ago
I agree with that But how is that "the point" of MediatR? Also, MediatR is not an implementation of the mediator pattern This is important
Thinker
Thinkerβ€’14mo ago
why is it even named that if it doesn't implement the mediator pattern?
Pobiega
Pobiegaβ€’14mo ago
No clue But it doesn't. It was just a command dispatcher and then they added a message Hub to it
rodrigovaz
rodrigovazβ€’14mo ago
It isn't, it was more of a heads up that the library does much more than simply helping implementing CQRS. I had the same question once and my doubt arose from looking at open source projects that used it for CQRS I really like that in their GitHub page it says "Simple mediator implementation in .NET"
Pobiega
Pobiegaβ€’14mo ago
I'm traveling right now, but I'll try to find an excellent article that shows the difference But yeah, CQRS is a big factor of why it's popular for sure. There is a Greg Young talk about CQRS and simplicity that is very good
Tvde1
Tvde1β€’14mo ago
What MediatR does, is replace classes that end in ...Service or ...Manager with a class that has only one method. whether it's
interface IUserCreationService
{
Result<User> CreateUser(CreateUserDto request);
}
interface IUserCreationService
{
Result<User> CreateUser(CreateUserDto request);
}
or whether it's
record CreateUserCommand(string name, ...) : ICommand<Result<User>>;

class CreateUserHandler : IHandler<CreateUserCommand>
{
record CreateUserCommand(string name, ...) : ICommand<Result<User>>;

class CreateUserHandler : IHandler<CreateUserCommand>
{
The result is that you're thinking in small functionalities that handle a command or request, instead of making large classes that do a bunch of inbetween things I want to put this way of thinking into my codebases, without using mediator. I want to see more very very small interfaces and
// some code
Result<User> userRes = _mediatr.Send<CreateUserCommand>(..);
// some code
Result<User> userRes = _mediatr.Send<CreateUserCommand>(..);
and
// some code
Result<User> userRes = _createUserService.Create(...);
// some code
Result<User> userRes = _createUserService.Create(...);
Are roughly similar
Thinker
Thinkerβ€’14mo ago
But you still need these small Command classes and Handler classes
Tvde1
Tvde1β€’14mo ago
luckily we don't need to watch how many kilobytes of code we write. You usally have a few GBs free on your disk :) whether it's an interface + implementation, or a command + handler, you'll write nearly just as much
Thinker
Thinkerβ€’14mo ago
Ah that's what this meant, instead of calling a method on a service, you use Send<T>(new T(...));
Tvde1
Tvde1β€’14mo ago
it feels like the service locator antipattern, which makes spaghetti code, but it isn't really you're not specifying a dependency, you just say "I want this command done. I don't care who does it, but I want this result from this input"
Pobiega
Pobiegaβ€’14mo ago
yes!
Pobiega
Pobiegaβ€’14mo ago
Pobiega
Pobiegaβ€’14mo ago
"The Mediator Pattern" is the section that breaks down how this isn't a mediator Oh, and the primary drawback (at least if you ask me) is that intellisense can't tell you that you have references to the handler
Tvde1
Tvde1β€’14mo ago
damn that is a very good article
Pobiega
Pobiegaβ€’14mo ago
ie, if you want to jump to the AddCommandHandler.Handle method, you need to know that its called such
Tvde1
Tvde1β€’14mo ago
it reflects the thoughts that I didn't know how to word
Pobiega
Pobiegaβ€’14mo ago
Mhm, I felt the same way.
Tvde1
Tvde1β€’14mo ago
usually you put the command and the handler in the same file
Pobiega
Pobiegaβ€’14mo ago
Yeah I know.. unfortunately at work we have stylecop that enforces no more than one class per file and I hate it πŸ˜„
Tvde1
Tvde1β€’14mo ago
that's... unproductive
Thinker
Thinkerβ€’14mo ago
Also if I wanted to write a MediatR handler for like creating a user or whatever, would I inject the EF db context and other related things directly in the handler?
Pobiega
Pobiegaβ€’14mo ago
we use a home-rolled command dispatcher thou, not mediatr. but its similar enough yep the idea is that the command would contain all the data needed, and the handler all the code needed.
Thinker
Thinkerβ€’14mo ago
ah, so it's like minimal APIs vs. controllers, where minimal APIs can request the minimum amount of things they actually need
Tvde1
Tvde1β€’14mo ago
having tiny handlers, all separated from other handlers would also allow you to make refactoring easier. If you switch to a different DB, you don't have to switch out one big layer to another, but you can approach it per handler you can also make controllers with just one endpoint :)
Pobiega
Pobiegaβ€’14mo ago
@rodrigovaz https://discord.com/channels/143867839282020352/1155864560859873411/1155883188032380978 thats the article I mentioned So Thinker, do you feel the question has been answered? πŸ™‚
Thinker
Thinkerβ€’14mo ago
Probably, at least I think I got a better understanding of what the point is, to the point I could see why I would use it myself. Actually nevermind, I have another question, why split a request handler and endpoint in the first place? Like if I have an endpoint which one job is to create something, why should I separate that into a request handler which creates the thing and an endpoint which literally just calls the handler with the request object?
Tvde1
Tvde1β€’14mo ago
you don't want to be tightly coupled to ASP.net what if you decide to be event bus-based, then you want your business logic to be accessible through a message consumer instead of that consumer needing ASP.net dependencies but, you could always refactor that later imo unless you start returning action results in your code imo a business logic csproj should never know it's being called by ASP.net
Thinker
Thinkerβ€’14mo ago
Well, I made this just to test it, but it feels dumb because like these could just be one method
public sealed record CreateArtworkRequest(
string? Title,
string? Description,
HighlightColor HighlightColor,
LocalDate UploadDate,
IReadOnlyCollection<int> Categories)
: IRequest<Artwork>;

public sealed class CreateArtworkHandler(ArtContext context)
: IRequestHandler<CreateArtworkRequest, Artwork>
{
public async Task<Artwork> Handle(CreateArtworkRequest request, CancellationToken cancellationToken)
{
var categories = await context.Categories
.Where(x => request.Categories.Contains(x.Id))
.ToListAsync(cancellationToken);

var entity = new ArtworkEntity()
{
Title = request.Title,
Description = request.Description,
HighlightColor = request.HighlightColor,
UploadDate = request.UploadDate,
Categories = categories,
};

context.Artworks.Add(entity);

await context.SaveChangesAsync(cancellationToken);

return entity.ToArtwork();
}
}

public sealed class CreateArtworkController
{
[HttpPost("/art")]
public async Task<IResult> Create(
[FromServices] IMediator mediator,
[FromBody] CreateArtworkRequest request)
{
var artwork = await mediator.Send(request);
return Results.Created("/", artwork);
}
}
public sealed record CreateArtworkRequest(
string? Title,
string? Description,
HighlightColor HighlightColor,
LocalDate UploadDate,
IReadOnlyCollection<int> Categories)
: IRequest<Artwork>;

public sealed class CreateArtworkHandler(ArtContext context)
: IRequestHandler<CreateArtworkRequest, Artwork>
{
public async Task<Artwork> Handle(CreateArtworkRequest request, CancellationToken cancellationToken)
{
var categories = await context.Categories
.Where(x => request.Categories.Contains(x.Id))
.ToListAsync(cancellationToken);

var entity = new ArtworkEntity()
{
Title = request.Title,
Description = request.Description,
HighlightColor = request.HighlightColor,
UploadDate = request.UploadDate,
Categories = categories,
};

context.Artworks.Add(entity);

await context.SaveChangesAsync(cancellationToken);

return entity.ToArtwork();
}
}

public sealed class CreateArtworkController
{
[HttpPost("/art")]
public async Task<IResult> Create(
[FromServices] IMediator mediator,
[FromBody] CreateArtworkRequest request)
{
var artwork = await mediator.Send(request);
return Results.Created("/", artwork);
}
}
Pobiega
Pobiegaβ€’14mo ago
Agree with all the above points. What if you later must add items not via an endpoint, but via a message queue? or via a CLI app? or a desktop app? if its all in the endpoint, you can't easily.
Tvde1
Tvde1β€’14mo ago
I have switched a bunch of endpoints to azure service bus consumers lately and some of them were a pain
Pobiega
Pobiegaβ€’14mo ago
let the controller/endpoint handle the HTTP specifics and nothing else (except calling the correct command) Yeah, I've had to implement CLI tools that do a lot of business logic from our big ASP application recently
Thinker
Thinkerβ€’14mo ago
So I should have a separate project which contains all the request handlers?
Pobiega
Pobiegaβ€’14mo ago
it was trivial, since we use a dispatcher
Tvde1
Tvde1β€’14mo ago
we have MyApplication.API, MyApplication.Business, MyApplication.Data
Pobiega
Pobiegaβ€’14mo ago
imho, yes. that would be your business logic project
Tvde1
Tvde1β€’14mo ago
etc etc
Pobiega
Pobiegaβ€’14mo ago
same here
Tvde1
Tvde1β€’14mo ago
Data.Database though, cause external dependencies get an e.g. MyApplication.Data.Discord etc
Thinker
Thinkerβ€’14mo ago
I have an API, EF/data, and "common" project (mainly containing models), it already feels somewhat like I'm splitting these up for no reason other than out of principle
Tvde1
Tvde1β€’14mo ago
be careful with "common" projects D: my codebase has waaay too many and models/DTOs end up not being common but they are used across projects
Thinker
Thinkerβ€’14mo ago
Yeah I just have no idea where else I would put the models which aren't EF entities
Tvde1
Tvde1β€’14mo ago
some like MyApplication.Domain for their domain models
Pobiega
Pobiegaβ€’14mo ago
Common would be stuff like a Result type, or other similar things
Tvde1
Tvde1β€’14mo ago
love those
Pobiega
Pobiegaβ€’14mo ago
we have Domain for domain models, and .Contracts for our public facing DTOs
Tvde1
Tvde1β€’14mo ago
I like that
Pobiega
Pobiegaβ€’14mo ago
that could easily be inside API as well, if you know you dont need it (for a client for example)
Tvde1
Tvde1β€’14mo ago
I'm drafting an API layout so that we improve on these things (e.g. internal models are given to the outside, and shared through csprojs with microservices) and I want to cut that all out
Thinker
Thinkerβ€’14mo ago
So like - API - Requests/request handlers - Models/domain/whatever - EF/data
Tvde1
Tvde1β€’14mo ago
you could put requests in your domain too
Thinker
Thinkerβ€’14mo ago
I guess
Pobiega
Pobiegaβ€’14mo ago
yup, seems like a good start to me
Tvde1
Tvde1β€’14mo ago
Thinker have you seen Accord's code?
Thinker
Thinkerβ€’14mo ago
@Accord?
Tvde1
Tvde1β€’14mo ago
GitHub
GitHub - patrickklaeren/Accord: A C# Discord bot with moderation, X...
A C# Discord bot with moderation, XP/guild participation and utilities - GitHub - patrickklaeren/Accord: A C# Discord bot with moderation, XP/guild participation and utilities
Tvde1
Tvde1β€’14mo ago
you'll like this
Pobiega
Pobiegaβ€’14mo ago
this is primarily for larger or more serious codebases btw. its not worth for a small app or a POC
Thinker
Thinkerβ€’14mo ago
4 projects??? only 4???
Pobiega
Pobiegaβ€’14mo ago
πŸ˜„
Thinker
Thinkerβ€’14mo ago
man MODiX looks like a beast in comparison
Tvde1
Tvde1β€’14mo ago
at work I have 113 csprojs lol
No description
Tvde1
Tvde1β€’14mo ago
worst thing is, this is ~15 ""microservice"" APIs and they share so many csprojs
Thinker
Thinkerβ€’14mo ago
what's a microservice again when
Pobiega
Pobiegaβ€’14mo ago
its like a unicorn
Tvde1
Tvde1β€’14mo ago
rare
Pobiega
Pobiegaβ€’14mo ago
a magical thing that doesnt exist πŸ˜„
Want results from more Discord servers?
Add your server