Menu

SOA Made Real

May 18, 2005

Rich Salz

In my previous column I presented a style of writing WSDL files that, when followed, resulted in reusable components and a nice service-oriented architecture. Pedants may complain that I was essentially proposing rpc/literal rather than doc/literal by requiring that every request and response had a name determined algorithmically from the operation name. There are two responses to this: first, since you can have attributes it's not RPC; second, who cares?

In this column we'll actually build a web service using the style, as well as the examples, from the previous column. Again, we'll use Norm Walsh's where in the world service. For grins, we'll look at a C#/.NET client and a Python server.

Getting Started

The .NET stub generator is called, interestingly enough, wsdl. The first step is to stitch together the fragments from last time and build a single WSDL file that we can feed into the wsdl program. The only thing missing is the schema definition. As I first discussed in September of 2003, use the "Russian Doll" pattern, which eschews the XSD type system for inlining definitions. Also, reuse structures based on element name, not type.

Looking at the nearby operation, we see the following "calling sequence":

nearby(

  xs:string userid,

  xs:string units = "mi",

  xs:decimal distance = 50.0)

This isn't a C++-like description, since either or both of userid or distance can be omitted. This description, using the above style, results in the following schema declaration:

<xsd:element name="nearby">

  <xsd:complexType>

    <xsd:sequence>

      <xsd:element name="userid" type="xsd:string"/>

      <!-- units defaults to "mi" -->

      <xsd:element name="units" type="xsd:string" minOccurs="0"/>

      <!-- distance defaults to "50" -->

      <xsd:element name="distance" type="xsd:decimal" minOccurs="0"/>

    </xsd:sequence>

  </xsd:complexType>

</xsd:element>

This is, admittedly, verbose, with nine lines of definition. The return value, which is an array of a complex type, is almost twice as long, and pretty heavy on the XSD-specific overhead:

<xsd:element name="nearby-response">

  <xsd:complexType>

    <xsd:sequence>

      <xsd:element name="landmark">

        <xsd:complexType>

          <xsd:sequence>

            <xsd:element name="name" type="xsd:string"/>

            <xsd:element name="latitude" type="xsd:float"/>

            <xsd:element name="longitude" type="xsd:float"/>

            <xsd:element name="distance" type="xsd:decimal"/>

          </xsd:sequence>

        </xsd:complexType>

      </xsd:element>

    </xsd:sequence>

  </xsd:complexType>

</xsd:element>

Just looking at those first half-dozen lines is enough to make you think of the Monty Python "spam" sketch. In defense, I can only point out that it's pretty boilerplate, and that you can pull out the landmark definition into its own element declaration, and use the ref attribute:

<xsd:element ref="tns:landmark" minOccurs="0" maxOccurs="unbounded"/>

Which method you use is a matter of taste. I typically inline the element if it's only used once.

Putting it all together, a complete WSDL can be found in listing 1.

Simple, elegant, unsupported

So if we feed our first WSDL into wsdl, we get

Error: Unable to import binding '&COMPONENT;-binding' from namespace '&BASE;'.

  - Unable to import operation 'nearby-operation'.

  - The element '&BASE;:nearby' is missing.

It turns out that my nice little scheme to use DTD's and ENTITY declaration, so that I only have to write things like the namespace URI once, and so that I can make a skeleton WSDL and edit a few lines at the beginning before filling in the details...isn't supported. In fact, just having a DOCTYPE with a few entities (even if they're never used) gives the .NET wsdl tools fits

I don't recall the WS-I profile saying you shouldn't use a DTD in a WSDL file.

So we have to edit the code from listing 1 and replace the entities with their values. If you do that, wsdl does the right thing, generating a C# client stub. The stub turns the "where in the world" service into a class and each operation becomes a method, which is pretty standard (although it's pretty nice when coupled with VisualStudio's auto-completion features).

Since our operations all take a sequence, wsdl also creates an object for each input and output message. Putting them together, we can write code like this:

witw b = new witw();

b.Url = "http://os360.datapower.com:4444/test/server";

@is i = new @is();

i.userid = "rsalz";

isresponse ir = b.isoperation(i);

Notice how you can change where the server is by setting the Url property on the binding, b. Also note that wsdl appends response or operation to distinguish among request data, response value, and operation.

For those who don't know C#, the atsign is a token-level quoting character. In this case is is a C# keyword, so you have to write it as @is. You will almost definitely face this kind of issue when creating your own services -- no doubt you'll end up using an identifier that's a keyword in some language, somewhere. Hopefully the WSDL toolkit(s) for the language in conflict will have a mapping mechanism. If not, or if it's too ugly (by some metric), you'll have to edit your service description. Using mixedCase or under_scores will probably avoid that kind of problem.

Messy, messy

Let's look at the atlandmark operation. Its allows a user to specify that they are at a particular landmark. (For example, is everyone in your dinner party over at Fry's Electronics?) The function definition is this:

atlandmark(

  xs:string userid,

  xs:string landmark)

But this is what you end up having to write as code:

atlandmark l = new atlandmark();

l.landmark = "Fry's Electronics";

l.userid = "rsalz";

b.atlandmarkoperation(l);

That's a hell of a lot more ugly than the obvious:

b.atlandmark("Fry's Electronics", "rsalz");

More from Rich Salz

SOA Made Simple

The xml:id Conundrum

Freeze the Core

WSDL 2: Just Say No

XKMS Messages in Detail

What happened? Unfortunately, it's fallout from the way I wrote the WSDL. Every operation has a single input parameter and a single output parameter. That means your language bindings have to create "objects", or at least containers, to hold what would be the bits of XML on the wire. That's kind of ugly, but it's the dirty little secret of SOA: your system will be better off, but you will have to write more code.

If we compile and run the code (available in listing 2), and capture what it sends, we'll see that things make sense (I pretty-printed the message; it was sent as a single long line):

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

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <soap:Body>

    <atlandmark xmlns="http://www.example.org/witw">

      <userid xmlns="">rsalz</userid>

      <landmark xmlns="">Tidepool</landmark>

    </atlandmark>

  </soap:Body>

</soap:Envelope>

Sure enough, we're shipping around a clean bit of XML. Granted, there are spurious (and redundant) namespace declarations there, but that's a small nit. (Bonus points if you can figure out which attribute in the schema definition caused the innermost empty namespace declarations.) And look, it's SOAP 1.1!

Next time we'll look at the server and discuss the joys of method dispatch.