2

Preface: This code is used within a windows desktop application, client / server application, where the server sends and receives messages to/from the client using SMessage based classes

I have the following interface

public interface IMessage
{
    string ID { get; }

    string R_ID { get; set; }

    DateTime Send { get; }
}

Here is the concrete implementation of this interface:

[Serializable]
public class SMessage : IMessage
{
    public string ID { get; set; }

    public string R_ID { get; set; }

    public DateTime Send{ get; set;}

    public SMessage()
    {
        R_ID = "";
        ID = Guid.NewGuid().ToString();
        Send = DateTime.UtcNow;
    }

    public SMessage(SMessage msg)
    {
        ID = msg.ID;
        Send = msg.UTCSend;
        R_ID = msg.R_ID;
    }

}

I have released software to the world using the above interface and now I need to add a piece of additional data to this interface "Where"

public interface IMessage
{
string ID { get; }

string R_ID { get; set; }

DateTime Send { get; }

string Where { get; }
}

My question : Will adding this piece of data break existing clients in the field?

If so, how I can I update the interface / concrete classes so existing clients don't break?

Thanks

Additional info:

The SMessage is the base class for other messages that are sent within the application:

public class InstallMessage : SMessage
{
}

public class ChangeState : SMessage
{
}

How can I keep from breaking existing clients?

So, if I do this:

public interface IMessage2 : IMessage
{
string Where { get; }
}

And this:

public class SMessage : IMessage2
{
 // the correct implementation for IMessage2 is added and omitted here for brevity
}

So what I am unsure about is how do I handle the case where I don't know if the message is from IMessage2 or not? ( NOTE: this code is in the client and server applications )

EXISTING CODE IN THE FIELD:

public void ReceiveChange( ChangeState msg )
{
    string x = msg.ID.ToString();
}

NEW CODE THAT WILL BE SENT OUT WITH NEXT VERSION:

public void ReceiveChange( ChangeState msg )
{
    string x = msg.ID.ToString();

    // do I need to do some converting to keep from breaking ?
    IMessage2 iMsg = msg as IMessage2;
    if( iMsg2 != null )
    {
       string y = iMsg2.Where;
    }
}

Thanks

MrLister
  • 634
  • 7
  • 32
  • I think it could only break clients that have created a derived type of `SMessage` (it's not sealed). – itsme86 Sep 02 '16 at 15:09
  • 1
    Please have a read of [versioning interfaces](http://programmers.stackexchange.com/questions/138948/how-do-you-evolve-version-an-interface) at programmers.SE. – kayess Sep 02 '16 at 15:11
  • For your update, you don't even need to cast, `msg` will already expose `.Where` you can just do `string y = msg.Where;` without issue. You would only need to do the cast if `ReceiveChange` took in a `IMessage` instead of a `ChangeState`. (Also, your cast is wrong, you will get a exception with that, you should have done `IMessage2 iMsg = msg as IMessage2;`, the `as` will return null if the cast fails where a normal cast will throw a runtime exception) – Scott Chamberlain Sep 02 '16 at 15:57
  • thank you very much, but I still have a concern if I update the server and there are OLD clients in the field that do NOT have the IMessage2 interface in the message.. won't the server crash then? In other words Server has new IMessage2 messages... client has IMessage message. Client calls into server with a message that is based off the IMessage interface... how does the server prevent a crash ? – MrLister Sep 02 '16 at 17:52

3 Answers3

1

If in an WebAPI scenario.

It will only break existing clients if you have a dependency upon the newly added fields in a method that accepts IMessage as a parameter.

public void ServiceMethod(IMessage message) {
   if (message.Where == null)
      throw new ArgumentException("message.Where is null");
}

you can add things to interfaces and as long as you have code to properly handle the missing information existing clients will be fine.

The proper way to handle this though is to 'version' your services and data contracts. I find namespace versioning the easiest to maintain. You would define a new namespace (say v2) and redefine everything that actually changes, methods, data contracts, etc. And then in your routing, route the v2 messages (http://acme.com/api/v2/messages) to the new namespace or if not specially routed (http://acme.com/api/messages) route it to the old namespace.

If in a directly referenced library.

Then yes - it will break existing clients. Unless your factory that produces concrete implementations can determine which the client wants. Something similar to the WebAPI routing - but for directly referenced libraries. But this is extremely difficult.

Rick the Scapegoat
  • 1,056
  • 9
  • 19
1

Your interface's consumers won't complain, but the implementations will.

If you want to avoid this, then create a new interface that extends from the old one:

public interface INewMessage : IMessage
{
    string Where { get; set; }
}
Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
0

Yes, it will break the existing clients if they implmented their own classes that use IMessage that do not derive from SMessage. This is the reason why Microsoft has not updated interfaces in the .NET framework between versions to add new features. For example in .NET 4.5 DbDataReader got new async methods that returned tasks but they could not update IDataReader because that would have broken anyone who implemented IDataReader without deriving from DbDataReader.

If you don't want to break the code of people who created classes with IMessage but without using SMessage you must either create a new derived interface that has the additional field (For example this is what COM objects do, you will often see ISomeInterface, ISomeInterface2, ISomeInterface3 etc.) or not update the interface at all and only update concrete implementations that other people may have derived from.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • That's because Microsoft didn't implement a factory to create and return IDataReader implementations, nor did they implement versioning. All implementations are directly instantiated. If a factory is used to instantiate concrete implementations of interfaces then you can change interfaces with versioning. – Rick the Scapegoat Sep 02 '16 at 15:21
  • @rick I can't think of a single time where I have ever directly created a data reader, it has always been from a `.ExecuteDataReader()` or `.ToDataReader()` factory method. – Scott Chamberlain Sep 02 '16 at 15:22
  • Yes you are absolutely right. But those methods return the concrete classes of DbDataReader - not IDataReader. That's where the crux of it lies. – Rick the Scapegoat Sep 02 '16 at 15:24