0

Background -

I am working on a service that internally raises domain events (those are raised through IDomainEventsAggregator.AddEvent. When an operation finishes (i.e. Transaction or an HTTP Request), these events are published to respective handlers through MediatR. Those MediatR are Notification handlers to handle the domain events.

Here is sample code

I have the following code (simplified) which represents domain events -

public interface IDomainEvent{}
public class ADomainEvent:IDomainEvent {}
public class BDomainEvent:IDomainEvent {}

And the following class for a collection of domain events (an event bag):

public class DomainEventsBag<TDomainEvent> where TDomainEvent: IDomainEvent
{
    public DomainEventsBag(IEnumerable<TDomainEvent> domainEvents)
    {
        DomainEvents = domainEvents;
    }

    public IEnumerable<TDomainEvent> DomainEvents { get; }
}

And Respective handlers are ...

public class ADomainEventNotificationHandler1:INotification<DomainEventsBag<ADomainEvent>>{ ... code to handle domain event of type ADomainEvent ...}
public class ADomainEventNotificationHandler2:INotification<DomainEventsBag<ADomainEvent>>{ ... code to handle domain event of type ADomainEvent ...}
public class BDomainEventNotificationHandler:INotification<DomainEventsBag<BDomainEvent>>{ ... code to handle domain event of type BDomainEvent ...}

A service that aggregates all domain events

public interface IDomainEventsAggregator
{
    void AddEvent<TDomainEvent>(TDomainEvent event) where TDomainEvent:IDomainEvent;
    void Commit();
}

public class DomainEventsAggregator:IDomainEventsAggregator
{
    private IMediator mediator;
    private Dictionary<Type, List<IDomainEvent>> domainEvents;
    public DomainEventAggregator(IMediator mediator)
    {
        this.mediator = mediator;
        domainEvents = new Dictionary<Type, List<IDomainEvent>>();
    }
    public void AddEvent<TDomainEvent>(TDomainEvent event) where TDomainEvent:IDomainEvent
    {
        if (!domainEvents.ContainsKey(typeof(TDomainEvent)))
            domainEvents.Add(typeof(TDomainEvent), new List<IDomainEvent>());

        domainEvents[typeof(TDomainEvent)].Add(domainEvent);
    }

    public void Commit()
    {
        foreach (var @event in domainEvents)
        {
            // **************************
            // This is where I need help!
            // To generate object of type DomainEventsBag<@event.Key> since @event.Key is type of event so that following line with work
            domainEventsBag = ??? // the code which I am not able to write or solution I am looking for
            // **************************
            mediator.Publish(domainEventsBag);
            // Actual problem - mediator handler will execute only if domainEventsBag has concrete type (i.e. ADomainEvent or BDomainEvent) instead of IDomainEvent = DomainEventsBag<ADomainEvent> and DomainEventsBag<BDomainEvent>. 
        }
    }
}

I tried Activator.CreateInstance but it does not since it throws an error for missing parameter on CTOR of type. Here is a sample of what I tried to create a dynamic object -

var type = typeof(DomainEventsBag<>).MakeGenericType(@event.Key);
var eventBag = Activator.CreateInstance(type, @event.Value);

Two problems with the above code -

  1. It throws an error that the CTOR with argument type @event.Value (For example ADomainEvent) is missing
  2. The return type of Activator.CreateInstance is an object instead of DomainEventsBag
Hiren Desai
  • 941
  • 1
  • 9
  • 33
  • Not sure why the #java tag was removed from the question. While the question is related to C#, the answer largely depends on the approach than on the framework itself. I also feel a possible answer might come up as a refactoring of code.. who knows! – Hiren Desai May 12 '23 at 13:13
  • 2
    This is an [X/Y Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Mike Nakis May 12 '23 at 13:13
  • So, you want to accomplish some end-goal which you have kept a secret. (You are giving us absolutely no hint as to what this `Commit()` function is intended to do.) Then you invented some theory as to what you might do to accomplish your goal, you tried to implement your theory, your implementation did not work, and you came to Stack Overflow to ask how to properly implement that theory. Instead of all that, why not just explain what that original end-goal of yours was? What is `Commit()` supposed to accomplish? – Mike Nakis May 12 '23 at 13:16
  • I don't see that working out. Any code you write will a best know that its a IDomainEvent. You can't programm/code against a type that isn't known at compile time but rather only at runtime. The only thing you know is that its an IDomainEvent. So i would let DomainEventsBag not be generic and it should only mange IDomainEvent(s). – Ralf May 12 '23 at 13:18
  • *"Not sure why the #java tag was removed from the question..."* -- you've posted no Java-relevant code. Please strive to keep the question as specific as possible. – Hovercraft Full Of Eels May 12 '23 at 13:21
  • @MikeNakis - Updated question with more information if that helps – Hiren Desai May 12 '23 at 13:27
  • Your update still does not show exactly what type `mediator.Publish( ? );` expects and therefore of exactly what type `domainEventsBag` must be. – Mike Nakis May 12 '23 at 13:48
  • @MikeNakis - Added handler and mediator information – Hiren Desai May 16 '23 at 05:52

1 Answers1

0

Not discussing WHY do you want this, from pure technical standpoint of view the problem is that List<IDomainEvent> is not a IEnumerable<ConcreteDomainEvent> (i.e. something like IEnumerable<ADomainEvent>) required by DomainEventsBag ctor, so you need to work around that fact. For example by making a generic method which will do what is needed:

public void Commit()
{
    foreach (var @event in domainEvents)
    {
        var method = typeof(DomainEventsAggregator).GetMethod(nameof(CreateBag), BindingFlags.Public|BindingFlags.Static);
        var eventBag = method.MakeGenericMethod(@event.Key).Invoke(null, new object?[]{ @event.Value});
       // to handle the mediator.Publish you can do the same as with CreateBag
       // or just call mediator.Publish there and refactor CreateBag accordingly
    }
}

public static DomainEventsBag<T> CreateBag<T>(List<IDomainEvent> events) where T : IDomainEvent =>
    new DomainEventsBag<T>(events.Cast<T>());

Note that this is a very simple implementation just to get you started which has some performance issues (hence repeated use of the reflection) which can be mitigated in different ways (for example by caching reflection in some way, like is done here).

Another approach is to make DomainEventsBag ctor accept IEnumerable<IDomainEvent> and handle it correctly there (this should make your solution to work), though this will not fix the problem with mediator.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I would be open to solution where my implementation is wrong ... i.e. Refactoring how I implemented DomainEventsAggregator or DomainEventBag ... :) – Hiren Desai May 12 '23 at 13:32
  • @HirenDesai my answer explains where and why your current implementation is wrong and suggests two options how to fix it. Not sire what solution you need other than that. – Guru Stron May 12 '23 at 13:35
  • What I meant is - 1. I Tried your solution that's reflection based and it works as expected .. 2. The refactor you mentioned on DomainEventsBag won't work since my notification handlers are of type DomainEventsBag and converting DomainEventsBag to non generic won't allow multiple notification handlers. I am more inclined to solution #2 even if that requires how I am storing events in Aggregator – Hiren Desai May 16 '23 at 06:12
  • 1
    @HirenDesai 1. If answer works for you - feel free to accept and upvote it) 2. I was not talking about making `DomainEventsBag` non-generic, I was talking about changing ctor only and doing filtering inside it (though again it will not fix mediator issue, so I would go with the first one. – Guru Stron May 16 '23 at 07:46