Loose coupling and WSDL versioning
I thought I would jot down some of my thoughts about loose coupling and WSDL versioning.
Some advocate that any change to a WSDL should result in a new WSDL namespace (hence, a new service) so that clients of the existing WSDL can continue to operate against the old service, while new clients can leverage the new service. Personally, I don't subscribe to that line of reasoning.
To me, a fundamental aspect of a loosely coupled system is that you can evolve the system without the need for a big bang-style simultaneous redeployment of all of its deployed components.
Now, while one might say that you could have two deployed services, and sunset the older version once all of its clients have migrated to the new service, that would require that you maintain two versions/instances of the service. This doubles the cost to host/manage the service and it means that you need to also track usage of the sunsetted service and pester its clients to upgrade.
Let's say that I wanted to extend my service's interface to add a new operation to enable a client to retrieve some new information. Sure, you could deploy a separate service for the new operation, but that would mean two services instead of one; again, more work, higher cost, etc. Additionally, access to that service would potentially require that a client that needed to access both operations would possibly be subject to a separate security session. Blech!
Instead, why not simply allow existing clients to ignore the new operation as if nothing had changed and allow newly deployed clients exploit it? Better yet, extend the original operation (assuming you left extensibility points in the right places).
Consider a stockQuote service that begins life with a simple query for the current price of a given stock. Then, by popular demand, some consumers of the service would also like to have the net change from the previous day and the 52 week high and low along with the current price.
If the schema were designed such that the additional information were returned in an extensibility point in the definition of the Quote, new clients could avail themselves of the new information, older clients could safely ignore it. No need to urgently redeploy all clients when the service is redeployed with the new functionality. Everyone is happy. (Of course, we would need the generated client code to ignore the extended content. See David Orchard's writings on XML versioning for explanation of the MustIgnore rule).
Here's the example WSDL for the original StockQuote service:
We can add to the content of the StockQuoteResponse by exploiting the wildcard as follows:
This shouldn't effect the existing clients, as they should be ignoring the wildcard content.
Similarly, one should be able to add a new (optional from the perspective of existing deployed client code) operation(s) to a WSDL portType, reuse the same WSDL namespace and portType name, regenerate and redeploy the service. If you already have deployed client code that understands the old version, they will simply be oblivious of the new operation(s). No harm, no foul. New client code can be generated from the revised WSDL and exploit the new operation(s).
Here's what this might look like:
My personal preference would lean towards the first approach outlined for this particular example, but either would be preferable to deploying a new service.
Most of the pain points with IT relate to the difficulty of evolving tightly-coupled systems because it requires such significant amounts of management and coordination, often requiring far more resources to effect than technical aspect of the change itself. Loose coupling helps reduce the need for coordinated versioning of client and service because it allows for a gradual evolution. That is the fundamental nature of being loosely coupled.
Some advocate that any change to a WSDL should result in a new WSDL namespace (hence, a new service) so that clients of the existing WSDL can continue to operate against the old service, while new clients can leverage the new service. Personally, I don't subscribe to that line of reasoning.
To me, a fundamental aspect of a loosely coupled system is that you can evolve the system without the need for a big bang-style simultaneous redeployment of all of its deployed components.
Now, while one might say that you could have two deployed services, and sunset the older version once all of its clients have migrated to the new service, that would require that you maintain two versions/instances of the service. This doubles the cost to host/manage the service and it means that you need to also track usage of the sunsetted service and pester its clients to upgrade.
Let's say that I wanted to extend my service's interface to add a new operation to enable a client to retrieve some new information. Sure, you could deploy a separate service for the new operation, but that would mean two services instead of one; again, more work, higher cost, etc. Additionally, access to that service would potentially require that a client that needed to access both operations would possibly be subject to a separate security session. Blech!
Instead, why not simply allow existing clients to ignore the new operation as if nothing had changed and allow newly deployed clients exploit it? Better yet, extend the original operation (assuming you left extensibility points in the right places).
Consider a stockQuote service that begins life with a simple query for the current price of a given stock. Then, by popular demand, some consumers of the service would also like to have the net change from the previous day and the 52 week high and low along with the current price.
If the schema were designed such that the additional information were returned in an extensibility point in the definition of the Quote, new clients could avail themselves of the new information, older clients could safely ignore it. No need to urgently redeploy all clients when the service is redeployed with the new functionality. Everyone is happy. (Of course, we would need the generated client code to ignore the extended content. See David Orchard's writings on XML versioning for explanation of the MustIgnore rule).
Here's the example WSDL for the original StockQuote service:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://example.org/StockQuote/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="StockQuote"
targetNamespace="http://example.org/StockQuote/">
<wsdl:types>
<xsd:schema
targetNamespace="http://example.org/StockQuote/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="StockQuoteResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
<xsd:element name="Value" type="xsd:float"/>
<xsd:any namespace="##any"
minOccurs="0" maxOccurs="unbounded"
processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="StockQuoteRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="StockQuoteResponse">
<wsdl:part
element="tns:StockQuoteResponse"
name="StockQuoteResponse"/>
</wsdl:message>
<wsdl:message name="StockQuoteRequest">
<wsdl:part
element="tns:StockQuoteRequest"
name="StockQuoteRequest"/>
</wsdl:message>
<wsdl:portType name="StockQuote">
<wsdl:operation name="GetQuote">
<wsdl:input message="tns:StockQuoteRequest"/>
<wsdl:output message="tns:StockQuoteResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="StockQuoteSOAP" type="tns:StockQuote">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="StockQuote">
<soap:operation
soapAction="http://example.org/StockQuote"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="StockQuote">
<wsdl:port binding="tns:StockQuoteSOAP"
name="StockQuoteSOAP">
<soap:address location="http://example.org/StockQuote"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
We can add to the content of the StockQuoteResponse by exploiting the wildcard as follows:
<xsd:element name="StockQuoteResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
<xsd:element name="Value" type="xsd:float"/>
<xsd:element name="Hi" type="xsd:float"/>
<xsd:element name="Low" type="xsd:float"/>
<xsd:element name="NetChange" type="xsd:float"/>
<xsd:any namespace="##any"
minOccurs="0" maxOccurs="unbounded"
processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
This shouldn't effect the existing clients, as they should be ignoring the wildcard content.
Similarly, one should be able to add a new (optional from the perspective of existing deployed client code) operation(s) to a WSDL portType, reuse the same WSDL namespace and portType name, regenerate and redeploy the service. If you already have deployed client code that understands the old version, they will simply be oblivious of the new operation(s). No harm, no foul. New client code can be generated from the revised WSDL and exploit the new operation(s).
Here's what this might look like:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://example.org/StockQuote/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="StockQuote"
targetNamespace="http://example.org/StockQuote/">
<wsdl:types>
<xsd:schema
targetNamespace="http://example.org/StockQuote/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="StockQuoteResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
<xsd:element name="Value" type="xsd:float"/>
<xsd:any namespace="##any"
minOccurs="0" maxOccurs="unbounded"
processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="AdvancedStockQuoteResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
<xsd:element name="Value" type="xsd:float"/>
<xsd:element name="Hi" type="xsd:float"/>
<xsd:element name="Low" type="xsd:float"/>
<xsd:element name="NetChange" type="xsd:float"/>
<xsd:any namespace="##any"
minOccurs="0" maxOccurs="unbounded"
processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="StockQuoteRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="AdvancedStockQuoteRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Symbol" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="StockQuoteResponse">
<wsdl:part
element="tns:StockQuoteResponse"
name="StockQuoteResponse"/>
</wsdl:message>
<wsdl:message name="StockQuoteRequest">
<wsdl:part
element="tns:StockQuoteRequest"
name="StockQuoteRequest"/>
</wsdl:message>
<wsdl:message
name="AdvancedStockQuoteResponse">
<wsdl:part
element="tns:AdvancedStockQuoteResponse"
name="AdvancedStockQuoteResponse"/>
</wsdl:message>
<wsdl:message name="AdvabcedStockQuoteRequest">
<wsdl:part
element="tns:AdvancedStockQuoteRequest"
name="AdvancedStockQuoteRequest"/>
</wsdl:message>
<wsdl:portType name="StockQuote">
<wsdl:operation name="GetQuote">
<wsdl:input message="tns:StockQuoteRequest"/>
<wsdl:output message="tns:StockQuoteResponse"/>
</wsdl:operation>
<wsdl:operation name="GetAdvancedQuote">
<wsdl:input message="tns:AdvancedStockQuoteRequest"/>
<wsdl:output message="tns:AdvancedStockQuoteResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="StockQuoteSOAP" type="tns:StockQuote">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="StockQuote">
<soap:operation
soapAction="http://example.org/StockQuote"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="StockQuote">
<wsdl:port binding="tns:StockQuoteSOAP"
name="StockQuoteSOAP">
<soap:address location="http://example.org/StockQuote"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
My personal preference would lean towards the first approach outlined for this particular example, but either would be preferable to deploying a new service.
Most of the pain points with IT relate to the difficulty of evolving tightly-coupled systems because it requires such significant amounts of management and coordination, often requiring far more resources to effect than technical aspect of the change itself. Loose coupling helps reduce the need for coordinated versioning of client and service because it allows for a gradual evolution. That is the fundamental nature of being loosely coupled.
6 Comments:
I cannot agree more that compatible changes should not force big bang style upgrades across all components. But I've a few comments on your suggestions.
a. The first solution is fine when third-parties want to extend the service. But this approach is not very convenient if the schema owners want to enhance/fix the service by making compatible changes to the schema.
b. The second solution may not be the right option if the change being made does not alter the exposed behavior of the service. Not only that, if a client wants to talk to services with and without the change, it will have to write complicated code (like, if GetAdvancedQuote operation is offered, use it, or else use GetQuote operation). This gets ugly with most SOAP toolkits.
- Subbu Allamaraju, posting anonymously as I don't use Blogger.
By Anonymous, at December 05, 2004 12:39 AM
Chris,
While I agree with you in principle about not wanting big bang changes and not wanting to maintain n versions of a service, I struggle with the details. For instance, in your first example, you extend a response message because you know the receiver (client) will ignore the pieces it doesn't recognize. But would it work on the request as well?
Imagine that the request had a wildcard and that a new version included an element asking to filter the results somehow. If a new client sent a request to an old service, the service would ignore the filtering and the client would get an unexpected result. I think DaveO's solution to that is introducing a mustUnderstand header into the message data, but that seems like an awful step to take, given the complexity we see with mustUnderstand processing in SOAP.
There is a similar issue with extending an existing WSDL portType definition with a new operation. It works fine as long as you have one instance of the service and it revs before its clients do. But if you have more than one instance of the service and/or clients may start trying to use the new operation before a service is updated, then requests that conform to the contract the service claims to support will fail.
Tim Ewald
By Anonymous, at December 07, 2004 1:39 PM
If I added an operation to an existing WSDL namespace, client services would be unable to use a service locator like UDDI to identify services supporting the new operation, right? The namespace might represent a service supporting the new operation, or it might not. Doesn't this mean if I want t support service discovery in this way I really should mint a new URI for a new interface?
By hughw, at September 21, 2005 2:42 PM
Doesn't the "any" wildcard defy the Unique Particle Attribution constraint? How do you go about that in inserting extension points in your message parts?
By Eco-nemesis, at April 29, 2009 5:22 PM
Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!
By Anonymous, at November 21, 2009 11:09 AM
Hello, I do not agree with the previous commentator - not so simple
By cialis, at November 24, 2010 4:47 AM
Post a Comment
<< Home