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

Support for publish/subscribe interface implementations #67

Closed
abrasat opened this issue Mar 1, 2016 · 5 comments
Closed

Support for publish/subscribe interface implementations #67

abrasat opened this issue Mar 1, 2016 · 5 comments

Comments

@abrasat
Copy link

abrasat commented Mar 1, 2016

I tried to test a publish/subscribe scenario with interface implementations and could not get it working.
I defined some message classes:

public interface IBaseMessage
{
    int MsgId { get; set; }
}
public class OtherMessage : IBaseMessage
{
    public int MsgId { get; set; }
    public int DataI { get; set; }
}
public class OtherMessage2: IBaseMessage
{
    public int MsgId { get; set; }
    public float DataF { get; set; }
}

and then published the message:

using (var bus = Depot.Connect("localhost"))
{
    while (running)
    {
        if (i % 2 == 0)
        {
            var pubMsg = new OtherMessage()
            {
                MsgId = i,
                DataI = i*2
            };
            bus.Publish<IBaseMessage>(pubMsg);
        }
        else if (i % 2 == 1)
        {
            var pubMsg = new OtherMessage2()
            {
                MsgId = i,
                DataF = i*3.0f
            };
            bus.Publish<IBaseMessage>(pubMsg);
        }
        i++;
        Thread.Sleep(1000);
    }
}

The subscriber looks like this, but no messages are received.

using (var bus = Depot.Connect("localhost"))
{
    bus.Subscribe<IBaseMessage>(SubscriberCallback);
 }
...
private static void SubscriberCallback3(IBaseMessage msg)
{
    string rcvMsg = String.Empty;
    if (msg is OtherMessage) {
        rcvMsg = String.Format("id {0}, dataI = {1}", msg.MsgId, (msg as OtherMessage).DataI);
    }
    else if (msg is OtherMessage2)
    {
        rcvMsg = String.Format("id {0}, dataF = {1}", msg.MsgId, (msg as OtherMessage2).DataF);
    }
    System.Console.WriteLine(rcvMsg);
}

If I explicitly publish/subscribe the derived classes instead of the interface, the messages are received correctly.
Is there any possiblity to use an interface or base abstract class for messages?

@jonnii
Copy link
Owner

jonnii commented Mar 1, 2016

publishing a base class doesn't really make sense. you should publish concrete implementations and subscribe to interfaces.

@jonnii jonnii closed this as completed Mar 1, 2016
@abrasat
Copy link
Author

abrasat commented Mar 1, 2016

You are right, but it was just an example. Subscribing to interfaces does not work either, even with published concrete implementations derived from the interface.
The changed publisher code:

using (var bus = Depot.Connect("localhost"))
{
    while (running)
    {
        if (i % 2 == 0)
        {
            var pubMsg = new OtherMessage()
            {
                MsgId = i,
                DataI = i*2
            };
            bus.Publish(pubMsg);
        }
        else if (i % 2 == 1)
        {
            var pubMsg = new OtherMessage2()
            {
                MsgId = i,
                DataF = i*3.0f
            };
            bus.Publish(pubMsg);
        }
        i++;
        Thread.Sleep(1000);
    }
  }

The GetMessageTypeName() method from the SubscriptionFactory.cs file does not seem to handle interfaces. Why does it actually remove the "I" from the interface type name ?

    public string GetMessageTypeName(Type type)
    {
        var typeName = type.Name;

        if (!type.IsInterface)
        {
            return typeName;
        }

        if (!typeName.StartsWith("I"))
        {
            var message = string.Format(
                "Cannot use '{0}' as an interface message contract because the message type name doesn't start with 'I'",
                typeName);

            throw new NotSupportedException(message);
        }

        return typeName.Substring(1);
    }

@jonnii
Copy link
Owner

jonnii commented Mar 1, 2016

IIRC the implementation here was done with the idea of just subscribing to interfaces, not for message versioning. The integration tests are here:

https://github.com/jonnii/chinchilla/blob/c73bc81197990111185c9dae081f72e6b29de439/src/Chinchilla.Integration/Features/SubscribeFeature.cs

Given the current implementation you could:

  1. Publish OtherMessage and OtherMessage2 to the same exchange
  2. Subscribe to IOtherMessage on a queue bound to that exchange

You'd need to configure the subscriptions and publishers to do this.

@abrasat
Copy link
Author

abrasat commented Mar 2, 2016

Thank you for the reply, I will look into that.
I was not thinking about message versioning, I just have different messages that all implement a base interface. This is a quite common scenario when doing messaging applications.
The integration tests that you mentioned work only because the name of the implementation class is the same as the name of the interface, just without the leading "I". I think this is a special case, and not a general approach for supporting subscriptions for interfaces.

@jonnii
Copy link
Owner

jonnii commented Mar 2, 2016

@abrasat I'm actually not sure what would happen if you tried to puhblish an interface, it would probably work but you'd need to configure the exchange explicitly. That said you need to be careful in general when serializing interface types, regardless of whether or not you use this library, e.g. how would you serialize an IList<string>? It would depend on the implementation. Deserializing you have the same problem. It could be an array, it could be a List<string> it could be a HashSet<string>?

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

No branches or pull requests

2 participants