SOA Made Simple
March 30, 2005
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 |
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.