Monday, November 04, 2013

EasyNetQ: Polymorphic Publish and Subscribe

logo_design_150

From version 18.0 of EasyNetQ, you can now subscribe to an interface, then publish implementations of that interface.

Let's look at an example. I have an interface IAnimal and two implementations Cat and Dog:

public interface IAnimal
{
    string Name { get; set; }
}

public class Cat : IAnimal
{
    public string Name { get; set; }
    public string Meow { get; set; }
}

public class Dog : IAnimal
{
    public string Name { get; set; }
    public string Bark { get; set; }
}

I can subscribe to IAnimal and receive both Cat and Dog classes, or any other class that implements IAnimal:

bus.Subscribe<IAnimal>("polymorphic_test", @interface =>
    {
        var cat = @interface as Cat;
        var dog = @interface as Dog;

        if (cat != null)
        {
            Console.Out.WriteLine("Name = {0}", cat.Name);
            Console.Out.WriteLine("Meow = {0}", cat.Meow);
        }
        else if (dog != null)
        {
            Console.Out.WriteLine("Name = {0}", dog.Name);
            Console.Out.WriteLine("Bark = {0}", dog.Bark);
        }
        else
        {
            Console.Out.WriteLine("message was not a dog or a cat");
        }
    });

Let's publish a cat and a dog:

var cat = new Cat
{
    Name = "Gobbolino",
    Meow = "Purr"
};

var dog = new Dog
{
    Name = "Rover",
    Bark = "Woof"
};

bus.Publish<IAnimal>(cat);
bus.Publish<IAnimal>(dog);

Note that I have to explicitly specify that I am publishing IAnimal. EasyNetQ uses the generic type specified in the Publish and Subscribe methods to route the publications to the subscriptions.

Happy polymorphismising!

9 comments:

Jochen Zeischka said...

Hey Mike!

Why do you make your users specify the type they want to use for publishing?

If you model your type hierarchy in exchanges, there is no need to do so at all. In your example: the Cat exchange would route all messages to the IAnimal exchange and so would the Dog exchange.

So, if I publish a Cat and subscribe to IAnimal, that would perfectly work, without any need to specify that I want to publish an IAnimal.

But if I subscribe to Cat, it would work as well...

Best regards!

Mike Hadlow said...

Hi Jochen,

That's a very interesting idea. I've heard it before, I think the MassTransit guys were thinking of wiring things up that way? But to be honest I haven't been keeping track of what they've been doing with RabbitMQ.

My main reason for not doing things that way is to keep things simple on the AMQP side, although I appreciate that's not a particularly strong argument :)

Thanks for the reminder though, I'll keep it as a possibility.

Anonymous said...

Strongly binding the type system into the message routing system like MassTransit does is interesting, but it's a whole extra level of opinionation compared to what EasyNetQ currently does.

My team abandoned MassTransit because its type system based routing puts a lot of constraints on what you can do.

I definitely prefer the EasyNetQ approach here.

Anonymous said...

Hey Mike, I would definitely agree with Jochen. The reason is, let's say I have a very generic handler that handles IEventMessage. Let's say it takes each message and updates a centralized event store. but I have other handlers for specific events. So my DogHandler "throws a stick", and my CatHandler "cleans up a hairball". But I still want that general handler that does something really basic with all events or with subsets of events. Is there a way I could accomplish that?

Mike Hadlow said...

Hi Eric,

No, there's no way of doing what you ask with the default API. Of course you could wire everything up yourself using the Advanced API, but that's probably not what you want.

Anonymous said...

Mike, if I took a swipe at adding polymorphic subscriptions ala NServiceBus/MassTransit, would you consider pulling it?

Mike Hadlow said...

Hi Eric,

As anonymous said above, there is value in a simple, if constrained approach. The whole ethos of EasyNetQ is to make working with RabbitMQ as simple as possible. I worry that if EasyNetQ starts creating a graph of exchanges every time one calls publish, it would be a step away from 'as simple as possible'. Having said that, I'd certainly love to see how you would implement it (although I guess I'd only have to go and look at the MassTransit code to get some ideas ;).

So, yes, I'd love to see a pull request, but, as always, I reserve the right not to accept it.

Anonymous said...

I totally understand Mike. We use MassTransit and IMO it's significantly complicated by abstracting transport to allow MSMQ/Zero/Rabbit. But we need the polymorphic messaging. I was trying to come up with a way to transition to EasyNetQ, but our project is highly dependent upon said functionality. After some further thought, I decided to take the opposite approach: Start with MassTransit and rip out everything non-rabbit and try to simplify and correct some other areas as well.

Mike Hadlow said...

That's the great thing about open source software, being able to rip out the bits you need from any codebase. Feel free to help yourself to any EasyNetQ code that might help.