Tips to avoid breaking existing SOAP APIs
These days it might be a bit uncommon to find anybody creating new SOAP (Simple Object Access Protocol) web services. However that does not mean SOAP web services are dead. Due to public perception, Software companies avoid mentioning components that might be considered "old" (or not trendy). In a highly competitive market, where companies keep fighting for the best Developers, referencing older technologies might throw some candidates off. Still, that does not mean components developed with "older" technologies do not require maintenance.
Doing API changes while keep backwards compatibility can be real challenge, that is why for this blog post I will share a few ideas to help avoid breaking existing SOAP APIs. Before continuing, just keep in mind that I will focus on .Net implementation and different frameworks might have different behaviors and/or rules. Here's a few tips that might be helpful.
Mark new properties as optional. If you are adding new properties to the classes used in your web service, make sure they are optional. This way, new API changes won't break any functionality for clients that are not using the latest version of your web service. In this particular case we need to clearly understand the difference between 'minOccurs' and 'nillable' element attributes on the WSDL XML schema. The attribute names are pretty much self-explanatory, but what's the actual different between one and the other? 'nillable' states if the property can is Nullable, keep in mind that most C# primitives types are not nullable (bool, int, decimal, so on). 'minOccurs' defines if the property should be always present or not. For new properties make sure 'minOccurs' is equal to '0' and 'nillable' is true.
Marking a new property as Nullable is easy, but how can be make sure it's skippable? By default non-primitive types can be omitted, but that's not the case for Nullable primitives. To solve this simply add a new boolean property, decorate it with "XmlIgnoreAttribute" to ensure it's not part of the WSDL, use exactly the same name as your new property and append "Specified" (something like "{new property name}Specified") and lastly just define the getter returning if you new property null or not. This is not that easy to follow in words, so check the example presented below.
// our new nullable property
public float? NewProp { get; set; }
// this statement ensures "NewProp" can be omitted. Remember to append "Specified" in front of the property name
[System.Xml.Serialization.XmlIgnoreAttribute]
public bool NewPropSpecified { get { return NewProp != null; } }
Preserve property ordering unchanged. Ensure property ordering remains unchanged on any classes used on your web service. This is important because ordering in preserved when the XSDL XML schema is generated. New properties must always go to the bottom. Here's an example Web service:
public class WsInput
{
public string Str { get; set; }
public decimal Dec { get; set; }
public int Num { get; set; }
// our new property, marked as nullable and added to bottom
public float? NewProp { get; set; }
// this statement ensures "NewProp" can be omitted. Append "Specified" in front of the property name
[System.Xml.Serialization.XmlIgnoreAttribute]
public bool NewPropSpecified { get { return NewProp != null; } }
}
public class WsOutput
{
public string Msg { get; set; }
}
[System.Web.Services.WebService(Namespace = "http://tempuri.org/")]
[System.Web.Services.WebServiceBinding(ConformsTo = System.Web.Services.WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class MyWebService : System.Web.Services.WebService
{
[System.Web.Services.WebMethod]
public WsOutput DummyWsMethod(WsInput input)
{
return new WsOutput();
}
}
New changes shall not have any impact on previous existing calls. Compare WSDL changes after your changes are applied. Make sure you create a test suite that fully tests your existing web service. After applying your changes, run the test suite without updating the client WSDL. Everything should pass flawlessly.