Intro

This document provides a Music information Web service Use Case and WSDL 2.0 description of the use case. It explores how to use the WSDL http binding to describe interactions with an HTTP service. This is an example of music information and purchasing Web service. It is styled after the CDDB database http://www.gracenote.com/gn_products/cddb.html and allmusic.com sites. It has generated a number of questions where I don't think the WSDL as exists can express certain things. I'm grateful for any reviews or comments on this.

Use Case

An Artist has

Clients have 3 types of interactions: retrieve all/specific information, change/add/delete information, and search based upon a subset of the Artist fields.

Observations on the Artist WSDL and Schema documents

  1. Overall, there is an explosion of schemas and operations that are needed because of the binding. I think this kind of sucks and comes close to making the http binding useless as it stands.

  2. There are 2 variations of identifiers or references shown. The first uses an ID element which can be used to construct a URI at the client. The second uses opaque URIs that the client must use as-is. It seems that it would be useful to combine the XML id attribute or an href attribute with a URI to combine the xml identifier and URI schemes. However, fragment IDs can't be used for server-side secondary resources as the fragment id is illegal in HTTP syntax. Therefore some other syntax, such as query string or URIs would be needed. I tried to show 2 cases, where there was an ID element that the wsdl binding said could be used to generate URIs (client generated URIs) and case where the server generated the URI (opaque URIs). WSDL can't model the typical case of IDs as attributes. There seems to be a need for 2 types of identifiers, opaque and transparent.

  3. In addition to interacting with the resources, it is often desirable to interact with the secondary resources (ie fragments). In general, the Identifier for properties becomes problematic. I've called this the "server-side secondary resource identifier" problem, as we can't use frag-ids when we want to send/receive just the property. The general question of how to specify CRUD methods on properties of resources, ie the Artist things, remains unclear. I created a number of structures for Properties: ArtistIDField to map an ID+fieldname to URI, fieldNameAndValue for updates. I don't think this is expressible in WSDL as the ID can't go in the body.

  4. It is unclear how to model opaque URIs, where the address is not known at WSDL time, from a WSDL perspective. Should the "location" be left blank in the operation and service? Should all of the operations that take in an opaque URI be modeled in a separate interface and then have another service without a location?

  5. Multiple namespace support, particularly Qnames is difficult. It's all well and good if things are in same ns, but then they get hairy if there's multiple ns. I've suggested a variety of Qname to URI mapping schemes.

  6. The goal of using a schema to manage a URI space, that is an XML tree maps to a URI tree, seems hard to achieve as it doesn't seem possible to simply(!) extend a schema and have the URI space that is defined by the schema + WSDL location attributes automagically be updated. If I add another field to Artist, I have to update about 5 different schemas. If I had a sibling to Artist, say Albums, there's no re-use of the operations.

  7. How to describe the situation where part of the message is in the URI and all of it is in the body, specifically a PUT. When generating a URI from an instance, all the contents of the instance are used. But in the case of PUT, only the identifier should go into the URI, not the rest of the instance.
  8. Specifying the exact hierarchical structure that is returned for a given query is complicated. Imagine Music has Artists that have Albums, and I find Albums that were made in 1994. Does that return * or * or *? NodeList is an container invented to provide a root node for returning "leaf nodes". I imagine for each type of return hierarchy, I would need to specify a different URI path component and operation.

  9. Most RESTful Web services, such as Atom and WebDAV, use the HTTP content-location, for the results of PUTS and POSTS. We do not have an accepted way of modelling that this "application data" is bound to an HTTP header. It's at least required that an HTTP header can be described in WSDL, and it would be very useful if the "output" could refer to this directly.

  10. Resource property (aka secondary resources) administration seems problematic. Can properties be "deleted", especially required properties? What happens when a property is added, but it already exists? Is it really an "add if not exist otherwise replace?". If the property type is a list, how is a particular item in the list identified? If I delete a node that is Nillable, is it set to Nil? Is this getting much to close to WebDAV and/or WS-ResourceFramework?

Schema

<?xml version="1.0" encoding="UTF-8"?> <xs:schema targetNamespace="http://www.w3.org/2002/ws/music/2004/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.w3.org/2002/ws/music/2004/" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Music"> <xs:complexType name="Music"> <xs:sequence> <xs:element ref="tns:Artist" maxOccurs="unbounded"/> <xs:element ref="tns:ArtistNameRef" maxOccurs="unbounded"/> <xs:any namespace="##other" processContents="lax"/> </xs:sequence> </xs:complexType> </xs:element> <!-- When Adding an artist, the ID isn't known --> <xs:element name="ArtistWithoutID"> <xs:complexType> <xs:sequence> <xs:element name="Name" type="xs:string"/> <xs:element name="Biography" type="xs:string"/> <xs:element name="Genre" type="xs:string"/> <xs:element name="Image" type="xs:anyURI"/> <xs:element name="Site" type="xs:anyURI"/> <xs:element name="Rating" type="xs:decimal" minOccurs="0"/> <xs:any namespace="##other" processContents="lax"/> </xs:sequence> </xs:complexType> </xs:element> <!-- Need to have ID + all of the fields for <artist> returns --> <!-- probably better modelling in schema to combine the id with the non-id fields --> <!-- To do a /Music/Artist/5, need to have a type with only 1 child --> <xs:element name="ArtistID" type="tns:ArtistIDBase"/> <xs:complexType name="ArtistIDBase"> <xs:sequence> <xs:element name="id" type="xs:ID"/> </xs:sequence> </xs:complexType> <xs:element name="Artist"> <xs:complexType> <xs:complexContent> <xs:extension base="tns:ArtistIDBase"> <xs:sequence> <xs:element name="Name" type="xs:string"/> <xs:element name="Biography" type="xs:string"/> <xs:element name="Genre" type="xs:string"/> <xs:element name="Image" type="xs:anyURI"/> <xs:element name="Site" type="xs:anyURI"/> <xs:element name="Rating" type="xs:decimal" minOccurs="0"/> <xs:any namespace="##other" processContents="lax"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <!-- Need a query structure to do queries, ie &name=thieverycorp --> <xs:element name="ArtistQuery"> <xs:complexType> <xs:complexContent> <xs:extension base="tns:ArtistIDBase"> <xs:sequence> <xs:element name="Name" type="xs:string" minOccurs="0"/> <xs:element name="Genre" type="xs:string" minOccurs="0"/> <xs:element name="Rating" type="xs:decimal" minOccurs="0"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <!-- ID + REF structure for getting back list of IDs that can be used to create URI --> <xs:element name="ArtistIDRef"> <xs:complexType> <xs:complexContent> <xs:extension base="tns:ArtistIDBase"> <xs:sequence> <xs:element name="Name" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <!-- REF structure for getting back list of names + referencesI --> <xs:element name="ArtistNameRef"> <xs:complexType> <xs:sequence> <xs:element name="Name" type="xs:string"/> <xs:element name="Ref" type="xs:anyURI"/> </xs:sequence> </xs:complexType> </xs:element> <!-- Artist Field names for doing field queries --> <xs:element name="ArtistIDFieldName" type="tns:ArtistIDField"/> <xs:complexType name="ArtistIDField"> <xs:complexContent> <xs:extension base="tns:ArtistIDBase"> <xs:sequence> <xs:element name="Field" type="tns:ArtistFieldNames"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <!-- Artist Field Name + Value for setting field --> <xs:element name="ArtistFieldNameAndValue"> <xs:complexType name="ArtistFieldNameAndValue"> <xs:complexContent> <xs:extension base="tns:ArtistField"> <xs:sequence> <xs:element name="Value"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <!-- List of field names --> <xs:simpleType name="ArtistFieldNames"> <xs:restriction base="xs:string"> <xs:enumeration value="Name"/> <xs:enumeration value="Biography"/> <xs:enumeration value="Genre"/> <xs:enumeration value="Image"/> <xs:enumeration value="Site"/> </xs:restriction> </xs:simpleType> <!-- URI for getting back opaque URIs --> <xs:complexType name="uri"> <xs:sequence> <xs:element name="uri" type="xs:anyURI"/> </xs:sequence> </xs:complexType> </xs:schema>

<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions xmlns:musicw="http://example.com/Artist" xmlns:wsdl=""> <xs:import href="Music-Artist.xsd"/> <wsdl:interface name="Artist"> <!-- GET /Music/Artist?genre="electronica" --> <!-- GET /Music/Artist --> <wsdl:operation name="ArtistSearch" method="GET"> <wsdl:input ref="music:ArtistQuery"/> <wsdl:output ref="music:Music"/> </wsdl:operation> <!-- GET /Music/ArtistNameRef?genre="electronica" --> <wsdl:operation name="ArtistNameRefSearch" method="GET"> <wsdl:input ref="music:ArtistQuery"/> <wsdl:output ref="music:Music"/> </wsdl:operation> <!-- GET /Music/ArtistIDRef?genre="electronica"--> <wsdl:operation name="ArtistIDRefSearch" method="GET"> <wsdl:input ref="music:ArtistQuery"/> <wsdl:output ref="music:Music"/> </wsdl:operation> <!-- GET /Music/Artist/5 --> <wsdl:operation name="GetArtist" method="GET"> <wsdl:input ref="music:ArtistID"/> <wsdl:output ref="music:Artist"/> </wsdl:operation> <!-- Given a uri in a field for an artist, simply dereference the URI --> <!-- GET /Music/Artist/id/zyz --> <wsdl:operation name="GetArtistOpaqueURI" method="GET"> <wsdl:input ref="music:uri"/> <wsdl:output ref="music:Artist"/> </wsdl:operation> <!-- GET /Music/Artist/5?field="site" --> <wsdl:operation name="GetFieldByFieldName" method="GET"> <wsdl:input ref="music:ArtistFieldName"/> <wsdl:output ref="xsd:any"/> </wsdl:operation> <!-- POST /Music/Artist --> <wsdl:operation name="AddArtistServerID" method="POST"> <wsdl:input ref="music:ArtistWithoutID"/> <wsdl:output ref="music:ArtistRef"/> </wsdl:operation> <!-- POST /Music/Artist/7 --> <wsdl:operation name="AddArtistClientID" method="POST"> <wsdl:input ref="music:Artist"/> <wsdl:output ref="music:ArtistRef"/> </wsdl:operation> <!-- DELETE /Music/Artist/5 --> <wsdl:operation name="DeleteArtist" method="DELETE"> <wsdl:input ref="music:ArtistID"/> </wsdl:operation> <!-- PUT /Music/Artist/5 --> <wsdl:operation name="UpdateArtist" method="PUT"> <wsdl:input ref="music:Artist"/> <wsdl:output ref="music:Artist"/> </wsdl:operation> <!-- PUT /Music/Artist/id/xyz --> <wsdl:operation name="UpdateArtistWithoutID" method="PUT"> <wsdl:input ref="music:ArtistWithoutID"/> <wsdl:output ref="music:Artist"/> </wsdl:operation> <!-- PUT /Music/Artist/5/site --> <wsdl:operation name="UpdateField" method="PUT"> <wsdl:input ref="music:ArtistFieldNameAndValue"/> </wsdl:operation> </wsdl:interface> <wsdl:binding name="Artist" interface="Artist" http:defaultMethod="GET"> <!-- GET /Music/Artist?genre="electronica" --> <!-- GET /Music/Artist --> <wsdl:operation ref="ArtistSearch" location="Artist/{Name}"/> <!-- GET /Music/ArtistIDRef?genre="electronica"--> <wsdl:operation ref="ArtistNameIDSearch" location="ArtistNameID/{Name}"/></p> <!-- GET /Music/ArtistNameRef?genre="electronica" --> <wsdl:operation ref="ArtistNameRefSearch" location="ArtistNameRef/{Name}"/> <!-- GET /Music/Artist/5 --> <wsdl:operation ref="GetArtist" location="Artist/{ID}"/> <!-- GET /Music/Artist/id/zyz --> <wsdl:operation ref="GetArtistOpaqueURI" location="{uri}"/> <!-- GET /Music/Artist/5?field="site" --> <wsdl:operation ref="GetFieldByFieldname" location="Artist/{ID}/"/> <!-- POST /Music/Artist --> <wsdl:operation ref="AddArtistServerID" location="Artist" method="POST"/> <!-- POST /Music/Artist/7 --> <wsdl:operation ref="AddArtistClientID" location="Artist" method="POST"/> <!-- DELETE /Music/Artist/5 --> <wsdl:operation ref="DeleteArtist" location="Artist/{ID}" method="DELETE"/> <!-- PUT /Music/Artist/5 --> <wsdl:operation ref="UpdateArtistWithID" location="Artist/{ID}" method="PUT"/> <!-- PUT /Music/Artist/id/xyz --> <wsdl:operation ref="UpdateArtistWithoutID" location="Artist/{ID}" method="PUT"/> <!-- PUT /Music/Artist/5/site --> <wsdl:operation ref="UpdateArtistField" location="Artist/{ID}" method="PUT"/> </wsdl:binding> <wsd:service name="Music" binding="Artist" location="http://www.example.org/Music"/> </wsdl:definitions>

Sample Messages

Example: ArtistSearch with 1 parameter

GET /Music/Artist?genre="electronica" Return: <Music> <Artist> <ID>5</ID> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://thievery.com</site> </Artist> </Music>

Example: ArtistSearch with no parameters, ie all.

GET /Music/Artist Returns: <Music> <Artist> <ID>5</ID> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://thievery.com</site> </Artist> </Music>

Example: ArtistNameRefSearch

GET /Music/ArtistNameRef?genre="electronica" Return: <Music> <ArtistNameRef> <Name>Thievery Corp</Name> <Ref>http://example.com/Music/Artist/id/xyz</Ref> </ArtistRef> </Music>

Example: ArtistIDRefSearch

GET /Music/ArtistIDRef?genre="electronica" Return: <Music> <ArtistNameRef> <ID>5</ID> <Name>Thievery Corp</Name> </ArtistRef> </Music>

Example: GetArtist

GET /Music/Artist/5 Return: <Artist> <ID>5</ID> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://thievery.com</site> </Artist>

Example: GetArtistOpaqueURI

* This assumes the URI="id/xyz" GET /Music/Artist/id/zyz Return: <Artist> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://thievery.com</site> </Artist>

Example: GetFieldByFieldName

GET /Music/Artist/5?field="site" Return: <site>http://thievery.com</site>

Example: AddArtistServerId

POST /Music/Artist <Artist> <Name>Thievery Corp version 2</Name> <Genre>Electronica</Genre> <site>http://newbetterthievery.com</site> </Artist> Returns: Content-Location: /Music/Artist/6 * How is the HTTP Content-location mapped to xml in this case? Is it dropped? Should there be XML returned?

Example: AddArtistClientID

* Illegal as the entire Artist will go into the URI POST /Music/Artist/7 <Artist> <ID>7</ID> <Name>Thievery Corp version 3</Name> <Genre>Electronica</Genre> <site>http://newwaybetterthievery.com</site> </Artist> Returns: Content-Location: /Music/Artist/7

Example: DeleteArtist

DELETE /Music/Artist/5

Example: UpdateArtist

* Illegal as the entire Artist will go into the URI PUT /Music/Artist/5 <Artist> <ID>5</ID> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://newbetterthievery.com</site> </Artist>

Example: UpdateArtistWithoutID

* illegal as the opaque URI can't be used PUT /Music/Artist/id/xyz <Artist> <Name>Thievery Corp</Name> <Genre>Electronica</Genre> <site>http://newbetterthievery.com</site> </Artist>

Example: UpdateArtistField

* Illegal as the ID can't get mapped into the body PUT /Music/Artist/5/site <site>http://newbetterthievery.com</site>

References

WSDL 2.0 Part 1

WSDL 2.0 Part 3

REST wiki

Mark N's Description format for REST

Generative Naming

Operation names in WSDL

Binding QNames to URIs