
SOA Made Real
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 |
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.
- Upholstery Cleaners Los Angeles 1-323-678-2704
2009-06-30 17:59:31 carpetcare - 123 Carpet Cleaning Los Angeles 1-323-678-2704
2009-06-11 14:57:11 whats - 123 Carpet Cleaning Los Angeles 1-323-678-2704
2009-06-11 14:57:09 whats - Best Book on SOA Security
2006-07-27 07:55:13 dbrainar - Best Book on SOA Security
2006-08-12 19:18:43 SOANetworkArchitect - Best Book on SOA Security
2007-01-26 04:36:36 johnvixen - Best Book on SOA Security
2006-08-04 00:35:22 ramrs - Best Book on SOA Security
2007-01-26 04:39:11 johnvixen - Best Book on SOA Security
2008-04-12 16:37:16 Laura.Nguyen - Best Book on SOA Security
2008-12-27 04:27:38 safieh - userid or distance can be omitted.
2005-06-14 16:58:12 ricka - Spurious Redundancy
2005-05-26 09:32:02 SparHawk - Spurious Redundancy
2005-06-01 10:27:54 Rich Salz