martinothamar / Mediator

A high performance implementation of Mediator pattern in .NET using source generators.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

3.0 tracking issue - rewriting source generation

martinothamar opened this issue · comments

Things I want to solve/merge in 3.0.

To solve these I'll be rewriting the source generation process. I will be starting out in an experimental branch
and seek feedback before bringing it into main (which is targeting 3.0 preview).

The task list is sorted from most difficult to least difficult (assumed).

  • Improve performance for large number of messages (#48)
  • Improvements for large solutions (#97)
  • Support generic messages (#76)
  • Fix unfortunate naming conflict between namespace and concrete Mediator type (#65)
  • Support .NET Standard 2.0 (#53)
  • Improve diagnostics (#49)
  • Reorder cancellation token parameters for functions, to realign with MediatR (#52)
  • Remove uneccessary dep on System.Threading.Tasks.Extensions (#86)
  • Generate code with nullable reference types enabled

Another thing I've considered: should this library be renamed? "Mediator" is kind of plain/vanilla. It's basically a working title that I never changed. Feel free to drop suggestions and feedback..

commented

Improve performance for large number of messages #440

Have you looked at using liquid instead of scriban? Fluid appears to be more performant than scriban. Not sure if liquid supports all your requirements

Not considered it yet, but open to it! Would be fairly easy to migrate I think. I'd like to hold off on that until the top 4 issues are completed though, since there will be significant changes in the source generator

commented

I'm not sure how much scriban affects performance tbh. Perhaps parsing the template once and reusing it could help, although I'm not sure if source generators allow this.

I'd like to hold off on that until the top 4 issues are completed though, since there will be significant changes in the source generator

Looking forward to seeing your changes, this project has been super helpful when wokring on Mapperly.

This project looks awesome! I'm interested in a conventional handler approach either on top of this library or as an additional source generator. Something that decouples the handlers from the messaging library like this: https://wolverine.netlify.app/guide/handlers/ It would just discover handlers by convention and generate the concrete handler that implements your IRequestHandler and friends interfaces. Any interest in this?

So that would only affect handlers right? The requests/notifications would still use the appropriate interface?

Related: one thing I like a lot about Wolverine (and Marten) is that there is support for static functions as handlers. The typical case for request/notification handlers is that they are stateless, so it feels silly allocating an object simply to invoke a method. There are often injected services, but they can be injected as parameters to the function as well.. Seeing as this is a performance oriented library, that's something I would like to explore in the future. ASP.NET Core minimal APIs is kind of also going in that direction, having static functions as handlers, and we can now have static abstract methods in interfaces.. Might be something there.

So yes there is some interest there from my side for sure (taking inspiration from the Wolverine approach in general), but I'm not sure how it could be integrated into this library. I'll think some more about this while refactoring the source generator.

Btw, a challenge with source generators is that they don't compose at all, so I don't think this is solvable as an additional source generator (the Mediator source generator wouldn't have access to code generated by another source generator)

Yes, this would only affect the handlers and not sending the messages.

Yeah, it really sucks that source generators can't simply be ordered by a priority with same priority using the same compilation model, but any subsequent ones would get an updated compilation model and be able to inspect code from previous priorities.

Was just looking at dotnet/aspnetcore#48817 and thinking how doing something similar would make an amazing mediator implementation.

Yea it's on our radar for sure. @Tornhoof also brought it up here: #7 (comment)

Since they are bringing it into ASP.NET Core atm I'm assuming the interceptors proposal is basically approved for .NET 8/C# 12? 😄
I am very tempted to go all in on .NET 8 SDK requirement and using interceptors. I haven't been able to come up with a design for 3.0 that solves all the issues in the OP (while keeping the perf that is), but with interceptors everything becomes a lot simpler

Yeah, interceptors was merged into main.

commented

It might be better to wait for these changes, but will you be adding support for incremental pipeline caching? The output from CompilationAnalyzer could be turned into an Equatable friendly type.

I'd be willing to have a go at this.

Since they are bringing it into ASP.NET Core atm I'm assuming the interceptors proposal is basically approved for .NET 8/C# 12? 😄

Most likely, but one thing I've realized about the dotnet codebases is that they'll update everything the moment it's available to be done. It's really quite interesting. They rewrote practically every instance they did null checks for parameters with the !! proposal a few years back before that was reverted. Same with ArgumentNullException.ThrowIfNull(param), and they've said they will with primary Constructors whenever that spec gets finalized.

So while it most likely will pass, I wouldn't go all-in just yet, especially since they've repeatedly said that it is a preview feature that likely will get changed or maybe even removed (although I highly doubt the latter due to just how important it is for the AoT story they're going all-in on)

They are somewhat committed to it because of the AOT story and they are going to be using it a lot (already has multiple PRs accepted) to generate code that supports that. They would need to rewrite a lot of their code as well. Not to mention that this will be a pretty straight forward thing to replace if they come up with a different similar approach.

I am very tempted to go all in on .NET 8 SDK requirement and using interceptors.

#7 (comment)

It's already possible to create a pseudo-interceptor in all versions of .NET. By using the caller line number and caller file path, it is possible to differentiate each caller and respond appropiately.

public static void AddMediator(.... parameters go here, [CallerLineNumber] line = 0, [CallerFilePath] file = "")
{
    // an efficient condition can be built using the line number, file path length and varying chars in the file path
    // not sure how much the compiler will do
    return (line, path) switch
    {
        (0, _) => Mediator1.AddMediator(services),
        (42, { Length: 15 }) => Mediator2.AddMediator(services),
        (42, { Length: 21 }) => Mediator3.AddMediator(services),
        _ => throw new ArgumentOutOfRangeException()
    }
}

public static TResponse Send<TRequest, TResponse>(.... parameters go here, [CallerLineNumber] line = 0, [CallerFilePath] file = "")
{
    ...
}

Note that this may not work if the same method is called repeatedly on the same line. This is very unlikely to occur for Send or AddMediator; however, a check and diagnostic could be used to prevent this.

Speaking about "Support generic messages".
Is this including support of generic Queries/Requests/Commands?

Yes, thats not possible currently, and requires rewriting the source generation of the IMediator implementation

@martinothamar Project is frozen or is there a plan and date for the new version ?

Not fozen, but there is no date/deadline either. How much time I get on this depend on dayjob, personal life etc. Next steps for me is going over and merging the great stuff @TimothyMakkison has been doing, then see what version 3.0 actually becomes

ok, Thanks. I'm currently using version 3 in my project. Therefore, I am interested in the further fate of the package.

My personal request is for integrations with Redis/RabbitMQ/Kafka etc. to incrementally take this into doing distributed requests and requests/responses.

I'd love to have a library that does mediator first and then transparently goes up the chain to distributed requests automatically.

My personal request is for integrations with Redis/RabbitMQ/Kafka etc. to incrementally take this into doing distributed requests and requests/responses.

I'd love to have a library that does mediator first and then transparently goes up the chain to distributed requests automatically.

I think you need to do this in your code yourself. If it is possible to make an example for this package in https://github.com/martinothamar/Mediator/tree/main/samples