martinothamar / Mediator

A high performance implementation of Mediator pattern in .NET using source generators.
MIT License
2.09k stars 70 forks source link

Abstractions to support other dependency injection providers? #10

Open zejji opened 2 years ago

zejji commented 2 years ago

I note that in the documentation you state that:

This library relies on the Microsoft.Extensions.DependencyInjection, so it only works with DI containers that integrate with those abstractions.

Have you considered structuring the library in such a way that use with alternative abstractions would be possible (if not supported currently)? This would allow extensions to be written to support additional DI containers.

The reason I ask is that I am also in the process of writing a mediator library using source generators, but am not nearly as far along in my endeavors. It is my intention to support compile-time DI containers such as StrongInject, Jab and Pure.DI.

Much of the logic would be shared between the implementations (e.g. analyzing the client code to collect information about all the registered handlers and pipeline behaviors), but would differ primarily in the source which is then generated from the collected information.

martinothamar commented 2 years ago

It's probably possible, as you say the analysis part is likely to be unchanged, so all the changes would basically be in the codegen templates. For example the options attribute there uses the ServiceLifetime enum, whereas StrongInject has it's own enum for this.

I'm not 100% sure what the best design would be here, creating common denominator abstractions around lifetime and service location/DI or just create a base template that is included and augmented in a Mediator.SourceGenerator.StrongInject library for example (then if you installed this package, the options attribute would use the Scope enum instead). I would need to investigate this. But basically the lifetime option, current AddMediator method that is generated and service location functions (GetServices, GetRequiredService etc) would need to be changed. And obviously it increases the unittesting scope as different DI containers can provide differences in functionality.

Overall I think it's a good idea, and the reason I haven't done anything about it was for simplicity sake since I'm not currently using anything other than the built in DI, so I'll probably look into this next time I'm working on this. Let me know if you want some input on your implementation and feel free to link it 😄

zejji commented 2 years ago

@martinothamar - Many thanks for your reply. I'll also take a look over the Mediator source code to see if there is a neat way to build in the extension point. I'm conscious that writing multiple implementations would significantly increase the project scope, but it would be great if it can be done in a way which permits additional community implementations without increasing your maintenance burden!

Hau-Hau commented 1 year ago

Hi and thanks for great package! I would love to see support for other DI providers!

I believe that for Jab it should be pretty straitghforward. Additionally - if this information is helpful - Jab supports IServiceProvider interface. Jab has module system so probably template should look like that:

  [Jab.ServiceProviderModule]
  {{~ for message in RequestMessages ~}}
  [Jab.{{ message.Handler.ServiceLifetimeShortName }}(typeof({{ message.HandlerWrapperTypeNameWithGenericTypeArguments }}))]
  [Jab.{{ message.Handler.ServiceLifetimeShortName }}(typeof({{ message.Handler.FullName }}))]
  [Jab.{{ message.Handler.ServiceLifetimeShortName }}(typeof({{ message.PipelineHandlerType }}))]
  {{~ end ~}}
  public interface IMediatorModule
  {
  }

Where message.Handler.ServiceLifetimeShortName should be just keyword "Singleton", "Transient" or "Scoped".

Then in application it could be used like that:

[ServiceProvider]
[Import(typeof(IMediatorModule))]
public class ServiceProvider
{
}

Also another related question is - how to distribute Mediator for other DI providers?

Edit 24.09.2022 I have took a look on code, tried to make it working with Jab. I did not even touch topic of tests at this moment. Basically - there is not so much changes for this specific DI Provider, however thing that makes a lot of troubles is packing and distributing. At this moment it would be really hard to distinguish common denominator because even analyzer part of Mediator.SourceGenerator.Implementation.Jab are slightly different than default Mediator.SourceGenerator.Implementation.

It generates situation when basically we would need to have packages grouped like that:

Most of code of these packages would be almost same. Maybe it is field to just extract interfaces as a "common denominator".

Another case is with Mediator.SourceGenerator.Roslyn38 and Mediator.SourceGenerator.Roslyn40; these two packages depends directly on default Mediator.SourceGenerator.Implementation. In my opinion these roslyn packages could operate on interfaces, however one of ideas is to create additional project upfront of them to provide solid implementation per vendor.

Below you can see simplified diagram that represnts my idea. mediator

It would be great to create minimal base which would be a guide how to implement additional providers because at this moment even if I would want publish changes to that supports Jab they would exclude Microsoft.Extensions.DependencyInjection and break a lot of tests.

Hau-Hau commented 1 year ago

I have pushed working proof of work for Jab DI container on my branch. Not so much changes in comparison to default implementation, most of them were in template, message handlers and RequestMessage class. Here you can take a look on changes: https://github.com/martinothamar/Mediator/commit/c9249fa39cf9572308027d3036893e6bf8b32d4b

Additionally - I affraid that it is not possible to make these two generators works gracefully together when https://github.com/dotnet/roslyn/issues/57239 is not finished.