What is WSDL and how does it work?
Acknowledgement
Thanks to David Chess for his detailed comments on the original version of this document.
What You Need to Know to Read This
This document assumes you have some familiarity with XML Schema, have at least a little OO background, know a tiny bit about protocols, specifically HTTP and are familiar with SOAP.
Programming Object Model
WSDL is an attempt to make it easier for programmers to deal with protocols. It does this by constraining protocol behavior so that it matches a fairly generic model that can be easily expressed in most programming languages.
In the WSDL worldview there are services and service consumers. A service is an object. An object has a set of interfaces and an interface consists of a set of methods.
WSDL supports four calling patterns when talking to methods. The one-way pattern is a function call with a void output. The request-response pattern is a function call with a non-void output. The solicit-response pattern is an asynchronous call from the service to the service consumer with a non-void output. The notification pattern is an asynchronous call from the service to the service consumer with a void output.
The solicit-response pattern will probably only work with code that is explicitly network aware and has set up the right server objects, think COM/CORBA. The notification pattern can be modeled as a call back and so can be run in just about any multi-threaded/interrupt enabled language.
WSDL also provides for explicit declaration of faults. A fault can be returned in the request-response and solicit-response patterns and have obvious language mappings, especially for languages that have built in exception handling.
WSDL was probably written to assume, although it certainly does not require, low latency. For example, there are no mechanisms to specify expectations about the time between a request and a response. This is critical in creating API interfaces as the API one would use for a low latency request/response pair is likely to be very different from the API one would use for a request for which the response may not be forthcoming any time soon. Most likely the WSDL authors thought that the longer latency transactions are more properly thought of as workflow and would be handled by the appropriate agencies. Either way, WSDL is still a young language and subject to rapid change.
Reading the Schemas
Strictly speaking WSDL is defined in XML Schema but within the actual document it invented its own schema language for illustrative purposes, that schema language is not normative. I have adopted its use here because the actual XML Schema is unreadable. Eventually I need to evaluate Relax-NG.
The WSDL illustrative schema language consists of an actual example of the schema with the characters "?", "*" and "+" appended.
"?" – Zero or 1 instances of the element
"*" – Zero or more instances of the element
"+" – 1 or more instances of the element
If no character is appended then the element appears once.
Implementation
WSDL's behavior is not an exact match for the OO terms I use above but it's close enough to give the reader some idea of what's going on. Below I provide a rough and ready translation between the OO and WSDL names:
Programming Name | WSDL Name |
Object | Service |
Interface | portType |
Method | Operation |
Types
Types are just wrappers for XML Schema definitions, although the extensibility mechanism supports using other types of schema definition languages, even ones that define non-XML syntaxes. The purpose of the types is to enable one to create all the XML definitions that will be referenced by messages when building up an actual protocol communication.
Schema
<wsdl:types> ?
<wsdl:documentation .... />?
<xsd:schema .... />*
<-- extensibility element --> *
</wsdl:types>
Example
<types>
<schema targetNamespace="http://example.com/stockquote.xsd"
xmlns="http://www.w3.org/2000/10/XMLSchema">
<element name="TradePriceRequest">
<complexType>
<all>
<element name="tickerSymbol" type="string"/>
</all>
</complexType>
</element>
<element name="TradePrice">
<complexType>
<all>
<element name="price" type="float"/>
</all>
</complexType>
</element>
</schema>
</types>
Message
Messages are defined at the global level and can be re-used. A message consists of parts. Each part inside the message can eventually be mapped to things like the SOAP body or the SOAP header. A part is just a pointer to a piece of XML Schema. WSDL currently supports referencing XML schema element declarations as well as simpleType and complexType declarations. In theory the XML Schema elements must be defined inside of WSDL type elements but there is some ambiguity in the spec on this issue.
Schema
<wsdl:message name="nmtoken"> *
<wsdl:documentation .... />?
<part name="nmtoken" element="qname"? type="qname"?/> *
</wsdl:message>
Example
<message name="GetLastTradePriceInput">
<part name="body" element="xsd1:TradePriceRequest"/>
</message>
Operation
The next level of object is the operation. An operation is how one expresses one of the four previously described calling patterns. Each operation XML element can contain an input and output XML element as well as zero or more fault XML elements. Different calling patterns require different kinds of elements defined in different orders. The table below gives the requirements:
Calling Pattern |
Required Elements in Specified Order |
One-Way |
Input |
Request-Response |
Input, Output, Fault* |
Solicit-Response |
Output, Input, Fault* |
Notification |
Output |
The calling patterns are conceptual and may or may not map to the actual physical message flow.
For example, a notification is conceptually defined as a single request with no response. However if the binding for this operation were HTTP then the notification would be delivered with a HTTP request and the recipient of the notification would be required to respond with a HTTP response. The fact that a physical response was sent is something that is dealt with by the actual binding. Code that is written to a WSDL interface would never see the response.
The input, output and fault XML elements use an attribute to refer to a particular message definition. This means, in theory, that the same message definition could be used to define an input, output and fault message across multiple operations.
portType
Operations XML elements are gathered together inside of a portType XML element. So now we can define a robust API interface (portType) that contains multiple methods (Operations) that specify the calling function (Input), the output from the calling function (Output) and any faults (Fault). We can also express the calling pattern as one of the four previously discussed by restricting which of the three XML elements we include and in what order.
Schema
<wsdl:portType name="nmtoken">*
<wsdl:documentation .... />?
<wsdl:operation name="nmtoken">*
<wsdl:documentation .... /> ?
<wsdl:input name="nmtoken"? message="qname">?
<wsdl:documentation .... /> ?
</wsdl:input>
<wsdl:output name="nmtoken"? message="qname">?
<wsdl:documentation .... /> ?
</wsdl:output>
<wsdl:fault name="nmtoken" message="qname"> *
<wsdl:documentation .... /> ?
</wsdl:fault>
</wsdl:operation>
</wsdl:portType>
Example
<portType name="StockQuotePortType">
<operation name="GetLastTradePrice">
<input message="tns:GetLastTradePriceInput"/>
<output message="tns:GetLastTradePriceOutput"/>
</operation>
</portType>
Bytes on the Wire
Up till now we have said nothing about how the XML messages are moved from point A to point B. This is intentional. The goal of WSDL is to enable programs to be written without having to think about the actual protocols being used to move messages around. One can write a program expecting a particular portType and not care a whit about what is happening on the wire.
Binding
To actually figure out how to move a particular portType around one has to declare a WSDL binding. A binding provides instructions on how to encode and transport the operations and messages defined in a portType. WSDL doesn't actually have that much to say about how bindings work because all the interesting bits are specific to a particular application, such as SOAP.
The binding XML element has the same structure (in terms of operation, input, output and fault XML elements) as the portType it is 'binding'. At this point an example will save a lot of typing. Imagine we have the following portType:
<portType name="StockQuotePortType">
<operation name="GetLastTradePrice">
<input message="tns:GetLastTradePriceInput"/>
<output message="tns:GetLastTradePriceOutput"/>
</operation>
</portType>
This is a potential SOAP binding for it:
<binding name="StockQuoteSoapBinding" type="defs:StockQuotePortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="GetLastTradePrice">
<soap:operation soapAction="http://example.com/GetLastTradePrice"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
For each XML element in the portType we have a matching XML element in the binding. Inside each of the XML elements in the binding we have instructions on how to encode the matching message inside of SOAP.
In the above example the portType defines a request/response interface with no fault messages. The binding explains how to transport this portType using SOAP over HTTP.
Schema
<wsdl:binding name="nmtoken" type="qname">*
<wsdl:documentation .... />?
<-- extensibility element --> *
<wsdl:operation name="nmtoken">*
<wsdl:documentation .... /> ?
<-- extensibility element --> *
<wsdl:input> ?
<wsdl:documentation .... /> ?
<-- extensibility element -->
</wsdl:input>
<wsdl:output> ?
<wsdl:documentation .... /> ?
<-- extensibility element --> *
</wsdl:output>
<wsdl:fault name="nmtoken"> *
<wsdl:documentation .... /> ?
<-- extensibility element --> *
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
Service
A binding says how to serialize and transport a message but it says nothing about the actual address one uses to send a request to. This separation is useful because one can provide a binding element that can be reused by many tools who wish to provide the interfaces at different locations.
The last step, associating an address with a binding is carried out by a service. A service is a collection of ports. A port is simply an association of an address with a binding. This means that all the methods (operations) for a particular interface (portType) are addressed using the same URI.
If two ports have the same binding then these are required to be alternatives for each other. The scenario where this matters is if one had a portType for maintaining a machine. If there were two machines to be maintained a protocol person would give each machine its own address but use the same binding. To people with a protocol background, network addresses are selection points. So it would seem natural to a protocol person to assign a unique URL to each machine and so differentiate them that way. WSDL doesn't quite forbid this behavior but it makes it more difficult. One either has to define two different services, once for each machine or one has to create two dummy portTypes that have the same content but different names.
A different way to think about this is that for each network object there should be a service. Looked at that way WSDL's service semantics make more sense.
Schema
<wsdl:service name="nmtoken"> *
<wsdl:documentation .... />?
<wsdl:port name="nmtoken" binding="qname"> *
<wsdl:documentation .... /> ?
<-- extensibility element -->
</wsdl:port>
<-- extensibility element -->
</wsdl:service>
Example
<service name="StockQuoteService">
<documentation>My first service</documentation>
<port name="StockQuotePort" binding="tns:StockQuoteBinding">
<soap:address location="http://example.com/stockquote"/>
</port>
</service>
Standards Status
The Web Services Definition Language is a creation of Microsoft, IBM and Ariba. As of 1/1/2002 it is not a standard, its only status is as a W3C Note which is the moral equivalent of just shoving it up on a web page. It has been submitted for consideration to the W3C XML Protocol (XP) working group but it doesn't appear that any official documents based on it have been produced yet.
Extensibility
WSDL is designed to be extremely extensible. It is possible, for example, to include types that are defined using schemas other than the W3C XML Schema language and we have already seen extensive use of various additional SOAP elements for defining bindings and services. The WSDL specification provides a lot of detail regarding which elements can be extended and in what ways.
WSDL also introduces the wsdl:required attribute which is a Boolean value that specifies if the interpreter of the WSDL document MUST understand the extended element or if it can safely ignore it.
Naming
Generally speaking each WSDL entity must have a name that is unique amongst all other entities of the same type defined in the same WSDL declaration. Keep in mind that included files are treated as if they are part of the WSDL declaration.
In the tree below top level names are entities that have to have names that are unique across all entities of the same type. Their children, such as Part, only have to have names that are unique within the scope of their parent.
- Message
- Part
- portType
- operation
- input
- output
- fault
- binding
- port
- service
Documentation and Importing
WSDL allows documentation elements to be inserted just about anywhere. The contents can be absolutely anything. WSDL also provides an import statement that just does a straight include.
SOAP and beyond
WSDL includes extensions for binding to SOAP. All the features one would expect, such as how to specify which HTTP method to use, how to specify the soapAction header and how to specify values that are to go into a soap header versus a soap body are specified. There are also sections on to specify that you want the SOAP messages encoded into URIs or sent as MIME body parts. The only non-obvious aspect is the RPC versus document style encoding. RPC encoding is intended to provide a very constrained mechanism for defining what bytes hit the wire (ala section 5 of SOAP 1.1) so as to ensure RPC level functionality. The document style encoding is no where near as strict in how it encodes which is fine for a normal protocol processor but would probably break a program that was translating to and from APIs.
WSDL also provides bindings for MIME and has gone to great pains to make it easy to add bindings for just about any conceivable protocol transport.