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:
<?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.
1 Comments:
Just think if they added Skype integration..
Mmmm - drool.
Ian (a paid up Trillian 2.0 subscriber)
By Anonymous, at November 29, 2004 4:01 AM
Post a Comment
<< Home