Evolving an Interface
April 8, 2002
When Matt and I first proposed the standardized interface for pencil discovery and ordering, we only intended to write a few articles on the concept and then let the whole idea more or less die. Then, the strangest thing happened—we realized we wanted to keep running with the idea. Actually, the realization came in a moment of serendipitous inspiration: To build the system mentioned in our last installment, the WSDL files that describe the pencil-related interfaces would have to contain some more features. In short, the interface would have to evolve—and we could talk about an issue that will soon be a high priority for many Web service developers: versioning. If you look at the documentation, articles, and books that have gone out about Web services, they spend little time talking about successive versions of a Web service.
So this week we will look at some ways that the underlying code can change to accommodate new versions. Then, we will demonstrate these ideas by adding the missing features to the implementations of the WSDL.
What did we miss? Here is a quick list:
- You can find out what a company is selling, but nothing tells you how much the item costs.
- You can search the items, but no method exists to just send the complete catalog.
- When an order fails, we did not define the specific faults that might happen. Right now, if the order items are not in stock, can only be partially filled, or do not exist, we just hand out generic faults. WSDL allows us to define custom faults, and we intend to use them.
Everyone who developed COM applications: How many of you remember the golden rules of versioning? Okay, hands down. As a quick review, here are the rules:
- Always increment the version number.
- Freeze the interface on the previous version. All new features should go into a new interface.
- Freeze all structures used on the previous version. Any enhancements to those structures should show up in new structures.
Whether talking about COM clients bound to IDL, or Web service clients bound to WSDL, these rules persist. Changing an established interface will decrease the likelihood that any clients will still work. For those clients that do still work, we can assume it is a result of luck. I don't like depending on luck. How do we take these golden precepts from COM and apply them to WSDL?
WSDL version indication is done via the targetNamespace attribute of the definitions element. This namespace gives the SOAP messages meaning by relating the messages to a specific bit of code implemented somewhere on the server. The targetNamespace attribute has an XSD type of anyURI. This attribute could be used in a large number of ways to indicate the version. For a service named Foo that was hosted at msdn.microsoft.com, a few options exist for the first version.
The targetNamespace could be named http://msdn.microsoft.com/foo. This works by giving the interface a unique namespace name. This option does not fit our needs, however, because it does not include an obvious mechanism for indicating whether one version is earlier or later than another. I suppose you could follow up later versions of the interface with namespaces such as http://msdn.microsoft.com/foo1 and http://msdn.microsoft.com/foo2, but that seems silly.
The targetNamespace could be named http://msdn.microsoft.com/1.0/foo. Again, this gives the interface a unique namespace identifier. This option fits our needs, because it gives us an obvious indicator of version as well as a place to increment that version in a way people are used to seeing. As we are dealing with an XML-centric world, however, we might do well to follow the lead of the newer XML specifications, such as SOAP 1.2, XML Schema, and XSLT. While this option is viable, it does not follow this lead.
Call the targetNamespace http://msdn.microsoft.com/2002/04/foo. This has a few small advantages over option 2. For one, this is the versioning scheme employed by the newer XML specifications. People who are used to looking at XML will find versioning by date more familiar. As an added bonus, versioning by date allows a person or machine to easily figure out when the version was released. You can increase the resolution of the version to reflect the frequency of releases. A resolution down to the hour would indicate that releases are coming out far too frequently. If your team does nightly builds, extend the interim granularity of the version to the date of the build. Regardless of what you do, don't be cute and use zero-based month and day-of-month numbers. It's counterintuitive.
Of the above options, both 2 and 3 fit the bill, with 3 being the versioning option that many XML users I have talked to like the best. An added advantage of date-based versioning is that you will know how long the interface has been available.
Once you have a versioning scheme ready, you still have to put those updates into your XSD and message layouts. WSDL has no concept of portType or binding inheritance. It does allow one endpoint to implement multiple bindings. To access the endpoint as one of several bindings, a specific targetNamespace would be used for the messages to indicate which binding is being invoked. To use the same endpoint in a different way, another targetNamespace is used. This is analogous to the way QueryInterface works on an object that implements different COM interfaces. It's the same object, but you access it in different ways by using different names. So, when you modify a Web service by changing existing XSD types, by adding operations, or by changing existing operations, what should you do?
When changing an XSD type, create a brand new type in a brand new namespace. This new namespace should still stick with your versioning model. If the first version was in a namespace such as http://foo.org/bar/2001/11/20/types, the new namespace should only change the date information. That new type, if published on April 5, 2002, should be in the namespace http://foo.org/bar/2002/04/05/types. Any related sub-types should remain in the old namespace and simply get imported into the new one. Here, no wishy-washy answers.
It gets trickier, though, when you talk about changes to the messages. Without a doubt, the changes should wind up in a new namespace that reflects the fact that its contents are newer than the interface being extended. So, if a message or XSD data type changes, the related operation, portType, and binding change too. The question here is "How?" The answer is "It depends."
If the methods are loosely related, you can get away with creating a just-enough WSDL to include the new signatures. You can tell that the methods are loosely related when an individual would not use the results of one Web method call to invoke another. For example, if a Web service exposed GetStockQuote and GetTemperature, you could separate the two methods and not harm usability at all. If you have something more like the Favorites Service, it would not make sense to version the GetFavorites call independent of AddCategory or any other Web method. When the methods in an interface are closely related, you should migrate the entire interface when revising or enhancing any part of that interface.
To sum things up, here are the guidelines to use when updating an interface:
- The changes always go into a new namespace.
- The new interface should be a superset of the old one.
- It is a good idea to keep the data model the same when versioning the interface.
- Never revise data structures. Instead, add new ones as needed.
Updating the PencilSellers.org WSDL Files
After looking at all of this advice for how to revise the WSDL files, the question remains: What did we do with the PencilSellers.org sample to resolve our issues with things we missed? Well, we took the schema and updated things to include what we missed. This may happen a few more times as this sample evolves. If you look at our existing WSDL, none of it includes date–based versioning information. Why did this happen? Mostly lack of thought on our parts. This installment of At Your Service represents a chance to fix that prior mistake. To do so, three items need to be updated:
- The Pencil Schema.
- The Discovery binding.
- The Ordering binding.
The bindings themselves are changing quite a bit as well. A good number of the existing SOAP toolkits support document/literal encoding. Document/literal has quite a bit more flexibility in how it represents data than rpc/encoded. We chose rpc/encoded because the Apache SOAP toolkit only supports that scheme. With the release of Beta 1 of the Axis toolkit from the Apache group, they are now on an even footing with .NET-based Web services. Because of this recent change, we now feel comfortable going with document/literal encoding.
Updating the Pencil Schema
This was quite possibly the easiest item to fix. The schema describing the pencil omitted the price. We wanted to avoid people seeing a really nice pencil and then getting shocked that just one would cost $25 US. Instead, we want to deliver that shock right away. In order to do so, the schema needs to add an element to pencil that reflects the price. It's a fairly simple matter to do this.
<xsd:complexType name="Pencil"> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="type" type="xsd:string" /> <xsd:element minOccurs="1" maxOccurs="1" name="hardness" type="xsd:int" /> <xsd:element minOccurs="1" maxOccurs="1" name="width" type="xsd:float" /> <xsd:element minOccurs="1" maxOccurs="1" name="length" type="xsd:float" /> <xsd:element minOccurs="1" maxOccurs="1" name="imageURL" type="xsd:string" /> <xsd:element minOccurs="1" maxOccurs="1" name="manufacturer" type="xsd:string" /> <xsd:element minOccurs="1" maxOccurs="1" name="styleName" type="xsd:string" /> <xsd:element minOccurs="1" maxOccurs="1" name="productID" type="xsd:string" /> <xsd:element minOccurs="1" maxOccurs="1" name="price" type="xsd:decimal" /> </xsd:sequence> </xsd:complexType>
In adding the price, we also had to decide how to handle currency. In order to keep the example somewhat simple and not stray into areas such as tracking values of currencies, we are going to assume that price information is always delivered in US dollars. The retailer is then responsible for knowing how to convert that into the local currency.
A new type also had to be added for the complete catalog. The catalog contains a listing of all items available for purchase as well as a date when the information contained in the catalog will be going bad. This type has the following declaration:
<xsd:complexType name="Catalog"> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="ValidUntil" type="xsd:dateTime" /> <xsd:element minOccurs="0" maxOccurs="1" name="Pencils" type="tns:ArrayOfPencil" /> </xsd:sequence> </xsd:complexType>
The type section also needs to include a few new types. Specifically, it will need to include some information about specific faults being delivered to the caller for the following conditions:
- Cannot fill complete request for item. If the supplier stocks somewhere between 1 and the amount requested by the caller, return this fault along with the number of pencils for the given ID currently available. The caller can use this information to change their order. They may request a smaller number of pencils, remove the item from the order completely, or go place the order elsewhere. Of course, if the item is not in stock, this fault will indicate that zero pencils are available and the caller just can't order that pencil at this time.
- Invalid item identifier. At least one item on the request has an invalid identifier. The item(s) not in the database will be returned as part of the details for this fault.
To identify exactly what these faults will look like, we need to add two new messages and two new types. The messages and modifications to the portType and binding will be shown in the updates to the Pencil Order interface. The faults are being placed in their own namespace: http://pencilsellers.org/2002/04/pencil/fault/types. The schema for these types is as follows:
<?xml version="1.0" encoding="utf-8" ?> <xsd:schema xmlns:tns= "http://pencilsellers.org/2002/04/pencil/fault/types" elementFormDefault="qualified" targetNamespace="http://pencilsellers.org/2002/04/pencil/fault/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:import namespace="http://pencilsellers.org/2002/04/pencil/types" schemaLocation= "http://pencilsellers.org/2002/04/pencil/types/pencilSchema.xsd"/> <xsd:complexType name="NotEnoughAvailable"> <xsd:sequence> <xs:element minOccurs="0" maxOccurs="1" name="QuantityAvailable" xmlns:pencil= "http://pencilsellers.org/2002/04/pencil/types" type="pencil:ArrayOfOrderItem" /> </xsd:sequence> </xsd:complexType> <xsd:element name="OrderItem" nillable="true" xmlns:pencil="http://pencilsellers.org/2002/04/pencil/types" type="pencil:OrderItem" /> <xsd:complexType name="InvalidProductID"> <xsd:sequence> <xsd:element minOccurs="0" maxOccurs="1" name="InvalidIDs" type="tns:ArrayOfInt" /> </xs:sequence> </xsd:complexType> <xsd:complexType name="ArrayOfInt"> <xsd:sequence> <xsd:element minOccurs="0" maxOccurs="unbounded" name="int" type="xsd:int" /> </xsd:sequence> </xsd:complexType> </xs:schema>
The schema imports the normal pencil schema for use in displaying which items from the order were unavailable. It lists each item as OrderItems. This time, each element indicates the order item that was unavailable in the requested amount, and indicates how many of that item is available. The number available will always be between 0 and the quantity requested.
Now that the data items have been updated, we just have to update the information for the messages the Web service accepts.
Updating Pencil Discovery
The discovery binding has a new operation, GetCatalog. As discussed, this returns the complete catalog to the caller and indicates when the data in the message expires. To do this, we add the request and response data types, a pair of messages, and some information to the binding. Since this is not a complex message, we will take a short look at the portType definition.
<operation name="GetCatalog"> <input message="discoveryMessages:GetCatalogSoapIn" /> <output message="discoveryMessages:GetCatalogSoapOut" /> </operation>
Other than this addition of an operation, all other changes to the binding had more to do with moving to document/literal and updating the namespace than anything else.
Updating Pencil Order
The main change here is the addition of fault information to the messages. At this point in time, ASP.NET does not make use of the fault specification, but you can still define to the developer what a fault will look like. The message that might return a fault is PlaceOrder. The operation has been updated to read as follows:
<operation name="PlaceOrder"> <input message="orderMessages:PlaceOrderSoapIn" /> <output message="orderMessages:PlaceOrderSoapOut" /> <fault message="faultMessages:NotEnoughAvailableFault" /> <fault message="faultMessages:InvalidProductIDFault" /> </operation>
Then, the binding gets updated like this:
<operation name="PlaceOrder"> <soap:operation soapAction="http://pencilsellers.org/2002/04/pencil/PlaceOrder" style="document" /> <input> <soap:body use="literal" /> </input> <output> <soap:body use="literal" /> </output> <fault> <soap:fault use="literal" /> </fault> </operation>
When updating a Web service description, you need to do more than simply add new types and messages. You also need to think about how to indicate that the new interface is a new version of the Web service. Do not worry too much about major and minor versions when you are updating a Web service interface. By changing one little thing, you are breaking the interface contract. When that contract is broken, the client does not care if your update took a day, a month, or a year. They just need to know two things:
- Does the old interface still work?
- Where is the WSDL for the new version?
Yeah, you need to update your API reference, sample clients, and other project artifacts. But those two things are what the developer using your service really cares about. Make sure to update the WSDL in a sane manner. Don't worry too much about distributing the types and information across a series of XSD files. If you distribute things in a logical manner, developers will like it.
If you want to start taking advantage of defining the interface first and then letting the .NET tools handle writing the interface in your favorite language, you should make sure to get some good reference material on XSD.
- Using W3C XML Schema
- An XML Overview Towards Understanding SOAP
- XML Schema Primer
- A Quick Guide to XML Schema
At Your Service