MediatR 3.0 ideas

Created on 15 Sep 2016  Â·  82Comments  Â·  Source: jbogard/MediatR

We've been using MediatR now for 3 years in production, and I wanted to gather some lessons learned.

  1. We generally create our own MediatR pipeline (see https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/)
  2. While we generally have a pipeline, what goes in the pipeline is pretty different. We have validation, logging, pre-post processing - it's all over the place.
  3. Registering delegates in the container is annoying and weird
  4. People seem to want to customize dispatching behaviors. See #101
  5. Creating multiple mediator pipelines for the different flavors of the handler interfaces is also annoying

Any other lessons learned from folks out there? @khellang or anyone else?

Most helpful comment

I propose the following public API:

Mediator

public interface IAsyncMediator
{
    // Calls IAsyncRequestHandler<TRequest>
    Task SendAsync<TRequest>(TRequest request, CancellationToken cancellationToken = default(CancellationToken));

    // Calls IAsyncRequestHandler<TRequest, TResponse>
    Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default(CancellationToken));

    // Calls IAsyncNotificationHandler<TNotification>
    Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default(CancellationToken));
}

public interface IMediator
{
    // Calls IRequestHandler<TRequest>
    void Send<TRequest>(TRequest request);

    // Calls IRequestHandler<TRequest, TResponse>
    TResponse Send<TResponse>(IRequest<TResponse> request);

    // Calls INotificationHandler<TNotification>
    void Publish<TNotification>(TNotification notification);
}

Requests

// This interface exists purely to infer the TResponse type for the calling method.
public interface IRequest<out TResponse> { }

Request Handlers

// There's now two types of synchronous request handler interfaces.
// One that doesn't return a result:
public interface IRequestHandler<in TRequest>
{
    void Handle(TRequest request);
}

// And one that *does* return a result:
public interface IRequestHandler<in TRequest, out TResponse> where TRequest : IRequest<TResponse>
{
    TResponse Handle(TRequest request);
}

Async Request Handlers

// Just like the synchronous handler interface, there's no only two asynchronous counterparts.
// One that doesn't return a result:
public interface IAsyncRequestHandler<in TRequest>
{
    Task HandleAsync(TRequest request, CancellationToken cancellationToken);
}

// And one that *does* return a result:
public interface IAsyncRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    Task<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken);
}

Notification Handler

public interface INotificationHandler<in TNotification>
{
    void Handle(TNotification notification);
}

Async Notification Handler

public interface IAsyncNotificationHandler<in TNotification>
{
    Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
}

This cleans up quite a few icky things:

  • There's no INotification and IRequest marker interfaces. These are not needed for anything and is just noise, IMO.
  • Because we have the TNotification and TRequest types at compile-time, we can easily construct the following types without using MakeGenericType:

    • INotificationHandler<TNotification>

    • IAsyncNotificationHandler<TNotification>

    • IRequestHandler<TRequest>

    • IAsyncRequestHandler<TRequest>

  • This is much faster and cleaner. It means we can get rid of the weird internal wrapper classes, ditch the type caches, and just ask the factory/container directly. The only methods left, are the ones that returns a TResponse.
  • The notification/request itself no longer dictates if it should be handled sync, async or "cancellable"-async. That's up to the caller to decide. There's no IAsyncNotification, IAsyncRequest<TResponse> or ICancellableAsyncRequest<TResponse>. You can use any object for the methods that returns Task or void, or IRequest<TResponse> for methods that returns a TResponse or Task<TResponse>. I think this cleans up the API considerably.
  • Having interfaces for request handlers that does and doesn't return a response means we can remove the abstract base class.
  • When I'm doing web apps, _everything_ is async. Since I'm only ever interested in async methods, I think it makes sense to split IAsyncMediator/IMediator to clean up the API, so I can inject IAsyncMediator into my controllers.

A few other notes:

  • I think it makes sense to build on top of Microsoft.Extensions.DependencyInjection and IServiceProvider. This gets rid of the icky factory delegates and should make container integration trivial for the ones that already have adapters for IServiceProvider. If not, it should be pretty quick to implement it yourself. We can simply provide an extension method for IServiceCollection called AddMediatR. This would make it _very_ convenient for users of ASP.NET Core, which I assume will be a majority of the users going forward.
  • If we also build on top of Scrutor we could add options to the extension method that could scan, register and decorate the handlers in specific assemblies, in a container-agnostic way. I think this could be really nice.
  • The extension method could also offer options to select what kind of dispatching strategy you'd want. We'd simply pull an INotificationDispatcher implementation from the container. We could ship with a SequentialNotificationDispatcher and a ParallelNotificationDispatcher to choose from. If you want to do something crazy, you could implement your own.

All 82 comments

Often want to send/publish messages to queue/topic, but use the same MediatR API for handling the dispatching and executing once it's handed back to the process. Basically a distinction between immediately incoming in-process and having Send/Publish delegate to something else.

@dcomartin what does that look like for you today?

Re the pipeline... It would be very neat if we could have a standard way to define a pipeline in a similar way to how you'd build an OWIN pipeline...

@flytzen what does your pipeline look like today? I'm gathering examples

What about async requests/notifications and handlers only? I find myself using IAsyncRequest all the time and use Task.FromResult() in a few cases where (yet) no async calls are made. It would clean up the API surface a bit and makes registering and decorating the interfaces easier.

It depends on the ratio/your usage. If you have 2 sync handlers out of 50 async then yes, I would use Task.FromResult() too. But if one has mostly or only sync handlers it's unlikely the overhead they are willing and have to pay for.

Or split Mediator into 2: AsyncMediator implementing IAsyncMediator (or just IMediator) and SyncMediator implementing ISyncMediator (or just IMediator).

@flytzen @jbogard I have integrated mediatr directly into owin, so bypassing controllers completely. It would be interesting to see more around this direct integration. Why use controllers when the only implementation is a call to Mediatr

@jbogard If you're thinking about building on top of MS.Ext.DI, what about taking a dependency on Scrutor and add some nice API on top for discovering and decorating handlers? I think that would be pretty neat :smile:

I propose the following public API:

Mediator

public interface IAsyncMediator
{
    // Calls IAsyncRequestHandler<TRequest>
    Task SendAsync<TRequest>(TRequest request, CancellationToken cancellationToken = default(CancellationToken));

    // Calls IAsyncRequestHandler<TRequest, TResponse>
    Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default(CancellationToken));

    // Calls IAsyncNotificationHandler<TNotification>
    Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default(CancellationToken));
}

public interface IMediator
{
    // Calls IRequestHandler<TRequest>
    void Send<TRequest>(TRequest request);

    // Calls IRequestHandler<TRequest, TResponse>
    TResponse Send<TResponse>(IRequest<TResponse> request);

    // Calls INotificationHandler<TNotification>
    void Publish<TNotification>(TNotification notification);
}

Requests

// This interface exists purely to infer the TResponse type for the calling method.
public interface IRequest<out TResponse> { }

Request Handlers

// There's now two types of synchronous request handler interfaces.
// One that doesn't return a result:
public interface IRequestHandler<in TRequest>
{
    void Handle(TRequest request);
}

// And one that *does* return a result:
public interface IRequestHandler<in TRequest, out TResponse> where TRequest : IRequest<TResponse>
{
    TResponse Handle(TRequest request);
}

Async Request Handlers

// Just like the synchronous handler interface, there's no only two asynchronous counterparts.
// One that doesn't return a result:
public interface IAsyncRequestHandler<in TRequest>
{
    Task HandleAsync(TRequest request, CancellationToken cancellationToken);
}

// And one that *does* return a result:
public interface IAsyncRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    Task<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken);
}

Notification Handler

public interface INotificationHandler<in TNotification>
{
    void Handle(TNotification notification);
}

Async Notification Handler

public interface IAsyncNotificationHandler<in TNotification>
{
    Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
}

This cleans up quite a few icky things:

  • There's no INotification and IRequest marker interfaces. These are not needed for anything and is just noise, IMO.
  • Because we have the TNotification and TRequest types at compile-time, we can easily construct the following types without using MakeGenericType:

    • INotificationHandler<TNotification>

    • IAsyncNotificationHandler<TNotification>

    • IRequestHandler<TRequest>

    • IAsyncRequestHandler<TRequest>

  • This is much faster and cleaner. It means we can get rid of the weird internal wrapper classes, ditch the type caches, and just ask the factory/container directly. The only methods left, are the ones that returns a TResponse.
  • The notification/request itself no longer dictates if it should be handled sync, async or "cancellable"-async. That's up to the caller to decide. There's no IAsyncNotification, IAsyncRequest<TResponse> or ICancellableAsyncRequest<TResponse>. You can use any object for the methods that returns Task or void, or IRequest<TResponse> for methods that returns a TResponse or Task<TResponse>. I think this cleans up the API considerably.
  • Having interfaces for request handlers that does and doesn't return a response means we can remove the abstract base class.
  • When I'm doing web apps, _everything_ is async. Since I'm only ever interested in async methods, I think it makes sense to split IAsyncMediator/IMediator to clean up the API, so I can inject IAsyncMediator into my controllers.

A few other notes:

  • I think it makes sense to build on top of Microsoft.Extensions.DependencyInjection and IServiceProvider. This gets rid of the icky factory delegates and should make container integration trivial for the ones that already have adapters for IServiceProvider. If not, it should be pretty quick to implement it yourself. We can simply provide an extension method for IServiceCollection called AddMediatR. This would make it _very_ convenient for users of ASP.NET Core, which I assume will be a majority of the users going forward.
  • If we also build on top of Scrutor we could add options to the extension method that could scan, register and decorate the handlers in specific assemblies, in a container-agnostic way. I think this could be really nice.
  • The extension method could also offer options to select what kind of dispatching strategy you'd want. We'd simply pull an INotificationDispatcher implementation from the container. We could ship with a SequentialNotificationDispatcher and a ParallelNotificationDispatcher to choose from. If you want to do something crazy, you could implement your own.

@khellang love everything proposed (if it's really doable) but dependencies: please don't take any dependencies, neither 3rd party nor Core. Not everybody uses or plans to use Core. One of the main advantages of Mediator is that it's slick and dependency-free, one results in another. Create child packages instead where you'll have all you helpers.

@abatishchev What do you mean by "Core"?

Currently there is just one ctor accepting factory delegates. However, Mediator itself has no use of them. Handlers cache has, and this should be separated and abstracted into IHandlerCache. Parent package will ship InMemoryHandlerCache, another option would be MemoryCacheHandlerCache using MemoryCache from Runtime.Caching.dll.

I faced this issue when tried to customize parallel/sequential dispatch. Mediator class has very tight dependencies on internal helpers including handlers cache what makes hard sub-classing or any other classes manipulation/composition.

@khellang .NET Core

@abatishchev

Handlers cache has, and this should be separated and abstracted into IHandlerCache. Parent package will ship InMemoryHandlerCache, another option would be MemoryCacheHandlerCache using MemoryCache from Runtime.Caching.dll.

It's not a handler cache. It's caching runtime generated handler types. This was due to Mono not doing the same reflection caching as the CLR.

I'm not sure why you would cache handlers. They should be cheap to create and used only for a single dispatch. If you want to reuse handlers across several dispatches, like for the lifetime of a HTTP request, you can tweak that in your container or factory delegate. Having a pluggable service, like IHandlerCache for this seems like insane overkill. It's merely an implementation detail to make things go a bit faster on some platforms.

Mediator itself has no use of them.

It doesn't? Of course it has. It uses them to get the handler instances. It has nothing to do with any handler caching :smile:

.NET Core

MediatR already runs on .NET Core. I'm not sure what that has to do with anything.

Re: Scrutor - Originally the MediatR.Microsoft.Extensions.DependencyInjection package took a dependency on Scrutor, but there were edge cases Scrutor didn't support, so I had to grab the open generic registration code from StructureMap instead. In any case, I didn't want to have my package depend on any one container for registration.

My point is do not to take hard dependency on .NET Core's DI infrastructure because it sucks and independently taking a hard dependency on .NET Core in non-Core compatible package sucks too.

@abatishchev it lives in a separate package today and I think it should stay that way.

@henkmollema yes, and please continue doing so. I think this is the best practice. @jbogard echoes this sentiment

My point is do not to take hard dependency on .NET Core's DI infrastructure because it sucks and independently taking a hard dependency on .NET Core in non-Core compatible package sucks too.

IServiceProvider isn't ".NET Core DI infrastructure" (there's no such thing). It's been in the .NET framework since v1.1.

IServiceProvider is OK - all containers support it. It's registration I wouldn't take a dependency on (i.e., Scrutor).

@jbogard

but there were edge cases Scrutor didn't support, so I had to grab the open generic registration code from StructureMap instead.

I'm curious what these edge cases were...

@khellang there such thing, it's called Microsoft.Extensions.DependencyInjection, and it's Core-only. I'm fine with System.IServiceProvider, it's always was there indeed.

@khellang in the MediatR examples, you'll see that the Scrutor output doesn't match StructureMap, for example. StructureMap:

Sending Ping...
Received: Ping Pong
Sending Ping async...
Received async: Ping Pong
Publishing Pinged...
Got notified.
Got pinged.
Got pinged also.
Publishing Pinged async...
Got notified also async.
Got pinged async.
Got pinged also async.

Scrutor:

Sending Ping...
Received: Ping Pong
Sending Ping async...
Received async: Ping Pong
Publishing Pinged...
Got pinged.
Got pinged also.
Publishing Pinged async...
Got pinged async.
Got pinged also async.

It's missing the types that can close via the variance options:

public class GenericHandler : INotificationHandler<INotification>

StructureMap registration:

var container = new Container(cfg =>
{
    cfg.Scan(scanner =>
    {
        scanner.AssemblyContainingType<Ping>();
        scanner.AssemblyContainingType<IMediator>();
        scanner.WithDefaultConventions();
        scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>));
        scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncRequestHandler<,>));
        scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
        scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncNotificationHandler<>));
    });
    cfg.For<SingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t));
    cfg.For<MultiInstanceFactory>().Use<MultiInstanceFactory>(ctx => t => ctx.GetAllInstances(t));
    cfg.For<TextWriter>().Use(Console.Out);
});

Scrutor:

var services = new ServiceCollection();

services.AddScoped<SingleInstanceFactory>(p => t => p.GetRequiredService(t));
services.AddScoped<MultiInstanceFactory>(p => t => p.GetRequiredServices(t));

services.AddSingleton(Console.Out);

// Use Scrutor to scan and register all
// classes as their implemented interfaces.
services.Scan(scan => scan
    .FromAssembliesOf(typeof(IMediator), typeof(Ping))
    .AddClasses()
    .AsImplementedInterfaces());

there such thing, it's called Microsoft.Extensions.DependencyInjection, and it's Core-only. I'm fine with System.IServiceProvider, it's always was there indeed.

No there is not.

First of all, it's not a part of .NET Core (corefx) and nowhere is it stated that it is official ".NET Core DI infrastructure". It's a NuGet package, just like StructureMap or Autofac are NuGet packages.

Second, it's not core-only _at all_. The abstraction package is netstandard1.0 and the main package is netstandard1.1. So it basically supports more or less everything from .NET 4.5 and up. Even Windows Phone Silverlight 8.0/8.1.

We need to stop spreading FUD :confused:

Regarding handlers cache. Right, I phrased it wrong, it's obviously handlers types cache.
I was talking about classes composition and dependencies. In particular, currently Mediator.cs does everything: creates handlers, caches types and dispatches. This complicates reusing of the existing code: I tried to write a decorator of Mediator.cs but it required access to the internals of Mediator.cs.
Existing infrastructure is marked internal and isn't reusable (has no interfaces, etc.). It's fine, it wasnt designed this way. And rethink that is what I suggest.

@jbogard From the way you're registering those classes, I don't understand how that can be Scrutor's fault. I'm thinking this might be a bug in Microsoft.Extensions.DependencyInjection. I'll see if I can reproduce this and send them a bug report.

Oh, come one!
For instance Steven @dotnetjunkie calls it "ASP.NET Core DI abstraction: "https://simpleinjector.org/blog/2016/06/whats-wrong-with-the-asp-net-core-di-abstraction/
It was created as part of .NET Core and it's intended use is within.NET Core.

For instance Steven @dotnetjunkie calls it "ASP.NET Core DI abstraction

Yes, because it _is_ the "ASP.NET Core DI abstraction". That's completely right.

It was created as part of .NET Core and it's intended use is within.NET Core.

ASP.NET Core is not a part of .NET Core. It's a different thing. It runs on .NET Core, yes, but it's also intended to run just as well on full, fat .NET Framework :smile:

Just a friendly reminder that the whole Core thing has started with ASP.NET 6, later rebranded as Core. Ok, this is now completely off-topic.

Just a friendly reminder that the whole Core thing has started with ASP.NET 6, later rebranded as Core.

It started with ASP.NET 5, at the same time as .NET Core, which is the reason why ASP.NET 5 was changed to ASP.NET Core (_after_ .NET Core). Still doesn't imply that ASP.NET Core is a part of .NET Core. Again, it runs just as fine on .NET Framework :stuck_out_tongue_closed_eyes:

Ok, this is now completely off-topic.

Yes. This is going nowhere.

@khellang one of the base design goals of MediatR was to _not_ depend on any container. I used to depend on the MS abstraction of Common Service Locator. IServiceProvider is about the most I'd want to support - and only because all the containers support it. I didn't want to have my own set of factory classes or require a specific container.

Right. I'm not saying we necessarily have to add this to the core package, but using IServiceProvider makes it much easier to add a package for MS.Ext.DI or Scrutor on top :smile:

Cool. And fix your bug :)

Want to sanity-check this before I file it? https://gist.github.com/khellang/408e0ce5e959e1216ad9be75ddabb0fa

My head always explodes when I'm dealing with co- and contravariance :scream:

@khellang looks good, except you're missing all the access modifiers ;P

  1. I like MediatR because it's not a framework. It requires no configuration and brings no dependencies. It's just a little bit of code to do "generic dispatch".
  2. I'm on board with @khellang's proposed public API. Although I would opt for overloaded methods rather than optional parameters on a public API. But in general I like that it simplifies things both for the user _and_ the library. And it addresses the only annoyance I have today with "void" responses. It's perfectly fine that it is a breaking change.
  3. There are no missing "features" that I wish MediatR had. Nearly everything else I need (pipelines, decorators, etc.) can be accomplished with my DI framework of choice. Sure, sometimes configuration is a bit awkward. But I feel that's a problem of the DI framework and not MediatR.

Although I would opt for overloaded methods rather than optional parameters on a public API.

Yeah, normally I would never keep optional parameters in a public API, as they do not version very well, but I tend to follow the .NET team's guidance on default parameters on CancellationToken:

Passing cancellation tokens is done with an optional parameter with a value of default(CancellationToken), which is equivalent to CancellationToken.None (one of the few places that we use optional parameters).

the .NET team's guidance on default parameters on CancellationToken

TIL. Thanks.

We have used MediatR in a fair amount of projects now, and have built our own lightweight application framework on top of it.

  1. I agree with the sentiment that MediatR shouldn't need any external dependencies.
  2. I'm also on board with @khellang's proposed changes, with the exception of the removal of the two marker interfaces. While technically they aren't needed, I like the fact that they help make explicit which types in my application are requests. If I wanted to write a unit test to check if all of my requests are marked with an attribute indicating who is authorized to use it, the marker interfaces are suddenly very useful. I could dig up all the request types going through the handler types, but this feels convoluted to me. Both "commands" and "queries" requiring an interface is more consistent I think.
  3. I think most popular DI containers are able to handle decorators/pipelines, so I don't think it's a feature that MediatR needs. We use handler decorators, mostly for authorization, validation and logging. SimpleInjector has worked well for us so far for this. If it would help people and could be handled outside of the core package I'm not nescessarily against it.
  4. We haven't had a need to customize dispatching behaviors, but I can see how some people would. Introducing an INotificationPublisher is probably the best option here over subclassing and overriding. Other than this I'm having a hard time coming up with things I'd want to influence that I couldn't handle with decorators.

[...] with the exception of the removal of the two marker interfaces. While technically they aren't needed, I like the fact that they help make explicit which types in my application are requests. If I wanted to write a unit test to check if all of my requests are marked with an attribute indicating who is authorized to use it, the marker interfaces are suddenly very useful. I could dig up all the request types going through the handler types, but this feels convoluted to me. Both "commands" and "queries" requiring an interface is more consistent I think.

You're free to add marker interfaces or attributes to whatever you want. I just don't think MediatR should _require_ them. It's an artificial requirement that serves no purpose as far as MediatR is concerned.

Now, I don't have a problem with adding the interfaces back, but just don't constrain the generic arguments on them :smile:

The consistency argument is good, though. It's a bit weird to have to add IRequest<TResponse> to requests that returns a response, but don't have to add anything for requests that doesn't return anything.

Keeping the marker interfaces but not restraining the generic arguments is an option.

I'll admit that a part of my preference probably has to do with style. I just think that sometimes there is nothing wrong with being, or being required to be, explicit about things, even though "technically" there might not be a need to do so.

Either way, I otherwise like the proposed changes and this small nitpick wouldn't stop me from using MediatR.

So, this has had a while to sink in, and most people that wanted to add something have probably done so. Any ideas on how to proceed from here @jbogard?

@khellang I've been thinking about your proposed API and I've been playing around with some implementations. It's all very simple. And I especially like the concept of separate Handlers for requests with and without a response.

However, I'm struggling to wrap my mind around how pipelines (and/or decorators) might be designed and implemented. The clear benefit of a single interface, a single method, is that implementing a decorator is trivial. But with two interfaces, two methods, I would need separate decorators even if they do the exact same thing.

Maybe the void request handler can be wrapped in an adapter that implements the usual TResult request handler (and returns null or Unit or something)?

Any thought or other ideas?

with two interfaces, two methods, I would need separate decorators even if they do the exact same thing.

@jdaigle, In my experience, handlers that actually return anything (query handlers) need different cross-cutting concerns than handlers that don't return anything (command handlers). While we need concerns like validation, transaction handling, business rules handling, audit trail logging and deadlock retry on the command side, we mainly see caching on the query side. Aspects that you'll see on both sides however are things like authorization and other security related stuff.

I've experienced the amount of duplication in decorators to be very minimal, which made the separation between the query and the command handler abstraction the best solution for the systems I've been working on.

Issue #111 might be a hint for a new feature unless there's a way to achieve it using current legendary mediatr

I'd love to see a MediatR have a more standardised approach to building a pipeline.

At the moment we define them in our IoC container, which depending on the IoC container you're using will have different behaviour and kind of feels (to me anyway) like a bit of a leaky abstraction and/or an abuse of the IoC container.

@JosephWoodward I used to feel the same way. But I actually now really like the elegance of letting the IoC/DI container do all the heavy-lifting. Here's my specific use case:

I have more than one "module" for which I want to use MediatR, and each _has it's own_ pipeline. In my container setup, I configure for handlers in ModuleX use PipelineX and for handlers in ModuleY use PipelineY. Now when MediatR itself asks for a handler it's automatically given the correct pipeline.

I don't want to lose this functionality. But I don't think it makes sense for MediatR to have that type of "configuration" either.

I agree with @JosephWoodward and indeed would like to have a configuration class rather than put all wiring-up logic into the composition root. It would make it testable. Currently it hurts readability by junior developers and other less proficient team members such as leads, pms and managers who try understand the code.

As MediatR is gaining traction (which is great to see), I've certainly noticed an increase of IoC related queries regarding setting up pipelines in various IoC containers. For instance, looking at MediatR on Stack Overflow, the majority of the questions are people having difficulty setting up a pipeline, or dependencies around the pipeline; which makes me think an API for creating your pipeline would be a welcome addition.

Yep, an api to create a pipeline could be very useful (:

I only just discovered MediatR a few days ago and it's been the simplest thing in the world to use. I wouldn't change much...

However, I'm struggling to wrap my mind around how pipelines (and/or decorators) might be designed and implemented. The clear benefit of a single interface, a single method, is that implementing a decorator is trivial. But with two interfaces, two methods, I would need separate decorators even if they do the exact same thing.

@jdaigle I'm not sure I get what you're saying here. Two interfaces and two methods. That doesn't mean you should mix the two. Either you return a response or you don't?

With a standardized pipeline too I wouldn't need to define decorators around the many different request/handler interfaces.

Hey! I've been working on a project over the last months that sits somewhere in between MediatR and more feature rich systems like MassTransit etc. Its core is a simple pipeline system that's based on the Middleware/IApplicationBuilder code from ASP.NET Core.

We use it in our business code to "send away" arbitrary POCOs to a target. This target could be a MediatR like handler (in case of command messages/query objects) but also an external system like a database/EventStore/message queue (in case of event messages). We even use it for ASP.NET Core based web APIs.

For that reason we also use an envelope/context object for having MessageIds and HttpContext-like functionality (Cancellation tokens, User, Items bag, ...). However it intentionally does NOT include any "advanced service bus" features like scheduling etc.

I think it supports many of the things described here (multiple pipelines, (maybe) more natural pipeline configuration, ...) but it obviously also has added complexities due to the envelope/context and additional dependencies (we use the Microsoft.Extensions.* abstractions).

I really like the simplicity of MediatR - it being dependency-free has many great usages. So I'm also worried that adding additional features will make it too complex for some cases.

Instead, I'm wondering if there's interest in a separate middle ground library like the one described by me? It's still an internal project but I want to open source it in a few weeks and I can share more details if you're interested!

Yes, sure, more open source is better :)

Just throwing this out there; what would people's feels be on going async-only? You could still have sync handlers, but the top-level mediator APIs would always return a Task.

We've still got a ton of code that is sync. I think if the pipeline
behaviors are async-only that strikes a good balance between the two.

On Fri, Dec 9, 2016 at 6:30 AM, Kristian Hellang notifications@github.com
wrote:

Just throwing this out there; what would people's feels be on going
async-only? You could still have sync handlers, but the top-level mediator
APIs would always return a Task.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-266003071,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMt0XWmi2Z-KmbL786wSjRrdA2x_xks5rGUnsgaJpZM4J-DOf
.

How about auto generate restful like interfaces from all the requests? Not sure what the right terms for it, ServiceStack has been doing that from beginning. Can be a separate package that wires up all the requests and serves them up over http or something?

Haven't check back for a while, maybe it's already existed?

Not that I know of?

On Tue, Dec 20, 2016 at 2:13 PM, ThisNoName notifications@github.com
wrote:

How about auto generate restful like interfaces from all the requests? Not
sure what the right terms for it, ServiceStack has been doing that from
beginning. Can be a separate package that wires up all the requests and
serves them up over http or something?

Haven't check back for a while, maybe it's already existed?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-268346316,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMh7TI7Otfc4aIQrfahSYj_s3Dr36ks5rKDbmgaJpZM4J-DOf
.

@ThisNoName I built something similar a bit ago to do that. It doesn't use Mediatr (ended up avoiding any deps), but could work as a starting point. Maybe it'd be worth building out an extension lib just for Mediatr? https://github.com/decoy/PingPongr

Just my two cents in regards to the rest of this topic: I'd love to see 'no dependencies' as a requirement for any new features for this project. Using the lowest version of netstandard you can makes it much easier to include in other packages.

Wouldn't it be preferable to move the public API (IMediator, IRequest and IRequest) to a dedicated assembly? The point is, regardless of future development, these interfaces are unlikely to change, which means consumers of MediatR (ie: typically adapters in a hexagonal-type architecture) would require less "nuget maintenance" and could also be compatible with multiple MediatR versions.

I don't understand, I very often use hexagonal architecture but have never
run into a problem with Nuget maintenance? After all, with one deployed app
it's only one project's dependencies I need to deal with.

On Tue, Dec 27, 2016 at 11:26 AM, bvachon notifications@github.com wrote:

Wouldn't it be preferable to move the public API (IMediator, IRequest and
IRequest) to a dedicated assembly? The point is, regardless of future
development, these interfaces are unlikely to change, which means consumers
of MediatR (ie: typically adapters in a hexagonal-type architecture) would
require less "nuget maintenance" and could also be compatible with multiple
MediatR versions.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-269355995,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMrCkkFQsTu71_y4ciLYuIW_zlUR5ks5rMUpIgaJpZM4J-DOf
.

Let's say I have my Request classes (IRequest and IRequest implementations) in project A, the IMediator consumer in project B and the request handlers in project C.

In this case, only project C needs to depend on the actual implementation of MediatR. Project A and B would only depend on the interfaces package (let's call it MediatR.Common) which would require little to no updates. I think this may be useful if I my request classes are located in a different solution to make them available through my company's Nuget server for others to use.

I understand your point that in the end only a one version of the package will be deployed with the application, but it just seems more natural to me that I don't need to worry about upgrading all my projects when a new version of MediatR is released, only those projects that actually contain request handlers.

Why would you make your request classes available but not the handlers
themselves?

On Wed, Dec 28, 2016 at 1:18 PM, bvachon notifications@github.com wrote:

Let's say I have my Request classes (IRequest and IRequest
implementations) in project A, the IMediator consumer in project B and the
request handlers in project C.

In this case, only project C needs to depend on the actual implementation
of MediatR. Project A and B would only depend on the interfaces package
(let's call it MediatR.Common) which would require little to no updates. I
think this may be useful if I my request classes are located in a different
solution to make them available through my company's Nuget server for
others to use.

I understand your point that in the end only a one version of the package
will be deployed with the application, but it just seems more natural to me
that I don't need to worry about upgrading all my projects when a new
version of MediatR is released, only those projects that actually contain
request handlers.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-269525914,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMuXiphsfpulGq_oFA0yonVk8jrioks5rMrYHgaJpZM4J-DOf
.

I don't want the application consumer to have a direct dependency on the implementation, only on the service contract, in this case the requests. For example, the project I am currently working allows developers from other teams to integrate new components into the application, but those are developed in isolation and loaded dynamically at runtime which means they don't know how or where the request handlers are implemented.

BTW, this is mostly hypothetical as we are not using MediatR at the moment. We do however share application service interfaces through shared assemblies, so swapping those for request classes would be trivial.

What does it matter if the application consumer has a direct dependency on
the implementation? What's the reason for the indirection and dynamic
loading?

On Wed, Dec 28, 2016 at 2:54 PM, bvachon notifications@github.com wrote:

I don't want the application consumer to have a direct dependency on the
implementation, only on the service contract, in this case the requests.
For example, the project I am currently working allows developers from
other teams to integrate new components into the application, but those are
developed in isolation and loaded dynamically at runtime which means they
don't know how or where the request handlers are implemented.

BTW, this is mostly hypothetical as we are not using MediatR at the
moment. We do however share application service interfaces through shared
assemblies, so swapping those for request classes would be trivial.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-269539363,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMlMxkaZgPOQv1iEp5KzI9ZybsVsUks5rMsx6gaJpZM4J-DOf
.

I think it is common practice to share a contract between components, not the implementation. In our case specifically, we don't have any control over the development of consuming components, those dlls are dropped in with the application and need to be loaded at runtime.

I follow SemVer for MediatR, so neither the message interfaces nor
IMediator interfaces change within a single major version. Across major
versions, there are breaking changes. So I'm not even sure splitting things
out would help anything. These aren't for example Actual Messages like WCF
or Azure Service Bus or something.

If anything, you'd want the messages and handler definitions in the same
assembly so that they can all be versioned together.

On Wed, Dec 28, 2016 at 3:36 PM, bvachon notifications@github.com wrote:

I think it is common practice to share a contract between components, not
the implementation. In our case specifically, we don't have any control
over the development of consuming components, those dlls are dropped in
with the application and need to be loaded at runtime.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-269544644,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMp7EWXLstCsGi-sZjokZ1z1heHzuks5rMtZBgaJpZM4J-DOf
.

Fair enough, I can live with that, thank you!

MediatR 3 is out now, with pipeline support. Thanks for the input all!

Is MediatR 3.0 compatible with MediatR 2.0 code? I already implemented a pipeline like in your example blog. Will I have to change everything?

No, the major version change indicates breaking changes.

You can keep your pipeline as is through decorators, or migrate to the
built in one, it's up to you.

On Fri, Jan 6, 2017 at 11:14 PM Ciel notifications@github.com wrote:

Is MediatR 3.0 compatible with MediatR 2.0 code? I already implemented a
pipeline like in your example blog. Will I have to change everything?

—
You are receiving this because you modified the open/close state.

Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271063846,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMkFkz90ezFDxGO13j_6KXaLTQGe0ks5rPx8ggaJpZM4J-DOf
.

Where I can found a example of the new integrated pipeline?

El 7 ene. 2017 10:42, "Jimmy Bogard" notifications@github.com escribió:

No, the major version change indicates breaking changes.

You can keep your pipeline as is through decorators, or migrate to the
built in one, it's up to you.

On Fri, Jan 6, 2017 at 11:14 PM Ciel notifications@github.com wrote:

Is MediatR 3.0 compatible with MediatR 2.0 code? I already implemented a
pipeline like in your example blog. Will I have to change everything?

—
You are receiving this because you modified the open/close state.

Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271063846,
or mute the thread
auth/AAGYMkFkz90ezFDxGO13j_6KXaLTQGe0ks5rPx8ggaJpZM4J-DOf>
.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271084582,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AJRyaZAO0qjLg3hn5TfPZa0Q1XhHjIS7ks5rP5ZKgaJpZM4J-DOf
.

I've been looking around and perhaps I'm just not reading clearly, but other than the advent of integrated pipelines, is there any sort of list of what else has changed?

I agree, we need a changelog or something similar

El 8 ene. 2017 12:42, "Ciel" notifications@github.com escribió:

I've been looking around and perhaps I'm just not reading clearly, but
other than the advent of integrated pipelines, is there any sort of list of
what else has changed?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271158641,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AJRyaWkt5bBEYYJQJWZ2RFWXawB6qAepks5rQQPfgaJpZM4J-DOf
.

Yes, I use GitHub releases for this purpose.

On Sun, Jan 8, 2017 at 9:44 AM, Emanuel notifications@github.com wrote:

I agree, we need a changelog or something similar

El 8 ene. 2017 12:42, "Ciel" notifications@github.com escribió:

I've been looking around and perhaps I'm just not reading clearly, but
other than the advent of integrated pipelines, is there any sort of list
of
what else has changed?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271158641,
or mute the thread
AJRyaWkt5bBEYYJQJWZ2RFWXawB6qAepks5rQQPfgaJpZM4J-DOf>
.

>

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271158753,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMnQwn2pPaRhBh5Irb55jEapmugTWks5rQQRCgaJpZM4J-DOf
.

The option to have IAsyncRequestHandler<TRequest> have been removed if I've read it correctly. My handlers that previously inherited AsyncRequestHandler<TRequest> must be moved to IAsyncRequestHandler<TRequest, TResponse> but then I must introduce a dummy response.

Is there any way to create a async request handler that doesn't return anything?

Edit: Is there any documentation on an upgrade path?

For example 2.1.0

````c#
public class Command : IAsyncRequest
{
public string Name { get; set; }

        public CustomerType Type { get; set; }

        public string CustomerNumber { get; set; }

        public AddressModel Address { get; set; } = new AddressModel();

        public IList<CustomerContactModel> Contacts { get; set; } = new List<CustomerContactModel>
        {
            new CustomerContactModel()
        };
    }

    public class Handler : AsyncRequestHandler<Command>
    {
        private readonly DbContext _context;
        private readonly IMapper _mapper;

        public Handler(DbContext context, IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        protected override Task HandleCore(Command message)
        {
            // Filter out empty contacts for now
            message.Contacts =
                message.Contacts.Where(
                        c => !string.IsNullOrWhiteSpace(c.Email) || !string.IsNullOrWhiteSpace(c.Name))
                    .ToList();
            var entity = _mapper.Map<Domain.Customer>(message);
            _context.Customers.Add(entity);
            return Task.CompletedTask;
        }
    }

````

Thanks!

There's another IAsyncRequestHandler that doesn't return anything.

On Tue, Jan 10, 2017 at 7:14 AM, Joakim Carselind notifications@github.com
wrote:

The option to have IAsyncRequestHandler have been removed if
I've read it correctly. My handlers that previously inherited
AsyncRequestHandler must be moved to IAsyncRequestHandler TResponse> but then I must introduce a dummy response.

Is there any way to create a async request handler that doesn't return
anything?

Thanks!

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/102#issuecomment-271572392,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMqG8eGDYojJoS9ye32VXPJbgwwAqks5rQ4Q6gaJpZM4J-DOf
.

Thanks @jbogard

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bugproof picture bugproof  Â·  6Comments

ArturKarbone picture ArturKarbone  Â·  3Comments

nevaenuf picture nevaenuf  Â·  4Comments

sq735 picture sq735  Â·  3Comments

zachpainter77 picture zachpainter77  Â·  5Comments