Closed Link9001 closed 9 months ago
OK this PR is enormous. I appreciate you taking the time and effort! It'd take me a day to review this, which I don't have that sort of time, but some general feedback:
IMediator
classI JUST moved the MS.DI.Extensions package into MediatR proper because like 98% of folks that use MediatR use both. Don't create separate packages or projects, this was an intentional decision on my part. I don't want any new packages, I want to simplify. MediatR and MediatR.Contracts is good enough.
Past that, can you give a rough overview of what problems this PR is trying to solve? There's a discussion to be had around that.
Thanks for the quick Feedback. Well I didn't know that the Contract Project was more or less finished, but that fine. My implementation works also with the old Version. I can roll that back.
Why I even got here in the first place was due to a Side Project of mine. At the start it went well and was fast. But after some time of development Performance got worse and worse. And after some Benchmarking and Profiling I found some Issues in my Code but while I was at it I saw that the time spend in the Mediator was quite long. I am publishing a lot of messages but I heard that MediatR should be optimized. And then I started looking a little more into MediatR and its implementation. Well I saw potential for improvement. I would like to spend some of the time that currently is spent in the Mediator rather in my App. I am also using DryIoc and not ServiceCollection in my Project and registering MediatR was not that easy. But the Idea of having it in my Project is simplifying multiple things for me. Therefor the general decoupling of the ServiceCollection.
Past that, can you give a rough overview of what problems this PR is trying to solve? There's a discussion to be had around that.
So the only Problem that this PR should fix is the (my) Performance Problem with the current implementation and the registration of MediatR in other Containers than ServiceCollection. But I also think that having more Performance in general is not only appreciate by me.
It is fine for me if the ServiceColletion is still in the Main Nuget. It should have been more of a question then 100% changing it. Except for the ´IRequest´ Interface but it seems that I there was some more work done then I thought.
Another Question would be why are 'dynamic handler' handlers there that accept any object but in the End just cast them to the Message Type or just fail. Shouldn't the User of the Mediator know what Message he is sending? Then why even providing such methods?
Hi @Link9001
Let me start off by saying that it's overall very appreciated by any open-source community to have contributions, be it filing issues, writing docs, investigations, or writing code. Don't let what I'm about to say put you off from achieving your own goals, but take it as good advice.
MediatR is a library with over 135 million total and 40k daily average downloads. It is serving a wide variety of projects, personal, commercial, small, and big. Jimmy has taken on the great responsibility to keep maintaining it in a professional manner. That comes with responsibilities that are not free.
It takes time to review code, especially big changes. Not only 'number of files' changes, but architectural changes (like changing interface/methods names) have to be thought over very well. Is it backward compatible? Does it break current users? Do we need a new major version number? Etc.
That all takes time, and in general, you cannot expect ANY project, be it a one-man open-source project or an entire team behind it, to just dump 200 changed files on them and ask for a review and a credit. The unit tests are there for a reason and should NOT be changed unless absolutely needed and more likely than not being discussed over before.
The fact that you changed so much of the architecture, that an update would break basically anyone using MediatR. That you don't know what the 'Send(object)' are for, and also bluntly asked for a credit in .csproj with the expectation of this getting merged, makes me think you're still a junior developer and have (and should!) still learn a thing or two.
Again, don't let this discourage you from reaching your own goals. You could still use this in your own project, or if you want to maintain your own version, you're always free to fork, give it a name and maintain it in a different repository. That's how open-source works most of the time.
Hi @remcoros I really appreciate your Feedback. I always like to learn new stuff and your comments aren't different. But I also did invest quite some time in how to structure and design a simpler MediatR. Also I think that there are might some misunderstandings regarding my Implementation. Correct me if my understanding is wrong, but my implementation just breaks the naming and some return types of handlers. The behavior of MediatR to send messages and letting them be handled by the Pipelines still remains the same. There will be no architectural breaking changes in the Mediator. And I also want to clarify that I would only apprehend it if I can get some Credits for what I have done. I did never demand @jbogard to make a place only for me so I can get my Credits. I think this is a big difference between demanding and just friendly asking. If he had said "no" then that is his choice. He is the Owner of this Repo and can do what he wants.
The "Send(object)" has of course its usage with the dynamic dispatching of the message type. I was caught up in removing as much reflection Code as possible because reflection is never fast. That is definitely something that I forgot about and that I should add back.
But regardless of that here are my thoughts on the current design of MediatR and how I think that it is more complicated then it should be.
First about the IBaseRequest
Interface. For me it seems that this interface is another Layer to handle both Request Types with a single implementation. But for which reason are there coupled. I can not think that this should be a non-generic abstraction of the Requests because the IStreamRequest
doesn't inherit from it but still is a Request. So what does this Interface provide except for the implementation of the Mediator? And why are two different Message Types even coupled.
The Unit
Type is something that just bothers me the most. The dotnet team designed C# in a very good way. And for the most part you will never have to represent Nothing with a new custom Type. Just use the Task or ValueTask for all aysnc operations that need to return nothing. This seems to me like a Design flaw in the MediatR implementation, that can be fixed. On top of that the Unit
Type just overcomplicates more, then helping to keep MediatR as simple as possible. And in my PR I fixed that issue.
About thous loose generic Constrains on all the IRequest...
Interfaces. Thous generic parameter in should only allow the types that are Requests and nothing else. It can be quite confusing that the Object Type is event allowed in them. If a Developer just adds Types that don't implement any Message related Interfaces are not warned by the Compiler that they are using the wrong Type. Another thing that makes the current MediatR not simpler but more complicated in my Opinion.
The Pipeline Delegates that would now require the request and cancellation token parameter is just the adaption from the MVC Middleware. When something is similar to a Developer that they already know the simpler the Library gets. Most people that are using MediatR are using it with the MVC framework. And a Pipeline and a Middleware are just synonyms for the same thing. So why do it different.
About the Unittests of course they shouldn't be changed. But they also should be good maintainable and also readable. Now there isn't any structure of how Unittests are written which makes them difficult to read and to update. I just copied them over and put them into a structure that is in union with all the other tests. Having different of how the Tests are implemented doesn't make them more maintainable.
The Version have to be updated. But that just comes with it when you update a Library with new Features of C# like ValueTasks. Something updating Code creates Breaking changes. But therefor is the Versioning to let other Developers know that the new Version might break your Code. What I also have seen here is that there aren't always Major Version updates if the public interface changes.
Because making breaking changes shouldn't happen very often. But when they happen then you should at least fix some naming issues from the current implementation. My renaming of the IMediator
Methods from Send
to SendAsync
is just something that should have already happened. Annotating Async Methods with a postfix of Async is not that uncommon today and it also makes the code more readable. This is just a best practice of writing C#.
If you disagree with something in this list, I would appreciate any feedback to make this bigger breaking change worthwhile a new Version of MediatR.
Therefor the general decoupling of the ServiceCollection.
MediatR isn't coupled to ServiceCollection though? It's only coupled to IServiceProvider
.
The optional registration is coupled to ServiceCollection
, but that's absolutely by design. 95% of folks that use MediatR were using the original separated out MediatR.Extensions.Microsoft.DependencyInjection
project, so I combined them together. It's absolutely not required to use with the "guts" of MediatR.
The performance of services.AddMediatR
is not something I've looked much at, mainly because it affects the startup performance, not runtime performance. If you're seeing performance issues with the registration that gets "slower and slower" over time, I suspect it's something wrong with your code. MediatR registration should be a one-time activity for the lifetime of the application.
But the AddMediatR
Method is coupled to the ServiceCollection. I like the idea of a Mediator which abstracts everything. So I added it into my Ui Application that uses DryIoC as its Container. The startup time here actually matters but because I had to write my own AddMediatR
Method that isn't the Performance Issue. I know that the most people will use it with the API. And yes, there isn't the Startup time that relevant. Writing my own AddMediatR
Method wasn't that of a simple task. In the End I had to look at the internal implementation of MediatR itself to figure out how to set it up correctly. And that's why I decoupled the AssemblyScanner and the whole setup with the Pipelines from the ServceCollection and made that you can use it with any container you like.
The Performance that is getting slower over time is because of sending "lots" of Messages with MediatR. My Application is growing and the more Features I add the slower it will get. I already did some benchmarking to figure out where the Bottleneck is. I also did improve in my App quite a bit. But the Exception Handling/Action and also just the sheer amount of Messages that are being sent in parallel is slowly but surly making my App slower. And after looking at the implementation of MediatR I saw lots of Reflection that could be removed and also the potential of Caching the Handlers. All of that will Improve as the Benchmarks show the Performance by a lot.
This PR is stale because it has been open 28 days with no activity. Remove stale label or comment or this will be closed in 14 days.
As as MediatR user I just wanted to add some two cents on MediatR+MicrosoftDI registration. While it's not a problem for me that the registration is built-in the main package, it's a bit confusing that scanning feature is required. We use modular monolith architecture and in some of our products there is no handlers at all in main project, but each module has it's own DI definition, which registers it's modules, including all handlers. It's a multitenant application (tenants as nested containers), so we want to have full control on how and what we register
So we are required to scan an assembly, which has no handlers, just to satisfy this requirement.
This auto-scanning behavior also caused some troubles to this StackOverflow user, so I'd argue that it's not always desired
We can add an explicit opt-out for scanning of handlers OR, remove the requirement that there must be at least one assembly to scan.
@jbogard yeah, from what I can see, this PR removes the requirement, so that's where I wanted to mention it. But since the changes related to MS DI are still under discussion, I just wanted to stress that this part is useful change.
I'm fine with explicit opt-out as well, if that will be preferred
Yes but this PR also does 80 other changes, some of them huuuuge breaking changes. I'll look at other similar libraries like FluentValidation to see how they like to handle it. I don't want to make normal apps harder or more confusing just to satisfy a single architectural style.
This PR is stale because it has been open 28 days with no activity. Remove stale label or comment or this will be closed in 14 days.
This PR was closed because it has been stalled for 14 days with no activity.
As the title might suggest, this PR improves the Performance of the Mediator and also decouples the implementation from Microsoft.Extension.DependencyInjection Package. I haven't updated the Readme and all the other documentation yet because I wanted to wait for the final implementation outcome. I will update it as soon the implementation is Approved.
Features Removed
IRequestExceptionHandler<>
andIRequestExceptionAction<>
Interface with the default for the exception Type asSystem.Exception
are removed.IMediator
with the Request Type is the Object Type. The Reasoning is as already in #894 mentioned that the marker interfaces should guard the user of the Library to pass the wrong message instance to the Mediator.IBaseRequest
was removed because it was not needed anymore.Features Moved
PublishCore
method was moved to theINotficationPublisher
.IRequest
and anIRequest<>
are in my Opinion two different Message Types. One just Commands another Part of the Software to do something and therefor just wants to know how long it has to wait. A Request on the other Hand requests something from another Part of the Software and awaits a Response. And because of this Reasons I split up theIRequest
and theIRequest<>
Pipelines into two different ones.Features Added
Things to Discuss
IRequest
Interface. Currently I just named theIRequest<>
InterfacesIRequestResponse...
to remove naming conflicts. My Proposals would beICommand
for all theIRequest
Interfaces and that use the Request itself.Microsoft.Extension.DependencyInjection
there is the possibility to move the Logic to another Project and create another NuGet out of it. The same applies also for theMediatR.Abstraction
Namespace if you only want the MediatR Interfaces.I put all lot of Time and and effort in the improvements. Therefor I would appreciate it if I could add me as one of the Authors in the Project (.csproj). But bevor you reject this PR please have a look at it.
Benchmarks
Handlers
These Benchmarks show how long it takes to just invoke the corresponding handler.
Old Handler
Improved Handlers
Cached Handlers
Pipelining
These Benchmarks show how long it takes with one Pipeline Handler to invoke the corresponding handlers.
Old Pipeline
Improved Pipeline
Cached Pipeline
Exception Handling
These Benchmarks show how long it takes to handle an Exception that is thrown by the Handler until it is handled by the only Exception Handler. There are no Exception Action Handlers registered.
Old ExceptionHandling
Improved ExceptionHandling
Cached Exception Handling
Assembly Scanner
These Benchmarks show how long it takes to register MediatR in the Service Collection. The most expensive Operation is the Assembly Scanning and registering.
Old Assembly Scanner
Improved Assembly Scanner