Web Services Design
Based on the readings from
Artjen Poutsma and Steve Loughran, I decided
to give contract-first web services a try. In this methodology we start
with data as it flows and then drive the schemas and WSDLs out. I have
it working with a slew of toolset from Emacs.
Here are the steps from a high
level-
- What are the entities I am working with
in the domain - I try to
come up with entities I am working with in the domain along with
relationships among them, following the CRUD talk by dhh at 2006
Railsconf. Once I have a good idea about this, I move to
- What does a physical message look like -
using Emacs I start
crafting raw XML messages. I do one for a request and one for a
response if its a req-resp MEP. This takes some time as I think through
all the possble ways to best represent the message(s)
- What will be te associated operations -
as I am modeling the
message flow or whiteboarding them, I get a sense of the operations. I
list them down and refactor any redundant ones. These operations
normally map to oneor more usecases defined in requirements.
- Generate the XSD from XML - I use XSD
inference tool based in
.Net (located at
/_utils/bin/Infer.exe)
to genrate the schema. I validate the message with the schema every
time I work with XSD or XML using DecisionSoft tool in my Firefox
window.
- Generate WSDL from XSD - I then use the
stylesheet (
/_utils/wsdl.xsl) given by Artjen on his
blog to
generate the WSDL
using SAXON (located in /_apps/saxon/saxon8.jar) I also
have a shell script (in $HOME/calc/wsdl.sh) to automate
this.
- Clean up WSDL - at this point I start
making sure WSDL represents
the operations and the messages accurately. I add the proposed service
info, tune the schema under wsdl:types as needed, work on defining all
operations under wsdl:portType and then under wsdl:binding. This takes
a good amount of time as I manually peruse the file as well as use Tom
V's WSDL viewer that converts WSDL to HTML form using a style sheet
(
/_utils/wsdl-viewer.xsl) and the script ($HOME/calc/whtm.sh)
- Implement using a SOAP stack - I can now reverse engineer
the implementation from this based on my choices
Following the above
methodology, here is how I tackled a Calendaring project.
STEP1
Identified the entities as CalendarEntry, User and an Event.
STEP2
Whiteboarding, I say we have a GetCalendarInfo operation that has a
CalendarInfoRequest and a CalendarInfoResponse. The request will have
the user, date, type of event etc. while the response will have all
calendar entries sorted by user with extended user information. My fist
raw XML looked like this (stored in $HOME/ws/CalendarInfo.xml)
<?xml version='1.0' encoding='UTF-8'?>
<c:CalendarInfo xmlns:c="http://balasubramanians.com/schema/calendar"
xmlns="http://balasubramanians.com/schema/calendar">
<CalendarRequest>
<user>raj</user>
<event>
<start_date>2006-10-11</start_date>
<end_date>2006-10-30</end_date>
<type>meeting</type>
</event>
</CalendarRequest>
<CalendarResponse>
<user id="raj">
<name>Raj Balasubramanian</name>
<email>raj@bala.com</email>
</user>
<event id="198928081" owner="raj" type="meeting">
<start_date>2006-10-11</start_date>
<start_time>1000</start_time>
<duration type="hrs">2</duration>
<parties>
<user id="stef"/>
<user id="ro"/>
</parties>
<description>Special date with
Roh</description>
</event>
<event id="19080" owner="stef" type="appointment">
<start_date>2006-10-10</start_date>
<end_date>2006-1-30</end_date>
<description>Vacatin tim for
me</description>
</event>
</CalendarResponse>
</c:CalendarInfo>
I then split out the request ($HOME/ws/CalendarReq.xml) and
response($HOME/ws/CalendarRes.xml) as follows
<?xml version='1.0' encoding='UTF-8'?>
<c:CalendarInfo xmlns:c="http://balasubramanians.com/schema/calendar"
xmlns="http://balasubramanians.com/schema/calendar">
<CalendarRequest>
<user>raj</user>
<event>
<start_date>2006-10-11</start_date>
<end_date>2006-10-30</end_date>
<type>meeting</type>
</event>
</CalendarRequest>
</c:CalendarInfo>
and
<?xml version='1.0' encoding='UTF-8'?>
<c:CalendarInfo xmlns:c="http://balasubramanians.com/schema/calendar"
xmlns="http://balasubramanians.com/schema/calendar">
<CalendarResponse>
<user id="raj">
<name>Raj Balasubramanian</name>
<email>raj@bala.com</email>
</user>
<event id="198928081" owner="raj" type="meeting">
<start_date>2006-10-11</start_date>
<start_time>1000</start_time>
<duration type="hrs">2</duration>
<parties>
<user id="stef"/>
<user id="ro"/>
</parties>
<description>Special date with
Roh</description>
</event>
<event id="19080" owner="stef" type="appointment">
<start_date>2006-10-10</start_date>
<end_date>2006-1-30</end_date>
<description>Vacatin tim for
me</description>
</event>
</CalendarResponse>
</c:CalendarInfo>
I go thru' few variations on the request as the query can contain just
user, or for a certain date or for certain event type etc.
STEP3
Only one operation is identified - GetCalendarInfo for now.
STEP4
Here is the genrated XSD
<?xml version="1.0"?>
<xs:schema xmlns:tns="http://balasubramanians.com/schema/calendar"
attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://balasubramanians.com/schema/calendar"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="CalendarInfo">
<xs:complexType>
<xs:choice>
<xs:element
name="CalendarRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="user" type="xs:string" />
<xs:element name="event">
<xs:complexType>
<xs:sequence>
<xs:element name="start_date" type="xs:date" />
<xs:element minOccurs="0" name="end_date" type="xs:date" />
<xs:element minOccurs="0" name="type" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element
name="CalendarResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="user">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string" />
<xs:element name="email" type="xs:string" />
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element maxOccurs="unbounded" name="event">
<xs:complexType>
<xs:sequence>
<xs:element name="start_date" type="xs:date" />
<xs:element minOccurs="0" name="end_date" type="xs:string" />
<xs:element minOccurs="0" name="start_time" type="xs:unsignedShort"
/>
<xs:element minOccurs="0" name="duration">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:unsignedByte">
<xs:attribute name="type" type="xs:string" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element minOccurs="0" name="parties">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="user">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="description" type="xs:string" />
</xs:sequence>
<xs:attribute name="id" type="xs:unsignedInt" use="required" />
<xs:attribute name="owner" type="xs:string" use="required" />
<xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
I make sure the element CalendarInfo can have either the request or the
response and not both, hence move the xs:sequence to xs:choice. (I need to be able to
better design this)
STEP5/6
Then I run thru' the WSDL generator tool and get the WSDL created. I
massage the WSDL until I am happy.
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:schema="http://balasubramanians.com/schema/calendar"
xmlns:tns="http://balasubramanians.com/schema/calendar"
name=""
targetNamespace="http://balasubramanians.com/schema/calendar">
<wsdl:types>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="http://balasubramanians.com/schema/calendar">
<xs:element
name="CalendarRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="user" type="xs:string"/>
<xs:element name="event">
<xs:complexType>
<xs:sequence>
<xs:element name="start_date" type="xs:date"/>
<xs:element minOccurs="0" name="end_date" type="xs:date"/>
<xs:element minOccurs="0" name="type" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element
name="CalendarResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="user">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="email" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element maxOccurs="unbounded" name="event">
<xs:complexType>
<xs:sequence>
<xs:element name="start_date" type="xs:date"/>
<xs:element minOccurs="0" name="end_date" type="xs:string"/>
<xs:element minOccurs="0" name="start_time"
type="xs:unsignedShort"/>
<xs:element minOccurs="0" name="duration">
<xs:complexType mixed="true">
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
</xs:complexType>
</xs:element>
<xs:element minOccurs="0" name="parties">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="user">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="description" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:unsignedInt" use="required"/>
<xs:attribute name="owner" type="xs:string" use="required"/>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>
<wsdl:message name="CalendarRequestMessage">
<wsdl:part
name="CalendarRequestMessage" element="schema:CalendarRequest"/>
</wsdl:message>
<wsdl:message name="CalendarResponseMessage">
<wsdl:part
name="CalendarResponseMessage" element="schema:CalendarResponse"/>
</wsdl:message>
<wsdl:portType name="CalendarPortType">
<wsdl:operation name="GetCalendarInfo">
<wsdl:input
message="tns:CalendarRequestMessage"/>
<wsdl:output
message="tns:CalendarResponseMessage"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CalendarBinding"
type="tns:CalendarPortType">
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="GetCalendarInfo">
<wsdlsoap:operation
soapAction="http://balasubramanians.com/GetCalendarInfo">
<wsdl:input>
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdlsoap:operation>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="CalendarService">
<wsdl:port name="CalendarPort"
binding="tns:CalendarBinding">
<wsdlsoap:address
location="http://localhost:8080/axis2/services/calendar"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Ofcourse the calendar
service will have a different endpoint based on the implementation
technology (in my example). For egs. using AXIS2 it will be
http://localhost:8080/axis2/services/CalendarService.
U-IBM-8B44D616B41\raj
Last modified: Thu Aug 3 09:50:34 CDT 2006