Menu

SOA Made Simple

March 30, 2005

Rich Salz

Regular readers of this column might notice that I've spent a lot of time on WSDL. There's a reason for this: after XML and SOAP, I firmly believe that service description is the most important component of designing, building, and deploying heterogeneous web services.

There are a couple of reasons why this is true. First, it is a precise description of what the bits on the wire will be. Without that, it is exceedingly difficult for distributed applications to talk to each other. It's still possible, and great things can be made — SAMBA is a tour de force of reverse engineering — but that's not really how you want your customers, partners, or colleagues to be spending their development time.

Second, if you have the luxury of time and resources — not to mention the managerial foresight — to be able to first design your network interfaces before developing the code behind them, then the service description will be the first thing you will write. The phrase "contract-first" seems to be increasingly bandied about as the official jargon to use here.

Even if you're wrapping a legacy application, you'll need a service description that other web services applications can use. Of course, there are many data-binding tools available that are, for example, capable of turning a Java class into an XML Schema or a Windows-compatible wizard with a "generate WSDL" button. But even then, you'll have to give those descriptions to others, the tools may have bugs (surprising, I know, but it's been known to happen), or you'll need to hand-tweak the generated files because a particular customer "just wants the namespace or URL changed." At that point, you don't want to have to burn the midnight oil with a copy of the WSDL and Schema specs in hand and the generated WSDL file on your screen.

Instead, following a few simple style rules can make your service description — the combination of Schema and WSDL — pretty boilerplate. The results should be pretty WS-I compliant, follow a service-oriented architecture, and be all-around buzzword compliant.

Naming

First, you have to decide on a couple of names. You need

  • A namespace URI for your schema
  • A namespace URI for your WSDL
  • A "simple" name for your service

Note that the first two can be the same. I really like the convention of ending a namespace with a pound sign, because items within the namespace can use the URL fragment notation. Even if you don't leverage that, and just put an HTML file at the URL with an appropriately-named anchor, I just think it's a neat hack.

I suppose that, politically, the URI's should really be International Resource Identifiers (IRIs), but SOAP 1.1 and WSDL 1.0 aren't defined for IRIs. To accommodate international names, the IRI RFC defines how to map IRIs into URIs, and you should do that.

In the fragments below, I'll use http://www.example.org/service# as the URI and sample as the service name.

Skeleton WSDL

Let's lay out the skeleton WSDL file. I use DTD entities as a simple "#define", which lets me re-use the same skeleton. It's also very convenient. I always use xmlns:tns as a namespace prefix for the targetNamespace of whatever file I'm writing.


<?xml version="1.0" encoding="utf-8"?>



<!DOCTYPE definitions [

<!ENTITY BASE 'http://www.example.org/service#'>

<!ENTITY COMPONENT 'service'>

<!ENTITY SERVICE 'service'>

<!ENTITY SERVICEURl 'http://ws.example.com/service/1.0'>

]>





<definitions name="&SERVICE;"

  xmlns="http://schemas.xmlsoap.org/wsdl/"

  xmlns:soapbind="http://schemas.xmlsoap.org/wsdl/soap/"

  xmlns:tns="&BASE;" targetNamespace="&BASE;">



  <documentation>

    This code is in the public domain.

  </documentation>



  < Obviously, the value for @location is wrong! -->

  <import namespace="&BASE;"

    location='http://www.example.org/schema/sample.xsd'/>



  <!--

  Message definitions will go here.

  -->



  <portType name="&COMPONENT;-porttype">

    <!--

    Operation definitions will go here.

    -->

  </portType>





  <binding name="&COMPONENT;-binding" type="tns:&COMPONENT;-porttype">

    <soapbind:binding style="document"

      transport="http://schemas.xmlsoap.org/soap/http"/>

    <!--

    Bound operations will go here.

    -->

  </binding>



  <service name="&SERVICE">

    <port name="&COMPONENT-port" binding="tns:&COMPONENT;-binding">

    <soapbind:address location="&SERVICEURL;"/>

  </port>



  </service>



</definitions>

Within WSDL, each part of the file is in a separate namespace, just like the same tag names within a C struct can exist within different structures. If you want, you can remove all the -binding, etc., suffixes from the outline above. I tried that once and it made things too hard for me to follow; a little redundancy is sometimes both good and useful.

Another thing that's very useful about this outline is that you can follow the template for several different schemas, ports, and bindings, specifying a single service to implement them. If you do this, put the schema, message, portType, and binding elements each in their own file. Your top-level service description should just import each separate component, and then only needs to have multiple port entries within the service element. This seems like very straightforward way to get re-use.

Now it's time to define the operations. We'll only define one here; we'll take the nearby operation from Norm Walsh's where in the world service. There's very little thinking involved; it's all boilerplate.

We'll work from the bottom (or end of the WSDL file) up. First, we'll be exchanging literal XML, so we define the bound operation:


<operation name="nearby-operation">

  <soapbind:operation style="document"/>

  <input name="nearby-oprequest">

    <soapbind:body use="literal"/>

  </input>

  <output name="nearby-opresponse">

    <soapbind:body use="literal"/>

  </output>

</operation>

Note that we're defining a very simple request/response operation that exchanges messages whose names are pretty formulaic.

It turns out that the messages themselves are pretty formulaic, too. The following fragment goes in the portType element:


<operation name="nearby-operation">

  <input name="nearby-oprequest" message="tns:nearby-request"/>

  <output name="nearby-opresponse" message="tns:nearby-response"/>

</operation>

Now we need to define the actual messages. For the first time, we need to think about the data. We want to avoid using a type system and instead concentrate on what the data looks like. This means using the element name, rather than type, to describe the message. While it admittedly looks a bit too RPC-ish, I often use the following structure for defining messages:


<message name="nearby-request">

  <part name="body" element="tns:nearby"/>

</message>

<message name="nearby-response">

  <part name="body" element="tns:nearby-response"/>

</message>

More from Rich Salz

SOA Made Real

The xml:id Conundrum

Freeze the Core

WSDL 2: Just Say No

XKMS Messages in Detail

This has a couple of ramifications. First, you could write your schema in RelaxNG and use Trang to convert it to XSD. Second, if the same element appears in multiple operations, you'll have to define wrapper elements that are operation-specific; this is admittedly kind of bogus. Third, your service is more likely to be future-proof, since the core definition of the nearby element might be expanded, and your code should still work. (That depends on the semantics of the expansion, of course.)

It turns out that for the common case — exchanging SOAP messages in a request/response exchange pattern — WSDL 1.0 is both flexible and verbose. It's probably quite feasible to define your own service language and then use XSLT to generate the WSDL.