Archive for the ‘Web Services’ Category

All-encompassing SOA reference

Wednesday, October 4th, 2006

Sometimes, people ask me for a good reference on Service-Oriented Architectures. So far, I’ve been reluctant to share my Tome of Knowledge, but since Jason van Zyl caught me reading it during JavaZone, I might as well publish the evidence here…

All kidding aside, the booklet is surprisingly good! :-)

Update: more pics from the Justice League of SOA.

XSLT that transforms from XSD to WSDL

Thursday, July 27th, 2006

I don’t like writing WSDLs. I think they force a wrong model. Unfortunately, they are the standard. I do like contract-first Web service. I think the XML messages, and therefore the XSD, are most important. So, what I usually do when creating a WSDL

  1. start with the XML messages,
  2. create or infer a schema from that,
  3. copy all the defined elements in messages,
  4. create operations from the messages
  5. create a portType from the operations
  6. create a binding from the portType
  7. create a service from the binding
Step 2 requires some manual tuning, but steps 3-7 are pretty tedious, so I wondered if I couldn’t automate it in some way. Craig Walls also wondered about this.

I started out writing this in Java, but that got even more tedious. After some though, it seemed like perfect opportunity to use an XSLT stylesheet, since we have XML coming in (the schema), and XML coming out (the WSDL). Basically, the idea is to iterate over all elements that end with a certain suffix (’Request’ by default), and to create operations based on that. So if you have a GetHolidayRequest element in the schema, a GetHolidayRequestMessage is created, used by a GetHoliday operation.

Note that it is a XSLT 2.0 stylesheet, because I needed some of the new functionality (most importantly the new <xsl:namespace/> tag). You will need an XSLT 2.0 processor, like Saxon, to run it. As far as I know, Xalan doesn’t do XSLT 2.0 (yet). Also note that I’m no XSLT expert, so there are probably some improvements to be made. Please give them in the comments! The XSLT will eventually end up in Spring Web Services, so that you can dynamically create the WSDL based on your XSD.

Without further ado, here it is:

<!--
  ~ Copyright 2006 Arjen Poutsma.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <!-- Parameters -->
    <!-- Name for the WSDL definition, which is also uses as a prefix for portType, binding, and service -->
    <xsl:param name="name"/>
    <!-- Location where the service can be found -->
    <xsl:param name="location"/>
    <!-- Namespace of the schema -->
    <xsl:param name="schemaNamespace" select="/xsd:schema/@targetNamespace"/>
    <!-- Prefix for namespace of the schema -->
    <xsl:param name="schemaPrefix" select="'schema'"/>
    <!-- WSDL Namespace, by default the same as the schema namespace -->
    <xsl:param name="targetNamespace" select="$schemaNamespace"/>
    <!-- Prefix for WSDL -->
    <xsl:param name="prefix" select="'tns'"/>
    <xsl:param name="requestSuffix" select="'Request'"/>
    <xsl:param name="responseSuffix" select="'Response'"/>
    <xsl:param name="messageSuffix" select="'Message'"/>
    <xsl:param name="inputSuffix" select="'Input'"/>
    <xsl:param name="outputSuffix" select="'Output'"/>
    <xsl:param name="portTypeSuffix" select="'PortType'"/>
    <xsl:param name="bindingSuffix" select="'Binding'"/>
    <xsl:param name="serviceSuffix" select="'Service'"/>
    <xsl:param name="portSuffix" select="'Port'"/>
    <xsl:param name="soapActionPrefix" select="$targetNamespace"/>

&lt;xsl:variable name=&quot;elements&quot; select=&quot;/xsd:schema/xsd:element&quot;/&gt;

&lt;!-- Elements that end with the request suffix, keyed under operation name --&gt;
&lt;xsl:key name=&quot;requestElements&quot; match=&quot;/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]&quot;
         use=&quot;substring-before(@name,$requestSuffix)&quot;/&gt;
&lt;!-- Elements that end with the response suffix, keyed under operation name --&gt;
&lt;xsl:key name=&quot;responseElements&quot; match=&quot;/xsd:schema/xsd:element[ends-with(@name, $responseSuffix)]&quot;
         use=&quot;substring-before(@name,$responseSuffix)&quot;/&gt;

&lt;!-- Global template --&gt;
&lt;xsl:template match=&quot;/&quot;&gt;
    &lt;xsl:call-template name=&quot;definitions&quot;/&gt;
&lt;/xsl:template&gt;

&lt;!-- WSDL definitions --&gt;
&lt;xsl:template name=&quot;definitions&quot;&gt;
    &lt;wsdl:definitions&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;$name&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;xsl:namespace name=&quot;{$schemaPrefix}&quot; select=&quot;$schemaNamespace&quot;/&gt;
        &lt;xsl:namespace name=&quot;{$prefix}&quot; select=&quot;$targetNamespace&quot;/&gt;
        &lt;xsl:attribute name=&quot;targetNamespace&quot;&gt;
            &lt;xsl:value-of select=&quot;$targetNamespace&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;xsl:call-template name=&quot;types&quot;/&gt;
        &lt;xsl:for-each select=&quot;$elements&quot;&gt;
            &lt;xsl:call-template name=&quot;message&quot;&gt;
                &lt;xsl:with-param name=&quot;elementName&quot;&gt;
                    &lt;xsl:value-of select=&quot;@name&quot;/&gt;
                &lt;/xsl:with-param&gt;
            &lt;/xsl:call-template&gt;
        &lt;/xsl:for-each&gt;
        &lt;xsl:call-template name=&quot;portType&quot;/&gt;
    &lt;/wsdl:definitions&gt;
&lt;/xsl:template&gt;

&lt;!-- WSDL types  --&gt;
&lt;xsl:template name=&quot;types&quot;&gt;
    &lt;wsdl:types&gt;
        &lt;xsl:copy-of select=&quot;/xsd:schema&quot;/&gt;
    &lt;/wsdl:types&gt;
&lt;/xsl:template&gt;

&lt;!-- WSDL Message --&gt;
&lt;xsl:template name=&quot;message&quot;&gt;
    &lt;xsl:param name=&quot;elementName&quot;/&gt;
    &lt;wsdl:message&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;concat($elementName,$messageSuffix)&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;wsdl:part name=&quot;body&quot;&gt;
            &lt;xsl:attribute name=&quot;element&quot;&gt;
                &lt;xsl:value-of select=&quot;concat($schemaPrefix,&apos;:&apos;,$elementName)&quot;/&gt;
            &lt;/xsl:attribute&gt;
        &lt;/wsdl:part&gt;
    &lt;/wsdl:message&gt;
&lt;/xsl:template&gt;

&lt;!-- WSDL Port type --&gt;
&lt;xsl:template name=&quot;portType&quot;&gt;
    &lt;xsl:variable name=&quot;portTypeName&quot; select=&quot;concat($name, $portTypeSuffix)&quot;/&gt;
    &lt;wsdl:portType&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;$portTypeName&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;!-- Find all elements that end with $requestSuffix, and start an operation for that  --&gt;
        &lt;xsl:for-each select=&quot;/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]&quot;&gt;
            &lt;xsl:call-template name=&quot;portTypeOperation&quot;&gt;
                &lt;xsl:with-param name=&quot;operationName&quot;&gt;
                    &lt;xsl:value-of select=&quot;substring-before(@name,$requestSuffix)&quot;/&gt;
                &lt;/xsl:with-param&gt;
            &lt;/xsl:call-template&gt;
        &lt;/xsl:for-each&gt;
    &lt;/wsdl:portType&gt;
    &lt;xsl:call-template name=&quot;binding&quot;&gt;
        &lt;xsl:with-param name=&quot;portTypeName&quot; select=&quot;$portTypeName&quot;/&gt;
    &lt;/xsl:call-template&gt;
&lt;/xsl:template&gt;

&lt;xsl:template name=&quot;portTypeOperation&quot;&gt;
    &lt;xsl:param name=&quot;operationName&quot;/&gt;
    &lt;wsdl:operation&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;$operationName&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;xsl:for-each select=&quot;key(&apos;requestElements&apos;, $operationName)&quot;&gt;
            &lt;wsdl:input&gt;
                &lt;xsl:attribute name=&quot;name&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($operationName, $inputSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
                &lt;xsl:attribute name=&quot;message&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($prefix,&apos;:&apos;,@name,$messageSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
            &lt;/wsdl:input&gt;
        &lt;/xsl:for-each&gt;
        &lt;xsl:for-each select=&quot;key(&apos;responseElements&apos;, $operationName)&quot;&gt;
            &lt;wsdl:output&gt;
                &lt;xsl:attribute name=&quot;name&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($operationName, $outputSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
                &lt;xsl:attribute name=&quot;message&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($prefix,&apos;:&apos;,@name,$messageSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
            &lt;/wsdl:output&gt;
        &lt;/xsl:for-each&gt;
    &lt;/wsdl:operation&gt;
&lt;/xsl:template&gt;

&lt;xsl:template name=&quot;binding&quot;&gt;
    &lt;xsl:param name=&quot;portTypeName&quot;/&gt;
    &lt;xsl:variable name=&quot;bindingName&quot; select=&quot;concat($name, $bindingSuffix)&quot;/&gt;
    &lt;wsdl:binding&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;$bindingName&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;xsl:attribute name=&quot;type&quot;&gt;
            &lt;xsl:value-of select=&quot;concat($prefix,&apos;:&apos;,$portTypeName)&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;wsdlsoap:binding style=&quot;document&quot; transport=&quot;http://schemas.xmlsoap.org/soap/http&quot;/&gt;
        &lt;!-- Find all elements that end with $requestSuffix, and start an operation for that  --&gt;
        &lt;xsl:for-each select=&quot;/xsd:schema/xsd:element[ends-with(@name, $requestSuffix)]&quot;&gt;
            &lt;xsl:call-template name=&quot;bindingOperation&quot;&gt;
                &lt;xsl:with-param name=&quot;operationName&quot;&gt;
                    &lt;xsl:value-of select=&quot;substring-before(@name,$requestSuffix)&quot;/&gt;
                &lt;/xsl:with-param&gt;
            &lt;/xsl:call-template&gt;
        &lt;/xsl:for-each&gt;
    &lt;/wsdl:binding&gt;
    &lt;xsl:call-template name=&quot;service&quot;&gt;
        &lt;xsl:with-param name=&quot;bindingName&quot; select=&quot;$bindingName&quot;/&gt;
    &lt;/xsl:call-template&gt;
&lt;/xsl:template&gt;

&lt;xsl:template name=&quot;bindingOperation&quot;&gt;
    &lt;xsl:param name=&quot;operationName&quot;/&gt;
    &lt;wsdl:operation&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;$operationName&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;wsdlsoap:operation&gt;
            &lt;xsl:attribute name=&quot;soapAction&quot;&gt;
                &lt;xsl:value-of select=&quot;concat($soapActionPrefix,&apos;/&apos;,$operationName)&quot;/&gt;
            &lt;/xsl:attribute&gt;
        &lt;/wsdlsoap:operation&gt;
        &lt;xsl:for-each select=&quot;key(&apos;requestElements&apos;, $operationName)&quot;&gt;
            &lt;wsdl:input&gt;
                &lt;xsl:attribute name=&quot;name&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($operationName, $inputSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
                &lt;wsdlsoap:body use=&quot;literal&quot;/&gt;
            &lt;/wsdl:input&gt;
        &lt;/xsl:for-each&gt;
        &lt;xsl:for-each select=&quot;key(&apos;responseElements&apos;, $operationName)&quot;&gt;
            &lt;wsdl:output&gt;
                &lt;xsl:attribute name=&quot;name&quot;&gt;
                    &lt;xsl:value-of select=&quot;concat($operationName, $outputSuffix)&quot;/&gt;
                &lt;/xsl:attribute&gt;
                &lt;wsdlsoap:body use=&quot;literal&quot;/&gt;
            &lt;/wsdl:output&gt;
        &lt;/xsl:for-each&gt;
    &lt;/wsdl:operation&gt;
&lt;/xsl:template&gt;

&lt;xsl:template name=&quot;service&quot;&gt;
    &lt;xsl:param name=&quot;bindingName&quot;/&gt;
    &lt;wsdl:service&gt;
        &lt;xsl:attribute name=&quot;name&quot;&gt;
            &lt;xsl:value-of select=&quot;concat($name, $serviceSuffix)&quot;/&gt;
        &lt;/xsl:attribute&gt;
        &lt;wsdl:port&gt;
            &lt;xsl:attribute name=&quot;name&quot;&gt;
                &lt;xsl:value-of select=&quot;concat($name, $portSuffix)&quot;/&gt;
            &lt;/xsl:attribute&gt;
            &lt;xsl:attribute name=&quot;binding&quot;&gt;
                &lt;xsl:value-of select=&quot;concat($prefix,&apos;:&apos;, $bindingName)&quot;/&gt;
            &lt;/xsl:attribute&gt;
            &lt;wsdlsoap:address&gt;
                &lt;xsl:attribute name=&quot;location&quot;&gt;
                    &lt;xsl:value-of select=&quot;$location&quot;/&gt;
                &lt;/xsl:attribute&gt;
            &lt;/wsdlsoap:address&gt;
        &lt;/wsdl:port&gt;
    &lt;/wsdl:service&gt;
&lt;/xsl:template&gt;

</xsl:stylesheet>

WS-Addressing needs a Phone Book

Saturday, July 22nd, 2006

I’ve been looking into WS-Addressing lately, the W3C specification that aims to provide a transport-neutral mechanisms to address Web services. The basic idea is that an incoming SOAP message contain so-called Message Information Headers in the header, and that information is used to find an Endpoint Reference that matches it. The response is then sent back using that same information. It might not even go back to the same requester, if the header contains a ReplyTo or FaultTo.

A very useful specification, right?

Well, it depends on which one you mean. Turns out there are multiple versions available. There is the March 2003 version, the March 2004 version, and the August 2004 version. Then, control was handed over to the W3C, which split it into multiple parts: a generic part, a SOAP part, and a WSDL. Perhaps they thought the original version was too easy to read. The W3C published two version, one in August 2005, and the final version in May 2006.

The funny thing is that all of these version differ not only in namespace, because that would be easy to solve, but also in minute other ways. For instance, some define RefererenceProperties as part of the EndpointReference, while other define RefererenceParameters. Some define both. The difference between these two is that one is used to identify the endpoint being conveyed, while the other are associated with the endpoint to facilitate a particular interaction. You can guess which is which.

The inexperienced developer, who is not used to WS-* specifications, might think that this is easily solved. Just implement the final, latest version! Everyone implements that one, right?

Wrong again. Nobody actually implements the final version yet, as far as I can determine. Here’s a little matrix which shows which version are implemented by which some popular SOAP stacks. One interesting thing to notice is that Microsoft doesn’t even support WSE 2.0 in WSE 3.0.

ActiveSOAP Axis 2 WSE 2.0 WSE 3.0 XFire
March 2003 X
March 2004 X
August 2004 X X X
August 2005 X X
May 2006 X
(This table was created by browsing source code, and reading documentation. I could be wrong. I hope I’m wrong. Please tell me the WS-Addressing world is better than this). (Update: Dims informs me that Axis 2.0 implements the May 2006 version. My mistake! It’s hard to figure out by looking at the code, since the August 2005 and May 2006 version actually have the same namespace: http://www.w3.org/2005/08/addressing).

There are two lessons to be learned from this, besides the regular “WS-* SUCKS” brouhaha. If anyone tells you they implement WS-Addressing, make sure they tell you which version. And if we every want to make this interoperability thing work, we’d better not write and implement five different versions before we get it right.

Tutorial: Writing Contract-first Web Services

Friday, June 9th, 2006

Over at the Spring-WS forum, people are starting to get convinced that doing contract-first Web service development is a good thing. After all, it’s all about the XML!

However, now that people have started working on their WSDLs and schema’s, a whole new range of questions come up. Mostly people are looking for advice or tutorials on writing schema’s and WSDLs. I could find any good ones, so I decided to write my own (I will leave it up to you whether this one is any good). The tutorial has three parts:

  1. the Messages
  2. the Schema
  3. the WSDL
In this post, I will use the excellent SOA methaphor provided by Dan North as a guiding scenario. You might want to read up on that. Basically, the Web service we’re going to define is a Human Resources service, to which you can send your holiday request.

Messages

The first thing to do is to forget about the programming language you use to implement the Web service. That’s right: forget about the curly braces, we’re doing angle brackets here!

Holiday

Next, we determine what the basic XML used in our service will look like. In our scenario, we have to deal with holiday request, so it makes sense to determine what a holiday looks like:

<Holiday>
  <StartDate>2006-07-03</StartDate>
  <EndDate>2006-07-07</EndDate>
</Holiday>

A holiday is just a start date and an end date. We decided to use the standard ISO 8601 date format for this, because we want to stick to the standards.

Note that there’s something missing? This isn’t 2000 anymore: namespaces are very fashionable these days. We might want to add one:

<Holiday xmlns="http://mycompany.com/holidays/schemas">
  <StartDate>2006-07-03</StartDate>
  <EndDate>2006-07-07</EndDate>
</Holiday>

Employee

We also have the notion of an employee in our scenario. Here’s what it looks like:

<Employee xmlns="http://mycompany.com/holidays/schemas">
  <Number>42</Number>
  <FirstName>Arjen</FirstName>
  <LastName>Poutsma</LastName>
</Employee>

We used the same namespace as before. If this employee could be used in other scenario’s, it might make sense to use a different namespace, such as "http://mycompany.com/employees/schemas".

HolidayRequest

We put both elements together in a HolidayRequest:

<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
  <Holiday>
    <StartDate>2006-07-03</StartDate>
    <EndDate>2006-07-07</EndDate>
  </Holiday>
  <Employee>
    <Number>42</Number>
    <FirstName>Arjen</FirstName>
    <LastName>Poutsma</LastName>
  </Employee>
</HolidayRequest>

The order of the two element doesn’t really matter: Employee could have been the first element just as well. As long as all the data is there; that’s most important.

In fact, the data is the only thing that is important: we are taking a data-driven approach here, we have not defined any behavior. As Ted Neward tells us in his excellent Effective Enterprise Java, Item 19: In general, you’ll want to prefer data-driven communications, particularly when navigating across component boundaries. Ted is a clever guy, we might want to adhere to his advice.

Schema

Now that we have decided what our data looks like, it makes sense to formalize this into a schema. Nowadays, you have four different ways of defining a grammar for XML:

DTDs are a thing of the past, let’s forget about those. Relax NG and Schematron are nice, and certainly easier than XSDs. Unfortunately, they are not so widely supported across platforms. So let’s stick with XSDs.

By far the easiest way to create the XSD is to infer it from sample documents. There is a wide variety of tools available which do this. Basically, these tools look at your sample XML documents, and generate a schema from it that validates them all. You will certainly have to polish up the resulting XSD, but it’s a great starting point.

Using the sample above, we end up with the following generated schema:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified"
    targetNamespace="http://mycompany.com/holidays/schemas"
    xmlns:holidays="http://mycompany.com/holidays/schemas">
  <xs:element name="HolidayRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:Holiday"/>
        <xs:element ref="holidays:Employee"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Holiday">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:StartDate"/>
        <xs:element ref="holidays:EndDate"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="StartDate" type="xs:NMTOKEN"/>
  <xs:element name="EndDate" type="xs:NMTOKEN"/>
  <xs:element name="Employee">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="holidays:Number"/>
        <xs:element ref="holidays:FirstName"/>
        <xs:element ref="holidays:LastName"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Number" type="xs:integer"/>
  <xs:element name="FirstName" type="xs:NCName"/>
  <xs:element name="LastName" type="xs:NCName"/>
</xs:schema>

Quite a lot of XML! Let’s try and make it simpler. The first thing to notice is that everything is a root-level element. This means that your Web service should be able to accept all of these elements as data. This is not what we want, we only want to accept a HolidayRequest for now. You can do this by removing the wrapping element tags (thus keeping the types), and inlining the results:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:holidays="http://mycompany.com/holidays/schemas"
    elementFormDefault="qualified"
    targetNamespace="http://mycompany.com/holidays/schemas">
  <xs:element name="HolidayRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Holiday" type="holidays:HolidayType"/>
        <xs:element name="Employee" type="holidays:EmployeeType"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="HolidayType">
    <xs:sequence>
      <xs:element name="StartDate" type="xs:NMTOKEN"/>
      <xs:element name="EndDate" type="xs:NMTOKEN"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="EmployeeType">
    <xs:sequence>
      <xs:element name="Number" type="xs:integer"/>
      <xs:element name="FirstName" type="xs:NCName"/>
      <xs:element name="LastName" type="xs:NCName"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

But there is still something wrong here. With a schema like this, you can expect the following messages to validate:

<HolidayRequest xmlns="http://mycompany.com/holidays/schemas">
  <Holiday>
    <StartDate>this is not a date</StartDate>
    <EndDate>neither is this</EndDate>
  </Holiday>
    ...
</HolidayRequest>

Clearly, we want to make sure that the start and end date are really dates. XML Schema has an excellent built-in date type for this, let’s use that. We might as well change those intimidating NCNames. And finally, we change the sequence in HolidayRequest to all. This tells the XML parser that the order of Holiday and Employee is not significant. Our final XSD looks like this:


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:holidays="http://mycompany.com/holidays/schemas" elementFormDefault="qualified" targetNamespace="http://mycompany.com/holidays/schemas"> <xs:element name="HolidayRequest"> <xs:complexType> <xs:all> <xs:element name="Holiday" type="holidays:HolidayType"/> <xs:element name="Employee" type="holidays:EmployeeType"/> </xs:all> </xs:complexType> </xs:element> <xs:complexType name="HolidayType"> <xs:sequence> <xs:element name="StartDate" type="xs:date"/> <xs:element name="EndDate" type="xs:date"/> </xs:sequence> </xs:complexType> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="Number" type="xs:integer"/> <xs:element name="FirstName" type="xs:string"/> <xs:element name="LastName" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema>

Save this as holidays.xsd, and you’re done with the schema.

WSDL

That leaves us with the WSDL. As I’ve written before, the WSDL format doesn’t really lend itself to data-driven communications, because it forces a behavior-driven model. In my opinion, you most certainly don’t need a WSDL: just give me the XSD and an URL to point my client to, and I’m happy. Other people are less easily satisfied, however.

We start our WSDL with the standard preamble, and by importing our existing XSD. To separate the schema from the definition, we will use a separate namespace for the WSDL definitions: "http://mycompany.com/holidays/definitions".

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
</wsdl:definitions>

Next, we define our messages based on the schema we’ve written. We only have one message: one with the HolidayRequest we put in the schema:

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
</wsdl:definitions>

Then we add the messages to a port type as operations:

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
  <wsdl:portType name="HumanResourcesPortType">
    <wsdl:operation name="RequestHoliday">
      <wsdl:input message="tns:RequestHolidayInput" />
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part of the WSDL. The concrete part consists of a binding, which tells the client how to invoke the operations you’ve just defined; and a service, which tells it where to invoke it. Let’s add those to our WSDL.

Adding a concrete part is pretty standard stuff: just refer to the abstract part you defined previously, make sure you use document/literal for the <soap:binding> elements (anything else is not interoperable), pick a soapAction (in this case http://example.com/RequestHoliday, but any URI will do), and determine the location URL where you want request to come in (in this case http://mycompany.com/humanresources):

<wsdl:definitions name="HumanResources"
      targetNamespace="http://mycompany.com/holidays/definitions"
      xmlns:tns="http://mycompany.com/holidays/definitions"
      xmlns:types="http://mycompany.com/holidays/schemas"
      xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://mycompany.com/holidays/schemas" schemaLocation="holidays.xsd"/>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="RequestHolidayInput">
    <wsdl:part name="body" element="types:HolidayRequest" />
  </wsdl:message>
  <wsdl:portType name="HumanResourcesPortType">
    <wsdl:operation name="RequestHoliday">
      <wsdl:input message="tns:RequestHolidayInput" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="HumanResourcesBinding" type="tns:HumanResourcesPortType">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="RequestHoliday">
      <soap:operation soapAction="http://example.com/RequestHoliday" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="HumanResourcesService">
    <wsdl:port name="HumanResourcesPort" binding="tns:HumanResourcesBinding">
      <soap:address location="http://mycompany.com/humanresources" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

And that’s our final WSDL, and also concludes this tutorial. The resulting schema and WSDL should be interoperable, and can be implemented with the SOAP stack of your choice. In a future post, I will show you how to implement this service with Spring-WS [See update below].

You can go back to your curly braces now.

Update 2006-06-11

Patrick suggested to use a <xsd:import> instead of a <wsdl:import>, which is indeed better. This also allows you to put the schema in a separate namespace. Thanks, Patrick!

Update 2007-03-26

A more recent version of this tutorial, including a second part which shows you how to implement this service in Spring-WS, is available here.

Mind your interfaces!

Sunday, May 21st, 2006

Haralampos Routis writes wise words over at the Spring Web Services forum:

In my experience, when you have to build a system that interoperates with others, you must pay attention to the interface that you provide. The implementation of the interface is important but it also can change in time. The interface on the other hand cannot/should not change.

So, if you allow me to give you an advice: Use whatever product, framework, soap stack best suits you, but invest on XML Schema and WSDL, because these are the essence of Web services.

I couldn’t agree more!