Consuming and Governing Evolving Services

Published 12 February 07 11:41 PM | andersnoras 

Two of the key benefits of an SOA are increased business agility and reduced cost of change. Still, many developers find that the intense focus on service contracts, data schemas and similar reduces their agility and prolong projects. An underlying assumption of the contract first approach is if you can design things right, you’ll achieve a stable and well-functioning service. This is the reason why many agile practitioners shun the contract first approach, and label it as Big Design Up Front. Unless you’re writing a simple service, my experiences with defining service and data contracts is that you cannot perceive what the needs of the service will be. This has led me to the conclusion that SOA, just like any other architecture, needs to evolve. Even when you adhere to the SOA tenets, it is a big risk that your clients will be tightly coupled with your services. This is partially because of how our web service frameworks are designed and how we design our services. There are many schema design patterns we can apply to make our contracts more open to extension and change, but this does not solve the problem as a whole. The common guidance for evolvability is to version contracts, but this increases the need for service governance. Hoping that a governance and service versioning will solve problems is really just a way of shifting the architecture burden from the service developers to the service owner. To avoid spending big on supporting deprecated services, these services need to be retired at some point. With a versioning strategy this means that all the consumers who are still using the deprecated service will need to make the jump to the right version at the same time. Since a breaking change introduced the need to create a new version of the service, this will create a ripple of changes for all the clients consuming the deprecated service.

Another reason why we get strong dependencies between clients and services is that web service clients generally are built using naïve XML serialization to give the developers the impression that they’re working with objects rather than messages.

As Ian Robinson and Martin Fowler point out in their interview with Ron Jacobs, most clients are only interested in a subset of the information in a complex message. The common all-or-nothing approach of regular web service proxies forces the client to recognize the entire structure, rather than just the few elements it needs. This implies that the contract really is an agreement between the service and its consumers – where the service dictates the terms and conditions. There is no way for a service to specify that I just need this field and that field, allowing the service to change the rest of the contract as it wishes without breaking anything. A better contract would acknowledge that it is an agreement between two parties, the service and a consumer. We cannot change the web service specifications to adopt this concept, but we can use different means to achieve such agreements within an SOA. Integration or acceptance tests is a good mean to achieve this, and this was my primary motivation for writing the Transparent SOAP HTTP Proxy I blogged about yesterday.

Lets look at some examples of how you can use the library to validate that at clients requirements are met by a service.

WebServiceX's Translation Service is a simple service you can use to translate a string from one language to another. The service exposes a simple contract, requiring an enumeration value to be supplied to specify the languages to translate between and a string containing the text to be translated. Still it is possible to make breaking changes to this contract. Whenever the service gets the capability to translate between new languages, a traditional client would have to regenerate its proxy to get an enumeration that expresses the new capability. Below is an example which specifies the language parameter as a string rather than an enumeration when invoking the service.

[Test]
def CanInvokeTranslationService():
    service=WebServiceClient("http://www.webservicex.net/TranslateService.asmx?WSDL")
    frenchText = service.Translate("EnglishTOFrench","The quick red fox jumped over the lazy brown dog")
    Assert.AreEqual("""Le renard rouge rapide a sauté par-dessus le chien brun paresseux""",frenchText)

Since the contract is simple, this example could also be implemented with a conventional .NET web service proxy, so lets look a more complex service.

[Test]
def CanGetStockQuote():
    symbol=
"MSFT"
    service=WebServiceClient("http://www.webservicex.net/stockquote.asmx?WSDL")
    quote=service.GetQuote(symbol)
    XmlAssertion.AssertXPathEvaluatesTo("/StockQuotes/Stock/Symbol",quote,symbol)
    XmlAssertion.AssertXPathExists("/StockQuotes/Stock/Name",quote)

This example gets delayed stock quotes from WebServiceX's Stock Quote service. In comparison with the previous example this service returns a string with an XML structure for the stock data. I'm using XmlUnit to verify that the returned stock quote is for the stock I requested by applying an XPath evaluation and that the quote has a "Name"-element.
The service returns lots of information about the stock, but since these two elements are the only ones used by our application We don't really have to care about the rest of the structure. As long as the message contains these elements, the service is free to change without breaking our client.

Our third example invokes a service that retrieves the time the sun rises and sets anywhere in the world. The service only requires the clients to specify the latitude and longitude. However, since the service is designed to both accept and return the same data structure. If we generate a web service proxy using Visual Studio, WSCF or the WSDL tool we'll get a proxy class similar to this:

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.webserviceX.NET/", TypeName="LatLonDate")]
public partial class LatLonDate
{
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="Latitude")]
    public float Latitude;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="Longitude")]
    public float Longitude;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="SunSetTime")]
    public float SunSetTime;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="SunRiseTime")]
    public float SunRiseTime;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="TimeZone")]
    public int TimeZone;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="Day")]
    public int Day;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="Month")]
    public int Month;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(ElementName="Year")]
    public int Year;
    public LatLonDate()
    {
    }
}

As you can see, this class contains a lot of data which really isn't needed when we submit a request to the service. Another issue with this is that the client can break easily if the service interface changes.

[XmlType(Namespace:"http://www.webserviceX.NET/", TypeName:"LatLonDate")]
class LatLon:
    [property(Latitude)] private lat as int
    [property(Longitude)] private lon as int
    def constructor():
        pass
    def constructor(lat as int, lon as int):
        self.lat=lat
        self.lon=lon

Since the WebServiceProxy is capable of doing type coercion, we can define our own structure for use within the client. The class above only contains the two properties that are relevant when we submit the service request.

[Test]
def CanGetSunriseAndSunsetTimes():
    service=WebServiceClient("
http://www.webservicex.net/sunsetriseservice.asmx?WSDL")
    oslo=LatLon(59,10)
    result=service.GetSunSetRiseTime_AsXml(oslo)
    XmlAssertion.AssertXPathEvaluatesTo("/*/*[local-name()=\"Latitude\" and namespace-uri()='
http://www.webserviceX.NET/']", cast(string,result.InnerXml),oslo.Latitude.ToString())
    XmlAssertion.AssertXPathEvaluatesTo("/*/*[local-name()=\"Longitude\" and namespace-uri()='
http://www.webserviceX.NET/']",cast(string,result.InnerXml),oslo.Longitude.ToString())

Since this service returns a complex type, the generated proxy will return a typed object that is not known at compile time. We could use reflection to interact with this type, but it is more convenient to apply the _AsXml suffix to the method name to make the WebServiceProxy return an XML representation rather than the concrete type. Again we can apply XPath validations to the document to verify that the service has not broken the client.

If these services were governed in an enterprise, the consumers could submit NUnit tests that verify that the services behave the way the client expects. Whenever a change is made, this test suite can be run. If all the lights are green the change hasn't broken anything. Governing services this way is both easier and cheaper than versioning service contracts and supporting deprecated services. It also allows us to use an agile approach in developing an SOA.

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Anders Norås' Blog said on March 27, 2007 2:11 PM:

Arjen Poutsma , who is the man behind Spring-WS has written about a more flexible way to do web services

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below