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-
  1. 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
  2. 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)
  3. 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.
  4. 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.
  5. 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.
  6. 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)
  7. 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