0
\$\begingroup\$

I need to design and implement a service (C#, .Net 4.5+) which receives messages, converts the messages to BLL models and passes them to the BLL.

Long story:

  • The service will receive messages (strings containing multiple fields (no JSON, no XML just a string)) from a client.

  • The format of the messages can change between different protocol versions. And the service needs to support multiple different versions.

  • The service will need to deserialize/pars those messages into a message class (version specific).

  • After the parsing the service needs to convert the message class to a BLL model. The idea behind the BLL model is to unify the different protocol versions. The same message type (version independently) should be converted to the same BLL model (There will be only version of the BLL model).

  • The BLL model will then be passed to the BLL.

  • For some messages (not all) the service needs to send a response to the client. This means the service also must be able to convert a BLL model to a version specific message and send this message to the client.

I've written a demo application to show you what I've planned to do. The application "supports" two protocol version (V1 and V2). There are two messages defined MyMessage and MyOtherMessage. MyMessage has changed in V2, MyOtherVersion is in V2 still the same as in V1.

The application contains a class simulating a client which sends both messages in both versions ("totally" random). The "received" messages are getting deserialized into message classes by a version specific serializer. After the deserialization the message classes are getting converted to version independent BLL models.

The communication between the different components (receiver, serializer, converter, etc...) is done using the event aggregation pattern.

My questions:

  • How would you implement the version specific message classes? (My idea was to create a namespace per version and implement the messages which has changed in this version)
  • How would you select the correct serializer and converter for the version of the received message?
  • How would you handle the serialization and conversion of messages which hasn't changed since the last version? (for example MyOtherMessage has not changed in V2, would you call the V1 serializer from the V2 serializer?) in the example I've "changed" the version of the message to V1 and published the event again, so it gets handled by the V1 serializer.
  • Do you think it's a good idea to work with the event aggregation pattern?
  • Do you have any experience with that kind of problem (different versions)… how have you solved it?

I've uploaded the whole demo solution to Google Drive.

Messages V1:

public class MyMessage : MessageBase
{
    #region Properties

    public String MytSring { get; set; }
    public String MySecondString { get; set; }

    #endregion
}

public class MyOtherMessage : MessageBase
{
    #region Properties

    public Int32 MyInt { get; set; }

    #endregion
}

Messages V2:

public class MyMessage : MessageBase
{
    #region Properties

    public String MyString { get; set; }
    public Int32 MyInt { get; set; }

    #endregion
}

Converter V1:

public class MessageConverter
{
    #region Ctor

    public MessageConverter()
    {
        PseudoIoC.EventAggregator.GetEvent<MessageDeserialized>()
                 .ObserveOn( new NewThreadScheduler() )
                 .Subscribe( MessageReceived );
    }

    #endregion

    private ModelBase ConvertMyMessage( MessageDeserialized message )
    {
        var receivedMessage = message.Message as MyMessage;
        return new MyMessageBllEquivalent
        {
            MytSring = receivedMessage.MytSring,
            MySecondString = receivedMessage.MySecondString,
            MyInt = 0
        };
    }

    private ModelBase ConvertMyOtherMessage( MessageDeserialized message )
    {
        var receivedMessage = message.Message as MyOtherMessage;
        return new MyOtherMessageBllEquivalent
        {
            MyInt = receivedMessage.MyInt
        };
    }

    private void MessageReceived( MessageDeserialized message )
    {
        if ( message.Version != "1" )
            return;

        ModelBase result;
        switch ( message.Type )
        {
            case MessageType.MyMessage:
                result = ConvertMyMessage( message );
                break;
            case MessageType.MyOtherMessage:
                result = ConvertMyOtherMessage( message );
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        Console.WriteLine( "Converted to {0}", result.GetType() );
        //publish new event with BLL object => event gets handled by the BLL
    }
}

Converter V2:

public class MessageConverter
{
    #region Ctor

    public MessageConverter()
    {
        PseudoIoC.EventAggregator.GetEvent<MessageDeserialized>()
                 .ObserveOn( new NewThreadScheduler() )
                 .Subscribe( MessageReceived );
    }

    #endregion

    private ModelBase ConvertMyMessage( MessageDeserialized message )
    {
        var receivedMessage = message.Message as MyMessage;
        return new MyMessageBllEquivalent
        {
            MytSring = receivedMessage.MyString,
            MyInt = receivedMessage.MyInt,
            MySecondString = String.Empty
        };
    }

    private void MessageReceived( MessageDeserialized message )
    {
        if ( message.Version != "2" )
            return;

        ModelBase result;
        switch ( message.Type )
        {
            case MessageType.MyMessage:
                result = ConvertMyMessage( message );
                break;
            case MessageType.MyOtherMessage:
                Console.WriteLine( "delegate V2 to V1" );
                message.Version = "1";
                PseudoIoC.EventAggregator.Publish( message );
                return;
            default:
                throw new ArgumentOutOfRangeException();
        }

        Console.WriteLine( "Converted to {0}", result.GetType() );
        //publish new event with BLL object => event gets handled by the BLL
    }
}

Serializer V1:

public class MessageSerializer
{
    #region Ctor

    public MessageSerializer()
    {
        PseudoIoC.EventAggregator.GetEvent<MessageReceivedEvent>()
                 .ObserveOn( new NewThreadScheduler() )
                 .Subscribe( MessageReceived );
    }

    #endregion

    private void MessageReceived( MessageReceivedEvent message )
    {
        if ( message.SchemaVersion != "1" )
            return;

        MessageBase result;
        switch ( message.Type )
        {
            case MessageType.MyMessage:
                result = ParsMyMessage( message );
                break;
            case MessageType.MyOtherMessage:
                result = ParsMyOtherMessage( message );
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        PseudoIoC.EventAggregator.Publish( new MessageDeserialized
        {
            Type = message.Type,
            Message = result,
            Version = "1"
        } );
    }

    private MessageBase ParsMyMessage( MessageReceivedEvent message )
    {
        var values = message.Message.Split( new[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries );
        var result = new MyMessage
        {
            MytSring = values[0],
            MySecondString = values[1]
        };
        Console.WriteLine( "Revived V1 MyMessage" );
        return result;
    }

    private MessageBase ParsMyOtherMessage( MessageReceivedEvent message )
    {
        var values = message.Message.Split( new[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries );
        var result = new MyOtherMessage
        {
            MyInt = values[0].ToInt32()
        };
        Console.WriteLine( "Revived V1 MyOtherMessage" );
        return result;
    }
}

Serializer V2:

public class MessageSerializer
{
    #region Ctor

    public MessageSerializer()
    {
        PseudoIoC.EventAggregator.GetEvent<MessageReceivedEvent>()
                 .ObserveOn( new NewThreadScheduler() )
                 .Subscribe( MessageReceived );
    }

    #endregion

    private void MessageReceived( MessageReceivedEvent message )
    {
        if ( message.SchemaVersion != "2" )
            return;

        MessageBase result;
        switch ( message.Type )
        {
            case MessageType.MyMessage:
                result = ParsMyMessage( message );
                break;
            case MessageType.MyOtherMessage:
                Console.WriteLine( "delegate V2 to V1" );
                message.SchemaVersion = "1";
                PseudoIoC.EventAggregator.Publish( message );
                return;
            default:
                throw new ArgumentOutOfRangeException();
        }

        PseudoIoC.EventAggregator.Publish( new MessageDeserialized
        {
            Type = message.Type,
            Message = result,
            Version = "2"
        } );
    }

    private MessageBase ParsMyMessage( MessageReceivedEvent message )
    {
        var values = message.Message.Split( new[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries );
        var result = new MyMessage
        {
            MyString = values[0],
            MyInt = values[1].ToInt32()
        };
        Console.WriteLine( "Revived V2 MyMessage" );
        return result;
    }
}

EventAggregator:

public interface IEventAggregator
{
    IObservable<TEvent> GetEvent<TEvent>();
    void Publish<TEvent>( TEvent eventArgs );
}

public class EventAggregator : IEventAggregator
{
    #region Fields

    private readonly ConcurrentDictionary<Type, Object> _subjects = new ConcurrentDictionary<Type, Object>();

    #endregion

    public IObservable<TEvent> GetEvent<TEvent>()
    {
        var subject = (ISubject<TEvent>) _subjects.GetOrAdd( typeof (TEvent), x => new Subject<TEvent>() );
        return subject.AsObservable();
    }

    public void Publish<TEvent>( TEvent eventArgs )
    {
        Object subject;

        if ( !_subjects.TryGetValue( typeof (TEvent), out subject ) )
            return;

        var castedSubject = subject as ISubject<TEvent>;
        castedSubject?.OnNext( eventArgs );
    }
}
\$\endgroup\$

1 Answer 1

4
+50
\$\begingroup\$

Usually I would vote to close, because this code looks more like example code, but as it has a bounty on it, closing could lead to a much more overhead by refunding the bounty back to you, so I will just answer your questions.


How would you implement the version specific message classes? (My idea was to create a namespace per version and implement the messages which has changed in this version)

  • I would create for eachh message type and version a new class naming them like e.g MyMessageV1, MyMessageV2 etc. By using namespaces you could come into trouble if you are using the wrong using's, hence you would get lost in the namespace jungle.

    If you need a V2 for e.g the MyMessage class but not for the MyOtherMessage class, I would nevertheless create a MyOtherMessageV2 which just inherits MyOtherMessageV1.

How would you select the correct serializer and converter for the version of the received message?

  • By using a SerializerFactory and ConverterFactory which will return the correct Serializer and Converter based on the type of the message and on the to be added property to the MessageBase class.

    In this way I would pass e.g the MessageDeserialized message to the ConverterFactory and would get the correct converter to use.

    This has the advantage, if you add a new message or change the message version, this will be the only place where you need to change anything about the converting or serializing of the messages. For sure you need to add a new serializer and converter too. These Serializer and Converter should implement an interface for the intented purpose.

How would you handle the serialization and conversion of messages which hasn't changed since the last version? (for example MyOtherMessage has not changed in V2, would you call the V1 serializer from the V2 serializer?) in the example I've "changed" the version of the message to V1 and published the event again, so it gets handled by the V1 serializer.

  • See first and previous answers.

Do you think it's a good idea to work with the event aggregation pattern?

  • Here I will answer with Martin Fowlers words from here

    When to Use It

    Event Aggregator is a good choice when you have lots of objects that are potential event sources. Rather than have the observer deal with registering with them all, you can centralize the registration logic to the Event Aggregator. As well as simplifying registration, a Event Aggregator also simplifies the memory management issues in using observers.


Do you have any experience with that kind of problem (different versions)… how have you solved it?

  • No.
\$\endgroup\$
6
  • \$\begingroup\$ Thanks for your answer. Why would you close the question?... I’ve described my problem and my solution, asked clear questions and provided code (inc. VS solution as download). \$\endgroup\$ Commented Aug 14, 2015 at 9:00
  • \$\begingroup\$ Because it does look like example code and example code is off topic \$\endgroup\$
    – Heslacher
    Commented Aug 14, 2015 at 9:01
  • \$\begingroup\$ It perfectly shows the problem and the possible solution... Why would I write the “final” code to then ask for design advice? \$\endgroup\$ Commented Aug 14, 2015 at 9:09
  • \$\begingroup\$ Because that is what this site is about. Design review isn't in the scope of this site. \$\endgroup\$
    – Heslacher
    Commented Aug 14, 2015 at 9:12
  • \$\begingroup\$ Ok I thought this would be part of the site... Can you recommend a design/architecture review site? \$\endgroup\$ Commented Aug 14, 2015 at 9:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.