mark nottingham

Describing Generative Identifiers in WSDL

Friday, 16 April 2004

Web Services

To use WSDL to describe RESTful interactions, you need some way of accommodating generative resource identifiers. In a nutshell, this means some part of the URI is dynamic. For example, with HTTP I might describe an address book where someone named “Jones” has a corresponding entry URI;

http://www.example.org/people/Jones

When describing the overall interface on offer, it’s not that interesting to talk about this particular resource; rather, you want to describe the interface provided for all people. To do this, you really need a way to talk about the sub-resources of /people more generically; e.g., something equivalent to

http://www.example.org/people/$person::name

where $person::name is a particular resource type. Then, when a resource is instantiated, you can dynamically (generatively) name it. Another use case for this is describing reliable POST in HTTP; you need to dynamically instantiate a resource that is specific to an interaction.

In WSDL terms, this feels like a binding issue. With an abstract operation described like this (portions omitted for brevity);

<types>
    <schema targetNamespace="http://example.com/AddressBook.xsd"
            xmlns="https://www.w3.org/2000/10/XMLSchema">
       <element name="name">
          <complexType>
              <all>
                  <element name="LastName" type="string"/>
              </all>
          </complexType>
       </element>
    </schema>
</types>

<message name="personName">
    <part name="body" element="x:LastName"/>
</message>

<portType name="AddressBookEntry">
    <operation name="getPerson" web:Method="GET">
        <input message="tns:personName"/>
        <output message="tns:personDetails"
         wsa:Action="urn:ietf:params:media-type:text/html"/>
    </operation>
</portType>

Here, the input message has a single element, x:LastName. In the binding, you need a way to map that to the request, assuming we’re just describing vanilla HTTP here, and not SOAP.

Although the WSDL HTTP GET/POST binding takes a stab at this, but it doesn’t really get there. What you really need is a way to directly map portions of the abstract request message to the request URI, something like

<binding name="AddressBookEntryRestHttpBinding" type="tns:AddressBookEntry">
    <rest:binding transport="http://example.org/ns/rest-http-binding"/>
    <operation name="getPerson">
       <input>
           <rest:path_segment value="x:LastName"/>
       </input>
       <output>
           <rest:body use="literal"/>
       </output>
    </operation>
</binding>

Here, the x:LastName element in the request message schema is mapped to the final path segment of the resource identifier. So, if the input message instance had:

<x:LastName>Jones</x:LastName>

it would get serialised onto the wire as:

GET /people/Jones HTTP/1.1
Host: www.example.com

(BTW, I’m being a bit loose in referring to this as RESTful; it’s more HTTP-specific, but I need to differentiate between this and current practice with HTTP in WSDL).

Queries for Free

This approach easily extends to URI query strings when binding to HTTP as well. For example, describing an operation that finds people;

<types>
    <schema targetNamespace="http://example.com/AddressBook.xsd"
            xmlns="https://www.w3.org/2000/10/XMLSchema">
       <element name="name">
          <complexType>
              <all>
                  <element name="LastName" type="string"/>
              </all>
          </complexType>
       </element>
    </schema>
</types>

<message name="personQuery">
    <part name="body" element="x:LastName"/>
</message>

<portType name="AddressBookEntryFinder">
    <operation name="findPerson" web:Method="GET">
        <input message="tns:personQuery"/>
        <output message="tns:peopleList"
         wsa:Action="urn:ietf:params:media-type:text/html"/>
    </operation>
</portType>

<binding name="AddressBookEntryFinderRestHttpBinding"
         type="tns:AddressBookEntryFinder">
    <rest:binding transport="http://example.org/ns/rest-http-binding"/>
    <operation name="findPerson">
       <input>
           <rest:query_arg name="name" value="x:LastName"/>
       </input>
       <output>
           <rest:body use="literal"/>
       </output>
    </operation>
</binding>

would, when the findPerson operation were called, turn the same input message instance into this request;

GET /personFinder?name=Jones HTTP/1.1
Host: www.example.com

Where Does This Get Us?

I admit to being not entirely happy with this approach for generative identifiers, because the information in the input message really isn’t input to the operation, as such; it’s metadata about the resource being identified. It would be cleaner in a from-scratch description format, but I don’t think it’s a deal-breaker for using WSDL on its own.

On the other hand, you do get the overhead of WSDL; there are quite a few layers of abstraction to work through. If you’re describing simple RESTful services bound to HTTP without SOAP, it’s a lot of extra work, especially at the binding level, where WSDL is the weakest.

Also, each one of these portTypes will also need a separate binding and service element; it can get pretty verbose. However, as mentioned before, there could be some techniques you could use to aggregate different resources into one portType and then map them back out into URI space in the binding.

For comparison, the same type of description in the straw-man purpose-built format would look something like;

<WebDescription xmlns="http://ns.mnot.net/wd/00">
    <URIStructure base="http://www.example.com/">
        <pathSegment value="personFinder" resource="#PersonFinder"/>
        <pathSegment id="people/">
            <pathSegment type="#lastName" resource="#Person"/>
        </pathSegment>
    </URIStructure>

    <resource id="Person">
        <documentation>An entry in the address book.</documentation>
        <method name="GET">
            <response>
                <representation content-type="text/html"/>
            </response>
        </method>
    </resource>

    <resource id="PersonFinder">
        <documentation>Finds entries in the address book.</documentation>
        <method name="GET">
            <request>
               <query>
                    <argument name="name" value="#lastName"/>
                </query>
            </request>
            <response>
                <representation content-type="text/html"/>
            </response>
        </method>
    </resource>

    <type id="lastName"
     base="https://www.w3.org/TR/2001-REC-xmlschema-2-20010502/#string"/>
</WebDescription>

The choice will probably be driven whether you’re trying to add RESTful semantics to a Web service that might be bound to other protocols, or whether you’re just trying to get the benefits of describing an existing HTTP service. In the former case, you’re already going to have WSDL, so you might as well tack a bit more onto it. In the latter case, it may make sense to start fresh.