Tutorial: Writing Contract-first Web Services
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:
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
- XML Schema (XSD)
- RELAX NG
- Schematron
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.
Steve Loughran said,
June 9, 2006 @ 19:45
This is really good. you’ve left off one thing, which is validating your XSD. If you create test docs that represent valid/invalid XML messages, you can use or (ant1.7) to check that valid messages are valid. Or junit to trigger xml validations. I swear by this as it lets you regression test your schemas. You were going to regression test your schemas, werent you?
-steve
fgt said,
June 10, 2006 @ 23:38
Thanks for the good example. Just one minor tip. Use the schema import instead of the wsdl import for the xsd. This is more a WS-I thing.
to
I would use the wsdl import only to import another wsdl.
Cheers,
Patrick
Christian Weyer said,
June 11, 2006 @ 20:36
Nice tutorial.
Do you know our tutorial for schema-based contract-first Web Services? We use a tool we are building and giving away for free, called WSCF.
http://www.thinktecture.com/Resources/Software/WSContractFirst/WSCF0.6Walkthrough.html
http://www.thinktecture.com/WSCF/
Cheers,
Christian
Like Your Work » Blog Archive » links for 2006-07-05 said,
July 5, 2006 @ 1:23
[…] The Ancient Art of Programming ยป Tutorial: Writing Contract-first Web Services (tags: WebServices soa spring) […]