Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MassTransit message consumers never see subclass type

This question deals with events (base class events and subclass events) and event handlers. I'm working on existing code, that doesn't seem to work the way the author expected it. I have difficulty understanding why it doesn't work though, so I want to understand what's going on before I try to fix the existing code.

I've found the following question, which may or may not suggest I need to make an additional event handler for the subtype events:

C#: Raising an inherited event

If making an additional event handler is indeed the solution, I would still like to learn why this is the case. This is my first question here, and I did really try to search for the answer/explanation to my question, but sincere apologies if it's still something I should've easily found. A stern "RTFM!" with a educational link would be fine with me at this point :)

We have 2 event classes, a base type and a subtype. The subtype event exists to deal with deletion events.

public class BaseTypeEvent
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    public BaseTypeEvent()
    { }

    public BaseTypeEvent(SomeRandomThing item)
    {
        Id = item.Id;
        Name = item.Name;
    }
}

public class SubTypeEvent : BaseTypeEvent
{
    public DateTimeOffset Deleted { get; set; }

    public SubTypeEvent()
    {
        Deleted = DateTimeOffset.UtcNow;
    }
}

A usage of these events that seems to be failing:

public class UsageClass
{   
    public UsageClass(IEventBusService eventBusService)
    {
        eventBusService.MyBaseTypeEvents += HandleMethod;
    }

    private void HandleMethod(BaseTypeEvent e)
    {
        if(e is SubTypeEvent)
        {
            //code that deals with deletion events
            //execution never actually gets here
        }

        //code that deals with events that are not deletion events
     }
}

The declaration of the events are in the IEventBusService and EventBusService:

public delegate void MyEventHandler(BaseTypeEvent e);

public interface IEventBusService
{
    public event MyEventHandler MyBaseTypeEvents;

    void PublishStuff(BaseTypeEvent e);
}

public class EventBusService : IEventBusService, IDisposable 
{
    public void Initialize()
    {
        //Bus is MassTransit
        Bus.Initialize(sbc =>
            {
                sbc.Subscribe(subs => subs.Handler<BaseTypeEvent>(OnBaseTypeEvent));
            }
    }

    private void OnBaseTypeEvent(BaseTypeEvent e)
    {
        if (MyBaseTypeEvents == null) return;
        try
        {
            MyBaseTypeEvents(e);
        }
        catch (Exception e)
        {
            //some logging
        }
    }

    public event MyEventHandler MyBaseTypeEvents;

    public void PublishStuff(BaseTypeEvent e)
    {
        //some logging

        //publish e to the event bus of our choice (MassTransit)
        Bus.Instance.Publish(e);
    }
}

And then finally the place where we send the deletion event (to try to delete an item of what I have cleverly named SomeRandomThing above):

eventBusService.PublishStuff(new SubTypeEvent
    {
        Id = id,
        Deleted = DateTimeOffset.UtcNow
    });

So the problem: after sending the deletion event with the last line of code above, the if-statement in the UsageClass that checks whether an incoming event is of type SubTypeEvent is never actually true. The type of e in HandleMethod of UsageClass is BaseTypeEvent.

Edit:

I've decided to get rid of the subtyping in this case. We now no longer have BaseTypeEvent and SubTypeEvent, but simply EventTypeA and EventTypeB. One deals with creates and updates, the other deals with deletes (for which we need significantly less information that the creates and updates anyway).

public delegate void MyEventAHandler(EventTypeA e);
public delegate void MyEventBHandler(EventTypeB e);

and

void PublishStuffForA(EventTypeA e);
void PublishStuffForB(EventTypeB e);

and so on.

I've made an extra subscription to MassTransit in the Initialize method of our EventbusService, and made extra handlers in the various UsageClasses that needed them:

sbc.Subscribe(subs => subs.Handler<EventTypeA>(OnEventTypeA));
sbc.Subscribe(subs => subs.Handler<EventTypeB>(OnEventTypeB));

and

public UsageClass(IEventBusService eventBusService)
{
    eventBusService.MyEventTypeAEvents += HandleMethodForA;
    eventBusService.MyEventTypeBEvents += HandleMethodForB;
}

and so on.

I now no longer have to check if an incoming event is of a certain type, I just handle to two types separately. Perhaps a cop out, but it works.

I'm hesitant to qualify this as the answer to my own question, as @Glubus' comments as well as @Travis' comments were what answered my question. Still thought this small edit write-up might be nice to let everyone know what I did as a solution :)

Edit 2:

Sources of information that were helpful:

Derived types are not published to consumers in MassTransit

MassTransit message mis-typing

MassTransit: Message contracts, polymorphism and dynamic proxy objects

https://groups.google.com/forum/#!searchin/masstransit-discuss/polymorphism/masstransit-discuss/q_M4erHQ7OI/FxaotfIzI7YJ

like image 950
user6473667 Avatar asked Jun 16 '16 09:06

user6473667


1 Answers

So I can tell you the short answer:

Using polymorphism in messaging contracts introduces coupling.

We believe, as MassTransit developers, that this is a bad idea. It's still possible, but not out of the box. You have to use binary serialization or a customer serializer. The default the serialization pipeline only populates a proxy of the type in the consumer.

like image 115
Travis Avatar answered Nov 12 '22 20:11

Travis