Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move interfaces to its own assembly #61

Closed
TsengSR opened this issue Dec 24, 2015 · 21 comments · Fixed by #686
Closed

Move interfaces to its own assembly #61

TsengSR opened this issue Dec 24, 2015 · 21 comments · Fixed by #686

Comments

@TsengSR
Copy link

TsengSR commented Dec 24, 2015

It would be nice to have all the interfaces and base classes (such as Unit) moved to it's own assembly, called MediatR.Abstractions or something like that, so we could use it in class libraries without having a dependency on the concrete implementation.

For example I have a shared projet, called Domain.Shared where I share commonly used interfaces and classes (such as IEntity<T> etc.) that I'd like to share accross multiple microservices, each implementing it's own Requests and Notifications.

This is a small problem, as if I want to use a newer versions of MediatR in one of the microservices, I'd have to update all the services, when I update Domain.Shared project.

Since the interfaces rarely change, it would make sense to extract them, so our class libraries only depend on the abstractions and we only have to reference the MediatR package in the application projects (ASP.NET 5, MVC, Console Application) and not in our class libraries. Then we could use individual verisons in our microservices, like having MediatR 2.0 on OrderService and having maybe 2.0.2 on ShippingService for example

@ThisNoName
Copy link

It sounds like an issue that your Domain.Shared interface contains concrete implementation with hard dependency on third party library?

@jbogard
Copy link
Owner

jbogard commented Dec 28, 2015

I've never tried to share MediatR requests across service boundaries. I really only intended to share them inside a service boundary. Across service boundaries I use web services or messaging, both of which are going to use something like JSON as their message format.

@TsengSR
Copy link
Author

TsengSR commented Dec 28, 2015

Since I was going to use MediatR for the CQRS pattern, the idea was to also use it for domain events (which base implementation and interfaces would be in the "Domain.Core" which is shared with all other projects).

The main idea was to put each bounded context into it's own class library and usually use one bounded context per microservice, so there won't be any interservice communication with MediatR and it will just be used for communication with the service.

But because the bounded contexts are just class libraries (and no applications like dnx, asp.net, console app etc.), there is no need for a concrete implementation of MediatR, just for the interfaces to implement the handlers and requests within the (sub-)domain/bounded context.

The actual implementation should referenced from the application itself. Since the interfaces themselves shouldn't change very often, it would be good idea to have them in separate assembly. So an update to the implementation won't require an update of the implementation will only require to update the application project, and I could have a webservice running maybe MediatR 2.0.2 and other, newer one with 2.1, as long as the core assembly with the interfaces didn't change and of course we wouldn't have the implementation of it leak into our domain.

I could also have basically implement the handler, request and notification interfaces myself, but then it's just a pain to translate it into the MediatR types, since I decided to use it for a CQRS pattern and since I use it for this I can also lift it's notification system for domain events.

I just don't like the idea of depending on the concrete implementation (and it's update cycles) in my domain, when all what I really need at this point is the contracts (the interfaces). When it's split, I could completely replace the MediatR implementation within the appliaction.

@jbogard
Copy link
Owner

jbogard commented Dec 28, 2015

A couple of things - replacing MediatR with your own implementation would likely take under a day in a single codebase. There's not much there to replace, so it's not something to optimize for. I've replaced far more extensive things - entire ORMs from EF to NH and vice versa, in a couple of days, without any repositories etc. to get in the way.

Second, bounded contexts are very large boundaries. If you're not separating out into different repositories, you've already got coupling.

Something more interesting people do is share contracts via NuGet packages - this is very often done with NServiceBus for example. With MediatR, you kinda assume someone is going to consume events in-process. That's coupling.

You mentioned you're using domain events across bounded contexts - don't. Domain events are events internal to a bounded context, events across bounded contexts often quite a bit more information. Codes instead of IDs, URLs instead of GUIDs etc. I would create an explicit package just for cross-context communication, with no dependencies, and if you want to publish them from domain events, have a handler turn around and re-publish the internal event into an external one.

A similar bad idea would be exposing a domain service directly as a WCF service. Don't do it. Create an anti-corruption layer. And don't fall into the trap of using mapping tools like AutoMapper (unless it's really really simple). You don't want to couple your internal and external messages.

@TsengSR
Copy link
Author

TsengSR commented Dec 28, 2015

I don't want to share the implementations of request/notification/handler, just the interfaces. An implementation of INotificationHandler<OrderSent> from "order bounded context" (let's call it: OrdersBoundedContext.Core) will never be consumed or handled in a "shipping bounded context" (let's call it ShippingBoundedContext.Core) for example, but the IDomainEventinterface will be shared.

OrdersBoundedContext.Core has no reference to ShippingBoundedContext.Core and can't use it's types, but both reference Domain.Shared (which have some shared interfaces and the DomainEvents static class, bad enough it took me like 4 days to look for alternatives for this static class and how to get it work with scoped live time in ASP.NET as neither singletons nor transient do work very well with transactions)

But since I wanted to use the MediatR INotification as base for the events, I did

public interface IDomainEvent : INotification
{
    DateTime CreatedOn { get; }
}

But I guess I'll try to implement it myself, I just really dislike the idea to have to write "my own" ddd framework before I can finally start to make the actual domain, expecially when it's a "pet project" I intend to do in my spare time, especially when still trying to understand EventSourcing.

@jbogard
Copy link
Owner

jbogard commented Dec 29, 2015

Don't share the domain events across bounded contexts. You really don't want to introduce this level of coupling. And if one bounded context is dispatching - synchronously - and sharing a transaction with another bounded context, you don't have bounded contexts, you just have a more complex project structure.

I can talk specifically about what we do. We have domain events in a project that uses MediatR. We have an INotificationHandler that does nothing but turn around and publish an NServiceBus event onto a queue, supplementing the domain event that has things like a CustomerId with additional data. The domain event is not coupled to the external event, other internal consumers can consume it easily and the producer of the domain event is not coupled to what external consumers need.

You mentioned you're new to DDD, so I'm trying to make sure you don't make the same mistakes I did when I got started. Bounded contexts are very well-defined boundaries, logical and likely physical. In larger orgs these are different teams different source control repos.

If these projects are in the same solution/repository, it's 90% certain you don't actually have different bounded contexts.

@jbogard jbogard closed this as completed Jan 5, 2016
@jmzagorski
Copy link

I have tackled this same issue in order to keep all the dispatching within MediatR. Since MediatR only ever resides in my Application and UI Layer, I create a generic class that implements INotification in my Application Layer that has one property, the DomainEvent.

  public class EventWrapper<TWrapped> : INotification where TWrapped : IDomainEvent
  {
    public TWrapped Wrapped { get; set; }
  }

I then have one event Publisher (the interface is in the Domain Layer and implementation is in the Application Layer) that wraps up the domain event using reflection. I am not sure if this is any better, but hopefully another example will help someone.

 public class MediatREventPublisher : IEventPublisher
  {
    private readonly IMediator mediator;

    public MediatREventPublisher(IMediator mediator)
    {
      if (mediator == null) throw new ArgumentNullException(nameof(mediator));

      this.mediator = mediator;
    }

    public void Publish<TEvent>(TEvent e) where TEvent : IDomainEvent
    {
      Type wrapperType = typeof(EventWrapper<>).MakeGenericType(typeof(TEvent));
      EventWrapper<TEvent> wrapper = (EventWrapper<TEvent>)Activator.CreateInstance(wrapperType);
      wrapper.Wrapped = e;
      mediator.Publish(wrapper);
    }

@jbogard
Copy link
Owner

jbogard commented Oct 14, 2016

What problem is this solving?

@jmzagorski
Copy link

jmzagorski commented Oct 14, 2016

just a way to keep the IDomainEvent in the Domain Layer while still letting MediatR publish the event so I don't have to take a dependency on INotification the in Domain Layer.

@bugproof
Copy link

bugproof commented Sep 6, 2018

I think this would be useful. In my case I have shared/common assembly(shared via nuget) and it would be nice to share IRequest classes (commands, or call them DTOs) with API client library. I don't feel like managing duplicated models or using swagger/oapi tools to generate them each time especially when I use C# for both projects. That would be something ala ServiceStack...

@jbogard
Copy link
Owner

jbogard commented Sep 6, 2018

I already do this today, literally on my current project. The common requests are in a Core project shared by the API and others. There's nothing preventing you from your core library referencing MediatR - which targets netstandard20 and sharing those with other projects.

In short, you can do this today. Just do it. It's fine.

@bugproof
Copy link

bugproof commented Sep 6, 2018

@jbogard yes but my API client library doesn't need features of MediatR, just the contracts/DTOs/requests/commands, however, you call them.

@jbogard
Copy link
Owner

jbogard commented Sep 6, 2018

So what's the harm exactly? Are you not sharing the API contracts because someone might see IMediator?

@bugproof
Copy link

bugproof commented Sep 6, 2018

@jbogard it's not a big harm but IMediator is not needed in the API client

@jbogard
Copy link
Owner

jbogard commented Sep 6, 2018

Early on I considered moving the contracts out, but a few reasons why I didn't:

  • It's really annoying to version the interface separately
  • It's really annoying to deploy through NuGet
  • Two packages could mean versioning hell
  • Minimal value added

@bugproof
Copy link

bugproof commented Sep 6, 2018

@jbogard I see. Thanks for the explanation. And yea it's really annoying to deploy through NuGet (why it has to be this way??)

@iuribrindeiro
Copy link

iuribrindeiro commented Sep 10, 2020

Now that we have Blazor, should we consider this again? I mean, if I could have a Shared project only with Requests and Response types, would be great to use it on the Presentation and Blazor layer.

@no1melman
Copy link

Early on I considered moving the contracts out, but a few reasons why I didn't:

  • It's really annoying to version the interface separately
  • It's really annoying to deploy through NuGet
  • Two packages could mean versioning hell
  • Minimal value added

Unless you do this in a mono-repo. But, so much yes to the two middle points

@buvinghausen
Copy link

buvinghausen commented Oct 6, 2020

@iuribrindeiro I don't mean to speak for Mr. Bogard here but I don't think MediatR should ever seep up into the browser. I get where you're coming from because it's nice to write a single request schema and share it between the client and the server. Given that the IRequest interfaces are all just marker interfaces you are far better off defining the request object in your Blazor client assembly and since your server has a reference to it simply inheriting from the client request class on the server and having the sever version implement the marker interface. Your controller then just takes the server version which is structurally the same and passes it off to MediatR and you get the benefits you're looking for while keeping the browser footprint as small as possible.

@jbogard
Copy link
Owner

jbogard commented Oct 7, 2020

I can see some benefits, but there's also the folks that want zero references to anything in their domain model, and I don't really want to encourage that either.

@jbogard jbogard changed the title Move interfaces to it's own assembly Move interfaces to its own assembly Oct 7, 2020
@iuribrindeiro
Copy link

@buvinghausen That was exactly my point. Anyway thanks for answers :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants