Designing the Web Service

We have now laid the appropriate groundwork to start the design of our Web service. This effort will build directly off of our Web service model and technology decisions.

I will approach this similarly to the way I approach a traditional n-tier application: by looking at each tier individually. Since we are building a Web service off of an existing system, I will start with the business layer and work up to the interface layer and then back down to the data layer. This may seem to be an odd order, but in this scenario, the business layer drives everything from the existing system, and the data layer exists primarily to support the interface and business layers.

Business Layer

The business layer for a brand-new Web service is typically very similar to that of a traditional n-tier application. That changes somewhat in this case because we are exposing an existing process. The business layer for our Web service will actually be fairly light, simply providing any functionality not directly exposed by the existing system.

We need to start the design of our business layer by analyzing the current COM objects and their interfaces. We have already had a look at them because our payload models were so dependent on them. However, we did not look at the parameters of the method calls because the information was already available through our requirements. Now that we have entered the design, we need to see the details. Let's start with the fetchAvailability call, shown here:

 Public Function fetchAvailability(_     ByVal dtmCheck_In As Date, _     ByVal dtmCheck_Out As Date, _     ByVal intAdults As Integer, _     ByVal intChildren As Integer, _     ByVal strCity As String, _     ByVal strState As String, _     ByVal strLandmark As String, _     ByVal strCoupon As String, _     ByVal curPrice As Currency, _     ByVal strBed As String, _     ByVal blnSmoking As Boolean, _     ByVal objAmenities As Variant) As Variant 

As discussed earlier, we need to traverse the variant array coming back to get the necessary information for our response. The only thing new to note here is that the amenities are defined through a variant object. This variant is used as a ranking of the amenities so that they can be prioritized. This is a little less efficient than another structure (like a dictionary) could be, since the variant has to be predefined and could potentially be empty, but this is the system we have to work with. The maximum size is 6 values, so the overhead isn't terrible.

We discovered earlier that fetchAvailability would not provide all the information we need. Because of this fact we need to utilize a second function called fetchHotel to build the entire availability response, as shown here:

 Public Function fetchHotel(ByVal Hotel_ID As Integer) As Variant 

The fetchHotel call returns a variant array that we also need to navigate. Otherwise, the call is fairly straightforward.

Since we have two calls with some arrays to handle, the best approach is probably to develop our own object that takes our request data, makes these two calls, traverses the arrays, builds the response payload, and returns it. Let's call the method wsFetchAvailability and define the method similarly to the fetchAvailability method, with the exception of returning a string instead of a variant array. The wsFetchAvailability function is seen here:

 Public Function wsFetchAvailability(...) as String 

We need to put a process flow together so that we have a clear definition of how to execute this process. (See Figure 7-8.) The first step is to take the data we have been provided and call the hotelAvailability interface into the HRS system. For each match that we get back, we then need to call the fetchHotel interface to get all the specifics about the hotel. Finally, we need to package all this data into the valid format to provide back to the consumer.

click to expand
Figure 7-8: The wsFetchAvailability process flow

We now need to look at the detail view request. This should not be confused with the fetchHotel method because it does not involve the reservation system. Instead, this simply involves the retrieval of content hosted on our Web server. The only "logic" needed is to reference the appropriate information when a request is received.

We already defined the request as the hotel ID, so we have a unique identifier to work from. The next design question is how to store and retrieve this data. To refresh your memory, the content potentially consists of the following:

  • Picture of hotel

  • Picture of lobby or meeting rooms

  • Picture of pool or workout facilities

  • Picture of Room A

  • Picture of Room B

  • Paragraph (50 to 100 words) describing hotel

Probably some of this information is already available for a Web site, but assume for this exercise that this is brand new content not referenced anywhere else. Remember that we want to expose this generically, since other systems and processes could utilize it. To accommodate this type of scenario, I suggest that you store the image files in a file structure rather than as binaries in a database. Furthermore, you should reference the image data in the payload instead of embedding it. This way data can be retrieved by the end user directly and does not have to be sent from us to the consumer and then on the user.

To support this approach, we will store the images on a Web server in a defined file structure and then store the path to these images in a database along with the paragraph of text associated with the appropriate hotel IDs. Although this content can be stored on our Web services system, it is best to post it on an existing content-based Web server so that we don't have to extend the access to our Web services box to our consumers' users.

Our logic then has to take the hotel ID and perform a lookup in the database, build the XML payload from the retrieved data, and return it. We can put this into the same object and call the method wsFetchHotelContent, shown here:

 Public Function wsFetchHotelContent(ByVal Hotel_ID As Integer) As String 

Now it is time to turn our attention to the reservation request itself. There is a single call exposed in the existing system that will handle this functionality. Let's take a look at it in its entirety, shown in Listing 7-1.

Listing 7-1: Reservation system's makeReservation method

start example
 Public Function makeReservation(_     ByVal intHotelID As Integer, _     ByVal intRoomType As Integer, _     ByVal dtmCheck_In As Date, _     ByVal dtmCheck_Out As Date, _     ByVal intAdults As Integer, _     ByVal intChildren As Integer, _     ByVal strFName As String, _     ByVal strMInitial As String, _     ByVal strLName As String, _     ByVal strHAddress1 As String, _     ByVal strHAddress2 As String, _     ByVal strHCity As String, _     ByVal strHState As String, _     ByVal intHZip As Integer, _     ByVal strHPhone As String, _     ByVal intCCNo As String, _     ByVal strCCIssuer As String, _     ByVal intCCExpMonth As Integer, _     ByVal intCCExpYear As Integer, _     ByVal strBAddress1 As String, _     ByVal strBAddress2 As String, _     ByVal strBCity As String, _     ByVal strBState As String, _     ByVal intBZip As Integer, _     ByVal strCoupon As String) As Long 
end example

Caution 

This function code is not valid "as is" in VB 6.0 because there is a 24-count limit on the number of line continuations it will support. You would just need to combine a few of the parameters onto a single line.

This call returns an integer value that represents the confirmation number for the reservation, if made. If for any reason the reservation is not made, a 0 is returned. The question that obviously presents itself in that case is how to handle the returned 0. The answer depends on which service model we are dealing with. Because of that dependency, we will leave this issue to our interface design.

Because this only has a single value returned and the parameters are all simple data types, we do not need to create a custom wrapper. We just call this functionality directly from our listener.

That of course brings us to the Web services listener. If you remember from Chapter 2, this is the component responsible for receiving the request and routing it appropriately. This is considered part of our interface layer, so let's proceed to that tier.

Interface Layer

The purpose of our interface layer is to define the Web services interface, handle requests from consumers, access the appropriate business logic, and return a response. To receive and handle the requests, this layer is also responsible for enforcing our interface. This layer almost acts as a gatekeeper to our service to ensure that only valid requests are passed along. The interface defines the rules for making a request of our Web services, and this layer is the enforcer.

We have already defined our business logic and covered at a high level how we are going to handle each request. Before really designing those processes any further, we need to define our interfaces. These should match up fairly closely to the calls of the business objects we have defined. If we designed our interfaces prior to designing our business objects, we will probably have to revisit them. This might be unavoidable, but you have the best chance of only going through this process once if you have your business objects designed.

When you think about designing your interface layer, you need to think in terms of designing schemas. If you are exposing your processes as XML, a schema will always be a necessary component of your interface. However, like in traditional object development, before designing the interface, you need to identify the building blocks that make it up. You may be used to inheriting objects to create new objects, or wrapping existing components with new components, but here you need to think of schemas made up of other schemas.

The unique perspective of Web services is that our interface doubles as a data definition. The interface we define is essentially a piece of data that can extend far beyond the existing interface. To maximize our reuse, we then need to think about what data, or subsets of data, may need to go to different destinations in that XML format.

You can relate this same concept to taking lunch orders from coworkers for a local fast food restaurant. Efficient schemas are like the numbered meals that come prepackaged with a sandwich, side item, and drink. Taking orders from 10 people could be as easy as the following:

  • Three number 1s with cola

  • Two number 1s with diet cola

  • One number 3 with cola

  • Two number 3s with lemon-lime soda

  • Two number 5s with cola

If all the meals of each type are packaged together, determining whose item is whose is very easy. Without this element of packaged meals, the task of ordering lunch is much harder. It could easily turn into this organizational mess:

  • Five hamburgers

  • Three cheeseburgers

  • Two chicken sandwiches

  • Four large fries

  • Three medium fries

  • Three small fries

  • Six colas

  • Two diet colas

  • Two lemon-lime sodas

The distribution of the correct orders is then made worse if everything is put in bags without any order. This is the kind of situation we will be in if we simply identify all the data necessary for our interface and make each element a child of the root. It will be harder to verify that we have all the data captured, harder to read, and more difficult to distribute portions of the data.

So before we jump into the schema designs themselves, let's try to map out the schemas we need to define. If we start out with a map of our schemas, it will help us to look ahead and try to anticipate some of the issues we might encounter and also identify opportunities for reuse. This will also ultimately make it easier for our applications to work with the data.

One Interface or Two?

The first question we need to answer in our interface design is whether to use one interface or two in defining the two "views" of our Web service. The issue is one of redundancy versus encumbrance.

Even though our payload model has been defined to support one set of logic for both services, there are still two Web services that could be exposed by either one or two interfaces. Recall that we also made the decision to utilize two different addresses for our two services. That already provides us with a convenient logical distinction between the two service types. Utilizing two different physical schemas to represent their interfaces takes this separation one step further.

As we saw in our analysis, the embedded model contains three interactions, while the isolated model contains five. Three of the five interactions in the isolated model are redundant with the three embedded interactions. We need to provide some additional content to those three through our XSL templates, but fundamentally they are responding with the same data. If these templates are applied to the payload before responding, the two interactions will be very different. However, we are adding an external reference to an XSL template that can be used to format the data on the consumer's side.

If we take this approach, we can theoretically reuse the three embedded interactions directly for our isolated model. Then we can add the two isolated interactions to the interface to develop a single schema for both service types. We just need to be aware that we need an optional element available in our interface (in the service variables element) to define an XSL template to meet the isolated model's needs. A good place for this is in our service variables!

Service Variables

This application is slightly more complicated than the norm, since we have two interface layers we are actually designing: embedded and isolated. The design of these interfaces picks right up where our model definition left off.

The one constant in both interfaces will be the service variables section. It will be treated as a header for all request and response payloads. This is where our service variables are defined, and both the embedded and isolated models will contain this information.

The service variables consist of information on the current session and state. Additionally, we now add an optional reference to an XSL template that can be used to format our responses for our isolated model. The XSD defining the entire service variables element is shown in Listing 7-2.

Listing 7-2: Service variables schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">      <xsd:element name="serviceVariables">            <xsd:complexType>                  <xsd:sequence>                        <xsd:element name="sessionID" type="xsd:int"/>                        <xsd:element name="stage" type="xsd:short" minOccurs="1"                          maxOccurs="1"/>                        <xsd:element name="stylesheet" type=" xsd:anyURI"                          minOccurs="0"/>                  </xsd:sequence>            </xsd:complexType>      </xsd:element> </xsd:schema> 
end example

We could potentially find the stage from a lookup of the sessionID, but keeping the stage separate does two things for us. First, it allows the service to recover gracefully if for any reason the session expires or is not found. Second, it allows us to route the request more efficiently without doing a lookup on the session. This assumes a certain amount of trust with the consumer, but that is acceptable for this scenario, since we are establishing a formal partnership anyway.

Interface Document

I need to point out here that the stage element is also redundant for another reason. Each interaction has a request and a response that contains various elements that make up its payload. Rather than define these payloads generically and force all data into a single structure, each interaction has its own document defined. The service provider can simply interpret the payload to determine what stage the consumer is requesting. However, the performance improves greatly if a stage value can be provided and maintained, since checking the existence of elements can be time consuming. If the session variables are maintained on a consistent level (relative to the root document), that value can easily be retrieved, and the entire payload can be processed by the appropriate logic. Otherwise, the logic has to take on a "pecking order" routine of searching for the appropriate element for stage 1, the appropriate element for stage 2, and so on until a match is found. This is very inefficient and burns valuable cycles in turning around a response to the consumer.

For this reason, all interactions also have the same document element (or root node). This means that the service variables are always in the exact same path. For this service, reservationWS will be our root node. That makes the path for the stage element reservationWS/serviceVariables/stage.

Without this standard root node defined, we can end up with multiple paths to our service variables, which essentially require the same "pecking order" routine we have without the stage element.

At this point we should design our interface schema document map. This is essentially our roadmap as we design the schemas for the Web services interface. (See Figure 7-9.) You can think of this as a template for structuring the various documents that will make up our interface.

click to expand
Figure 7-9: Logical interface schema hierarchy map

We just determined that we will have one root document for both services' interface. However, we really have two different interfaces, one for embedded and another for isolated. This is a logical map, so it applies to both. Once we design the interfaces, we will develop a physical map for each.

Beneath the interface schema, we have the interaction schemas. These are the different calls in the interface that the consumer can make. We already identified a total of five interactions that our service will support for both interfaces. These schemas then reference the payloads for the specific requests and responses, which then reference data type schemas that define our reusable elements.

With the global ground rules for our interfaces defined, let's proceed to design the specific interfaces for our embedded and isolated models.

Embedded Service Interactions

At this point, we do not want to identify every single schema we will build, but rather the high-level schemas that will define our interactions with the consumer and other objects. This level of analysis will help us to recognize what data is getting transferred around so that you can determine during design where potential redundancies will lie. If you recall, the embedded interface has three interactions with the consumer: availability request, hotel detail view, and reservation request.

Schema Maps

For the availability request, we need to define a schema for the request from the consumer. (See Figure 7-10.) Once we receive that, we need to make a request of our wsFetchAvailability component. This will be a standard COM method call, and we will pass the data through parameters. It is, however, going to be returning data in an XML string, so we need to define the schema for that. We are then going to add the session data to that document, so let's call that a third schema.

click to expand
Figure 7-10: Availability request schema map

The detail view request requires an initial schema. (See Figure 7-11.) Again, we will break the data down and call a component. It will provide an XML string back, to which we will again want to add the session data. As with the availability request, we are looking at three main schemas for our interactions.

click to expand
Figure 7-11: Hotel detail view schema map

The reservation process is slightly different than the others, since we are calling a component of the reservation system directly. It does not pass XML back, so we do not need a schema for its response. This leaves us with one schema for the request coming in and another for the response we are sending back. (See Figure 7-12.)

click to expand
Figure 7-12: Reservation request schema map

With these schemas identified, we can confidently move on and proceed to design the specific schemas for our interface.

Schema Designs

We now know at a high level what steps need to be carried out for each request and pretty much how we are going to accomplish them. The next step is to build out the schemas for each step so that we can validate both the consumer requests coming in and the data we are sending out.

Availability Request

The availability request will consist of the data elements we listed previously in our requirements. At this point, all we need to do is structure the data appropriately and identify which elements are required and which are optional. We also need to determine if there are any data elements that need to be defined through a subschema, which would simply be their own schema.

We should first look over our request data and define any appropriate categories. As we look through the list, defined by the requirements, the groups that jump out are preferences and amenities. Everything else is fairly simple and specific to the availability request process.

The next decision is whether these groups should be defined externally or simply in our schema. The preferences data includes price, bed size, and smoking. As preferences, we would probably get little reuse by defining them externally, since that is specific to looking for a matching room. However, we may need to ask ourselves if these preferences could ever be utilized as properties for other functions. Because this is such a narrow group of data that is neither generic nor complete, we will just define preferences as an internal entity.

The amenities group is a little different. These are all aspects of hotels that might have value in a few different ways. This includes a list of attributes such as concierge, room service, and workout facilities. In this context, we want to know which amenities the user would like to find in matching hotels. This definition of this information could also easily be used to describe a particular hotel. It ends up later that we will need this functionality ourselves, so let's go ahead and define the amenities entity on its own, as shown in Listing 7-3.

Listing 7-3: Amenity element schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">      <xsd:simpleType name="amenity">            <xsd:restriction base="xsd:string">                  <xsd:enumeration value="Concierge"/>                  <xsd:enumeration value="Workout Facilities"/>                  <xsd:enumeration value="Pool"/>                  <xsd:enumeration value="Restaurant"/>                  <xsd:enumeration value="24-hour Room Service"/>                  <xsd:enumeration value=" High Speed Internet Access"/>            </xsd:restriction>      </xsd:simpleType> </xsd:schema> 
end example

This is a relatively simple schema, but reusing it will make our applications more efficient as well as make any updating of these properties very simple. Keep in mind during implementation that this can be a growing list so our applications do not break because of new values.

With our only external dependency defined, we can turn our attention to the availability request schema. The one requirement that is a little tricky here is the option of entering a city and state or landmark for the search location. This means that someone should be able to enter either "St. Louis, MO" or "The Arch" and successfully run an availability search. These either/or cases are always among the trickier ones to define.

Defining the "or" scenario is easiest through a choice element. The fact that you are choosing between using a two-element option and a single-element option just adds an extra twist to it. To get around this, I will define city and state as a single element and simply make the city and state elements children of it. This makes the choice between the landmark and citystate nodes. Of course, this choice requires an element to define it. That puts our city and state data deeper in our "tree," but it allows us to enforce the rule through our schema. We might be adding a little overhead in our schema, but the dividends will be worth it when we are working with the data.

So let's go ahead and define all of our availability criteria. These include all of the base elements that make up the availability request and allow for reuse across other schemas, as shown in Listing 7-4.

Listing 7-4: Availability criteria schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/amenities.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/USAddress.xsd"/>       <xsd:element name="locale">             <xsd:complexType>                   <xsd:choice>                         <xsd:element ref="cityState"/>                         <xsd:element ref="landmark"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element>       <xsd:element name="bedSize">             <xsd:simpleType>                  <xsd:restriction base="xsd:string">                       <xsd:enumeration value="King"/>                       <xsd:enumeration value="Queen"/>                       <xsd:enumeration value="Double"/>                  </xsd:restriction>             </xsd:simpleType>       </xsd:element>       <xsd:element name="cityState">             <xsd:complexType>                  <xsd:sequence>                       <xsd:element name="city" type="city"/>                       <xsd:element name="state" type="state"/>                  </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="approxPrice" type="xsd:decimal"/>       <xsd:element name="couponCode">             <xsd:simpleType>                  <xsd:restriction base="xsd:string">                       <xsd:maxLength value="10"/>                  </xsd:restriction>             </xsd:simpleType>       </xsd:element>       <xsd:element name="preferences">             <xsd:complexType>                  <xsd:sequence>                        <xsd:element ref="bedSize" minOccurs="0"/>                        <xsd:element name="smoking" type="xsd:boolean" minOccurs="0"/>                        <xsd:element name="amenity" type="amenity" minOccurs="0"                          maxOccurs="unbounded"/>                  </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="landmark">             <xsd:simpleType>                   <xsd:restriction base="xsd:string">                         <xsd:maxLength value="50"/>                   </xsd:restriction>             </xsd:simpleType>       </xsd:element>       <xsd:element name="numberAdults">             <xsd:simpleType>                   <xsd:restriction base="xsd:short">                         <xsd:minInclusive value="1"/>                         <xsd:maxInclusive value="4"/>                   </xsd:restriction>             </xsd:simpleType>       </xsd:element>       <xsd:element name=" numberChildren">             <xsd:simpleType>                   <xsd:restriction base=" xsd:short">                         <xsd:minInclusive value="0"/>                         <xsd:maxInclusive value="4"/>                   </xsd:restriction>             </xsd:simpleType>       </xsd:element> </xsd:schema> 
end example

You might have noticed that I am referencing the USAddress schema, which we have not yet discussed. What it defines is likely self-evident, but this is a schema that we will cover later in our reservation request. (See Listing 7-14.) This is looking a little ahead, but in this instance, we are referencing a state element that has an enumeration defined for every state by abbreviation. Nothing exciting, but definitely something we don't want to define multiple times!

As I mentioned earlier, we will reference our amenity schema and group the preference data into its own element for readability. Since the amenities are part of the user's preferences, we will define them as part of that group, as shown in Listing 7-5.

Listing 7-5: Availability request schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityCriteria.xsd"/>       <xsd:element name="availabilityRequest">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="checkInDate" type="xsd:date"/>                         <xsd:element name="checkOutDate" type="xsd:date"/>                         <xsd:element ref="numberAdults"/>                         <xsd:element ref="numberChildren"/>                         <xsd:element ref="locale" maxOccurs="2"/>                         <xsd:element ref="couponCode" minOccurs="0"/>                         <xsd:element ref="approxPrice"/>                         <xsd:element ref="preferences" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

We identified earlier that we need the service variables present, even in an initial request. There are a couple different ways to handle this. One is to add the service variables to this schema. We can add them to the availabilityRequest sequence and simply make them optional. The other option is to add them to the root document defining our interface. We will take this second approach, which externalizes the handling of the service variables from this level of definition. This makes for a much cleaner design that is easier to modify in the future in case we decide to add more information to our header.

The availability request payload "as is" exposes the functionality as a Web service. We are then adding session data for the sake of managing the entire response. Since the existing interface definition has value through other services, or perhaps even internally, I would hate to modify it directly. Instead, let's take the other approach and define a new schema strictly for this Web service we are creating.

This new schema basically consists of wrapping this data with the root node and including the necessary header data. We do need to think of a new root node name. We don't want to reuse the availabilityRequest node here because that is likely to cause confusion between the two schemas. Since it is a Web service, let's have some reference to that in the name, like a prefix of "ws." In other examples we used the node hotelAvailability, so take that and add our prefix.

Notice that you are keeping the core availability request and service variable definitions external to this definition. You are simply including their independent schemas to efficiently reuse those definitions without duplication. This is also only the request at this point. We will address the response shortly.

When this request comes in from a consumer, the first thing we need to do is determine if a session already exists. If it does not, we need to create one. This session is defined in our data source and consists of the consumer, the time, the request type, and some of the main information from any availability requests. We will design this more completely when we get to our data layer.

Once this is done, we will want to submit this information to our wsFetchAvailability business object. This should return an XML document containing the matching data. We should now define the schema for that matching hotel data, as shown in Listing 7-6.

Listing 7-6: Availability results schema

start example
 <xsd:schema xmlns:xsd=" http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation=" http://www.architectingwebservices.com/         interfaces/hrsws/amenities.xsd"/>       <xsd:include schemaLocation=" http://www.architectingwebservices.com/         interfaces/hrsws/USAddress.xsd"/>       <xsd:element name="availabilityResults">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="hotel" maxOccurs="unbounded"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="hotel">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="hotelName" type="xsd:string"/>                         <xsd:element name="hotelID" type="xsd:integer"/>                         <xsd:element name="proximity" type="proximity"/>                         <xsd:element name="roomType" type="roomType"/>                         <xsd:element name="roomRate" type="xsd:decimal"/>                         <xsd:element name="hotelAddress" type="USAddress"/>                         <xsd:element name="hotelPhone" type="xsd:string"/>                         <xsd:element name="roomAmenity" type=" amenity"minOccurs="0"                           maxOccurs="unbounded"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:complexType name="proximity">             <xsd:simpleContent>                   <xsd:extension base="xsd:integer">                         <xsd:attribute name="measurement" type="xsd:string"                           use="required"/>                   </xsd:extension>             </xsd:simpleContent>       </xsd:complexType>       <xsd:simpleType name="roomType">             <xsd:restriction base="xsd:string">                   <xsd:enumeration value="Value"/>                   <xsd:enumeration value="Business"/>                   <xsd:enumeration value="Suite"/>                   <xsd:enumeration value="Premiere"/>             </xsd:restriction>       </xsd:simpleType> </xsd:schema> 
end example

We now have the raw matching hotel data. However, we still need to package it as an appropriate response to the consumer. We need to add our date stamp for when the request was made. We are also going to add a copy of the request that was made of us for reference by the consumer and/or user. We could modify the availabilityResults schema to incorporate these changes, but this would compromise the data we need to validate from that object. Instead, we will create a new root node for the response payload. You'll create an availabilityResponse node, as shown in Listing 7-7.

Listing 7-7: Availability Response Schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/        interfaces/hrsws/availabilityRequest.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityResults.xsd"/>       <xsd:element name="availabilityResponse">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="searchDate" type="xsd:date"/>                         <xsd:element ref="availabilityRequest"/>                         <xsd:element ref="availabilityResults" minOccurs="0"                           maxOccurs="unbounded"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

The condition we have not addressed yet is how to handle empty results from the reservation system. Should we develop a completely different schema for that payload? In this case, that really is not necessary. The main objective in such a situation is to communicate to the consumer that no matches were found. To accomplish this for this service, I would simply modify the existing schema to allow for no hotel nodes, as shown here:

 <xsd:element ref="hotel" minOccurs="0" maxOccurs="unbounded"/> 

I would then utilize this option when the search comes back empty and leave the rest of the nodes the same. (See Listing 7-8.) This still provides the same information to the consumer in the rest of the payload and allows consumers to easily run a check to see if any matches were found.

Listing 7-8: Sample null result payload for an availability request

start example
 <hotelAvailabilityResponse xmlns:xsi=   "http://www.w3.org/2001/XMLSchema-instance"   xsi:noNamespaceSchemaLocation=" http://www.architectingwebservices.com/   interfaces/hrsws/availabilityResponse.xsd">      <searchDate>2001-07-16</searchDate>      <availabilityRequest>            <checkInDate>2001-08-01</checkInDate>            <checkOutDate>2001-08-03</checkOutDate>            <numberAdults>1</numberAdults>            <numberChildren>0</numberChildren>            <locale>                  <cityState>                       <city>Memphis</city>                       <state>TN</state>                  </cityState>            </locale>            <approxPrice>50</approxPrice>      </availabilityRequest> </hotelAvailabilityResponse> 
end example

We now have one schema defined for the request and the response data payloads. However, we need to combine these two schemas into a single interaction schema. After all, when you look up the interface on an object or component, you don't have to go to different places to find the call and the response details! Our objective should be to combine these two schemas into one schema that can be referred to for the entire interaction.

The easiest approach is to add one more level of schema and simply reference each of those. I will use a simple choice element to choose between our two definitions for the response and the request and will utilize a parent node called availabilityInteraction, as shown in Listing 7-9.

Listing 7-9: Integrated hotel availability interaction schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityResponse.xsd"/>       <xsd:element name="availabilityInteraction">             <xsd:complexType>                   <xsd:choice>                         <xsd:element name="availabilityRequest"/>                         <xsd:element name="availabilityResponse"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Notice that even though I am referencing both availabilityRequest and availabilityResponse elements, I have only included a reference to the availabilityResponse schema. This is because availabilityResponse schema references the availabilityRequest, since it can include an image of the request that generated the results. Including the availabilityRequest schema directly in this schema would generate an error, since you would effectively have two definitions for a single element. You can get around this by importing the schema and associating a namespace, but there really is no need. Adding multiple instances of the same schema is not only redundant but also more demanding of the applications that work with the data. The downside is that if we de-reference the request in the response, we have to modify this schema to reference it directly. Since interface changes can be such a dangerous issue, this should be avoided whenever possible.

Tip 

As we discussed in Chapter 6, enhancements or changes to Web services should be approached with caution. If a Web services interface needs to be changed after being deployed, the most responsible, efficient solution is to create a new interface, reusing as many structure schemas as possible from the old interface. The old interface could then be maintained at least until all of its consumers have been transitioned over.

We can take this one step further and say that we need to combine all interactions into a single document to define the interface for the Web service. This is where the reservationWS document node comes into play. We obviously do not have the other interactions defined, but we can at least reference this interaction and see how we will add the service variables, as shown in Listing 7-10.

Listing 7-10: The reservationWS interface schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/serviceVariables.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityInteraction.xsd"/>       <xsd:element name="reservationWS">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="serviceVariables"/>                         <xsd:element ref="payload" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="payload">             <xsd:complexType>                  <xsd:choice>                        <xsd:element ref="availabilityInteraction"/>                  </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Hotel Detail View

Now that we have the availability interface defined, we need to define the interface for our detailed view. This is a fairly simple process, but does not have an existing object to work from. We created our own method to provide this functionality, so we will draw mostly from it.

The request itself only consists of the hotel ID, aside from the service variables. Since the data only consists of the one variable, it is not necessary to define it separately and reference it from the interaction schema. Instead we will define it directly in our interaction schema. If we decide to make it more robust, we can always pull it out into its own schema in the future. For now, let's define the request schema internally.

After receiving the detail request, call the wsFetchHotelContent method and receive the data it retrieved from our data source. We covered the information earlier, so let's look at the schema for this data, as shown in Listing 7-11.

Listing 7-11: Hotel detail response schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:element name="hotelContent">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="mainPicture" type=" xsd:anyURI"/>                         <xsd:element name="facilitiesPicture" type=" xsd:anyURI"                           minOccurs="2" maxOccurs="2"/>                         <xsd:element name=" roomPicture" type=" xsd:anyURI"                           minOccurs="2" maxOccurs="2"/>                         <xsd:element name="hotelDescription" type="xsd:string"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

I have made a few modifications here, so we should briefly walk through this schema. Our requirements stated that you needed to provide five pictures and a description of the hotel itself. The main picture is set aside on its own, but I have grouped the other four into two pairs: facilitiesPicture and roomPicture. Since the content of the pictures was so loosely defined, I decided to group them accordingly with a common name to give a little more meaning to them. That way if the picture is of a pool or a meeting room, it doesn't really matter because they are both facilities. This also allows for future growth, since we can easily add more pictures to either of these groups in the schema.

Since we want to avoid separate schemas for each request and response, let's build on the request schema we already have defined. By taking roughly the same approach we did for the availability interaction, the schema defining this interaction takes the form shown in Listing 7-12.

Listing 7-12: Detail interaction schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/hotelContent.xsd"/>       <xsd:element name="detailInteraction">             <xsd:complexType>                   <xsd:choice>                         <xsd:element name="hotelID" type="xsd:short"/>                         <xsd:element ref="hotelContent"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Next, we need to add this interaction to our interface schema. This essentially involves adding a reference to the interaction schema and adding the detailInteraction element as an option in our payload, as shown in Listing 7-13.

Listing 7-13: The reservationWS interface with availability interaction

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/serviceVariables.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/detailInteraction.xsd"/>       <xsd:element name="reservationWS">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="serviceVariables"/>                         <xsd:element ref="payload" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="payload">             <xsd:complexType>                   <xsd:choice>                         <xsd:element ref="availabilityInteraction"/>                         <xsd:element ref="detailInteraction"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Reservation Request

The next interaction to define is the reservation request. This interface closely matches the makeReservation object from the existing system, with only a few possible exceptions. This service is an excellent example of a heavy request and light response. We are going to perform a process for the consumer and simply respond with a confirmation ID to show that the reservation was made.

The main design issue for this interface is determining how to structure the data. We are receiving a lot of data on the user, and we can define it all in a "flat," single-layer structure, or we can provide some hierarchy. This especially makes sense if the data has a logical grouping. In this case, we are dealing with personal information in the form of addresses, names, and criteria. Since we are potentially dealing with two different addresses that are structured the same, it makes sense to reuse the same definition. We should also make the schema generic enough to be reused for other documents (as we did earlier for our availability criteria). I have chosen to define just U.S. addresses to keep from needlessly overcomplicating the example in Listing 7-14.

Listing 7-14: U.S. address data type schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:complexType name="USAddress">             <xsd:sequence>                   <xsd:element name="address" type="address" maxOccurs="3"/>                   <xsd:element name="city" type="city"/>                   <xsd:element name="state" type="state"/>                   <xsd:element name="zipCode" type="xsd:string"/>             </xsd:sequence>       </xsd:complexType>       <xsd:simpleType name="city">             <xsd:restriction base="xsd:string">                   <xsd:maxLength value="30"/>             </xsd:restriction>       </xsd:simpleType>       <xsd:simpleType name="address">             <xsd:restriction base="xsd:string">                   <xsd:maxLength value="30"/>             </xsd:restriction>       </xsd:simpleType>       <xsd:simpleType name="state">             <xsd:restriction base="xsd:string">                   <xsd:enumeration value="AL"/>                   ...<!—I will spare listing all 59 US states and properties here —>                   <xsd:enumeration value="WY"/>             </xsd:restriction>       </xsd:simpleType> </xsd:schema> 
end example

We will save this separately as an independent schema and reference it when necessary. Our request payload will potentially reference this twice for both the home address and billing address. The other obvious opportunity we have for creating a subschema is the credit card data. We have four pieces of data that have little value independently and can potentially be used elsewhere. Let's go ahead and define it separately in Listing 7-15.

Listing 7-15: Credit card data type schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:element name="ccData">             <xsd:complexType>                   <xsd:all>                         <xsd:element name="number" type="ccNumber"/>                         <xsd:element name="issuer" type="ccBanks"/>                         <xsd:element name="expirationDate" type="xsd:date"/>                         <xsd:element name="nameOfOwner" type="xsd:string"/>                   </xsd:all>             </xsd:complexType>       </xsd:element>       <xsd:simpleType name="ccNumber">             <xsd:restriction type="xsd:string">                   <xsd:length value="16"/>             </xsd:restriction>       </xsd:simpleType>       <xsd:simpleType name="ccBanks">             <xsd:restriction base="xsd:string">                   <xsd:enumeration value="Visa"/>                   <xsd:enumeration value="Mastercard"/>                   <xsd:enumeration value="American Express"/>                   <xsd:enumeration value="Discover"/>             </xsd:restriction>       </xsd:simpleType> </xsd:schema> 
end example

The data for the credit card has to be fairly open to accommodate all the different types. We can, however, restrict the issuer to those that we accept and define the valid credit card number length. By separating this, we can reuse it for other schemas that require credit card data, which we can likely expect to have.

With those two entities defined, we can define the schema for the entire payload. What we are left with are the personal information of the user and hotel data specifying the hotel and room type. Since there is a very clean separation between these two types of data, I'm going to separate them into separate nodes. This doesn't necessarily buy us reuse, but does make your code a little more readable, as you can see in Listing 7-16.

Listing 7-16: Reservation request schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/USAddress.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/ccData.xsd"/>       <xsd:element name="reservationRequest">             <xsd:complexType>                   <xsd:all>                         <xsd:element ref="hotelData"/>                         <xsd:element ref="personalData"/>                   </xsd:all>             </xsd:complexType>       </xsd:element>       <xsd:element name="hotelData">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="hotelID" type="xsd:short"/>                         <xsd:element name="roomType" type="xsd:string"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="personalData">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="homeAddress" type="USAddress"/>                         <xsd:element name="firstName" type="xsd:string"/>                         <xsd:element name="lastName" type="xsd:string"/>                         <xsd:element name="homePhone" type="xsd:string"/>                         <xsd:element name="billAddress" type="USAddress"                           minOccurs="0"/>                         <xsd:element ref="ccData"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Notice how I am referencing the USAddress element from our previous schema, but naming it something pertinent to its usage in this schema. Also, the billing address is optional for when it and the home address are the same.

We still have to add the service variables to this payload before it will fit the interface structure we have established for the other two calls of this service. We will wait to do that until we define the response and build our interface schema incorporating both pieces of the interaction.

After receiving this request, we will verify that the session is current. On doing this, we will need to look up data related to the session concerning the original request. Specifically, we will need to retrieve the following information:

  • Check-in date

  • Check-out date

  • Number of adults

  • Number of children

  • Coupon

Add this information to the data received from the consumer and call the existing makeReservation method utilizing the appropriate parameters. It then responds with either a reservation number or an error number. If it is a reservation number, we simply pass it back to the consumer. If it is an error, we interpret it and send back a meaningful message to the consumer. In these instances, we can send back some of the data we were sent so that information could be verified, as shown in Listing 7-17.

Listing 7-17: Reservation response schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationRequest.xsd"/>       <xsd:element name="reservationResponse">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="reservationRequest" minOccurs="1"                           maxOccurs="1"/>                         <xsd:element name="confirmationID" type="xsd:integer"/>                         <xsd:element name="error" type="xsd:string" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

As before, we need to now bring this together with the request to define the interaction schema. We will call it reservationInteraction. As with the availability interaction schema, only include the response schema, because it references the request schema in it, as shown by Listing 7-18.

Listing 7-18: Reservation interaction schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationResponse.xsd"/>       <xsd:element name="reservationInteraction">             <xsd:complexType>                   <xsd:choice>                         <xsd:element name="reservationRequest"/>                         <xsd:element name="reservationResponse"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Finally, we need to incorporate this interaction into our interface schema. This consists of the same steps we took to incorporate the detailInteraction schema, as shown in Listing 7-19.

Listing 7-19: The reservationWS interface schema with reservation interaction

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/serviceVariables.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/detailInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationInteraction.xsd"/>       <xsd:element name="reservationWS">            <xsd:complexType>                 <xsd:sequence>                       <xsd:element ref="serviceVariables"/>                       <xsd:element ref="payload" minOccurs="0"/>                 </xsd:sequence>            </xsd:complexType>       </xsd:element>       <xsd:element name="payload">            <xsd:complexType>                 <xsd:choice>                        <xsd:element ref="availabilityInteraction"/>                        <xsd:element ref="detailInteraction"/>                        <xsd:element ref="reservationInteraction"/>                 </xsd:choice>            </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

As I promised, I have assembled a physical interface schema hierarchy map to help illustrate the relationships between each of our schemas. (See Figure 7-13.) Notice that this falls in line with the earlier logical map we saw in Figure 7-9.

click to expand
Figure 7-13: Physical embedded interface schema hierarchy diagram

Now that we have designed our interface for the embedded model of our service, it is time to turn our attention to the isolated model.

Isolated Service Interactions

The other "flavor" of our Web service is the isolated model. The interface for this model will be much more robust than the embedded model, but it will build on this same interface. The same process will be in place, but we will add an initial request to retrieve the hotel availability form for presentation to the user. Fundamentally, the two existing request payloads will be very similar, if not identical, but the response payloads will contain more presentation-ready data.

As with the embedded model's design, we will base our isolated service on the model we defined earlier in the section about modeling the Web service. It might be obvious to you that the existing logic and functionality still apply to this version of the service so we won't have to generate a lot of new business logic. This works to our advantage during implementation, since we don't have to develop two separate services.

Schema Maps

If you compare the two process models we developed for the embedded and isolated processes (see Figures 7-3 and 7-4), you see that we have a few additional interactions the embedded process does not. Here is the entire list of interactions, with the ones specific to the isolated service denoted by an asterisk:

  • Availability form*

  • Availability request

  • Detail view

  • Reservation form*

  • Reservation request

The first additional interaction is the request starting the process. Whereas the consumer provides the UI for the hotel availability request in the embedded service, the service provider provides the UI data for the isolated service. The consumer makes this initial request of the service to initiate the process for each new user. The service then responds with the hotel availability form data. We will develop an external XSL template that the consumer can reference to generate a look and feel. This information can then be presented to the user. (See Figure 7-14.) Consumers have the option to forgo our stylesheet for their own, or even apply an additional transformation on top of ours to modify a few elements. I will look at these options in more detail in Chapter 8.

click to expand
Figure 7-14: Availability form schema map

The next interaction we need to map out is the reservation request interface. The response to this request will contain a valid input form that collects the information from the user that the consumer will submit to the service to make the reservation. Like the previous map, the request is very light, simply asking for the UI components to provide the form for the end user. (See Figure 7-15.) Like with the other interactions, we will provide a reference to an external XSL template that consumers can use at their discretion.

click to expand
Figure 7-15: Reservation form schema map

These are the only additional schema maps we need to generate for the isolated service. The other isolated interactions will be exactly the same as the embedded model. (See Figures 7-10, 7-11, and 7-12.) Thus we can proceed on to design the schemas for our interface.

Schema Designs

It is now time to turn our attention back to schema development. We will take what we have just laid out as our roadmap for both steps we are adding to our service and create definition schemas so that we can validate the requests coming in and the responses we are sending out.

Availability Form

The first interface to design is the initial service request. This will consist of a very light request that simply needs to communicate the identity of the consumer and the service the consumer is requesting. The identity will be handled through our authentication process, which we will design when we address the service listener design.

There are several ways to go about declaring the service the consumer is requesting. We can have different URLs for each service, or we can have specific header variables that identify this. When we defined our model earlier, we decided to go ahead and use separate URLs for each of our services, but the same URL for each step of the process. This means that our listeners will automatically know they are requesting the isolated service instead of the embedded model.

This means that your request payload actually becomes nondescript. We can essentially take any request that comes in any unrecognizable form and assume it is an initial request of the service. By unrecognizable, I mean that it does not contain any payload data that has any meaning to us. All consecutive requests will actually contain header data at the very least to communicate the session information.

This might seem like a risky proposition security-wise, but keep in mind that we will use a different mechanism for authentication, so we will be able to determine if this is one of our consumers and not some random visitor. There might be an issue if we were allowing anonymous consumer access to the service, but fortunately we have ruled out that option.

What has to be recognized is that, through this approach, we will be making an ideological decision to support invalid requests, assuming they are meant as the initial service request. This can be dangerous, since we will essentially never generate an error on an invalid request format. For this reason, it is best to still have some method for explicitly requesting even this initial step. This will pay dividends down the road when the consumer is trying to implement your service by providing a more accurate response to a request. If the consumer is requesting a reservation and receives the hotel availability UI, the consumer will have accidentally requested it explicitly, not just provided an invalid request of any type.

So, for our request, let's essentially utilize the header data that provides the session and state data. (See Listing 7-2.) For this initial request, we will require that the state element be set to 0, with the session ID only set if it is an existing session (thus not actually the first request from a user but another hotel availability search).

In terms of our schema hierarchy, we will not need a request defined for this interaction, only a response. Let's go ahead and define that response. Remember that this will be an XML document, but will need to define the UI. The easy option here is to simply provide XML-compliant HTML (or XHTML) as our response. However, this is not going to give us (or the consumer) the most flexibility.

A better option is to provide the data that makes up the interface through an XML data response and create an XSL document that can then structure the document into a form. This approach allows the consumer to utilize our format, but provides the consumer the flexibility to change it when more comfortable with Web services.

In the interface design for our isolated service, we simply need to define the data that makes up the interface. This produces response documents that are very similar to the embedded response, with additional information for validation purposes. Later, in the implementation section, we will concern ourselves with building the XSL document(s) that define the form for the user.

To define the data for our availability form response, we need to refer first to our hotel availability request schema. (See Listing 7-5.) This is the most fundamental definition for the user request, so this is what we must comply with to define a user interface. Through that schema we can define all the elements and their attributes that are pertinent to an HTML interface. (See Table 7-1.)

Table 7-1: Availability Form Elements and Attributes

ELEMENT

DATA TYPE

RANGE/LENGTH

VALUES

checkInDate

date

  

checkOutDate

date

  

numberAdults

numeric

1–4

 

numberChildren

numeric

0–4

 

city

text

max=30

 

state

text

min=2 max=2

 

landmark

text

max=50

 

couponCode

text

max=10

 

approxPrice

decimal

  

bedSize

Text

 

King

Queen

Double

smoking

boolean

 

True

False

amenities

string

 

Concierge

Workout facilities

Pool

Restaurant

24-hour room service

High speed

Internet access

Some of this information will be of no use to us in creating an HTML interface. For example, the data types for elements with defined values will not be necessary, since we can define a simple drop-down box or checkbox interface that controls the input values.

Remember that while we want to end up with an HTML interface, we need to define this information in XML, which can be transformed into the eventual HTML output. Because this process is so new, it is probably helpful to first generate an HTML page that takes into account each of these elements and their attributes. (See Listing 7-20.) This will help you to identify which data needs to be included in your XML.

Listing 7-20: Basic HTML output for an availability form

start example
 <html>       <body>       <form action="availability.asp" method="post">             Check-In Date<br/>             <select  name="check-in-month">                   <option value="1">January</option>                   ...                   <option value="12">December</option>             </select>             <select  name="check-in-day">                   <option value="1">1</option>                   ...                   <option value="31">31</option>             </select>             <select  name="check-in-year">                   <option value="2001">2001</option>                   <option value="2002">2002</option>             </select><br/>             Check-Out Date<br/>             <select  name="check-out-month">                   <option value="1">January</option>                   ...                   <option value="12">December</option>             </select>             <select  name="check-out-day">                   <option value="1">1</option>                   ...                   <option value="31">31</option>             </select>             <select  name="check-out-year">                   <option value="2001">2001</option>                   <option value="2002">2002</option>             </select>             <p/>             Number of Adults<br/>             <select  name="numAdults">                   <option selected="" value="1">1</option>                   <option value="2">2</option>                   <option value="3">3</option>                   <option value="4">4</option>             </select>             <p/>             Number of Children</br>             <select  name="numChild">                   <option selected="" value="0">0</option>                   <option value="1">1</option>                   <option value="2">2</option>                   <option value="3">3</option>                   <option value="4">4</option>             </select>             <p/>             City<input type="text"  name="city" maxlength="30" size="30"/>             <p/>             State<input type="text"  name="state" maxlength="2" size="2"/>             <p/>             Landmark             <input type="text"  name="landmark" maxlength="30"               size="30"/>             <p/>             Price Preference<BR/>             $<input type="approxPrice"  name="approxPrice"               maxlength="4" size="4"/>             <p/>             Coupon Code<BR/>             <input type="text"  name="coupon" maxlength="30" size="20"/>             <p/>             Smoking Preference<br/>             <input type="radio"  name="smokePref"               value=" Yes">Yes</input>             <input type="radio"  name="smokePref" value="No">No             </input>             <p/>             Bed Size<br/>             <select  name="bedSize">                  <option selected="" value="KING">King</option>                  <option value="QUEEN">Queen</option>                  <option value="DOUBLE">Double</option>             </select>             <p/>             Hotel Amenities<br/>             <input type="checkbox"  name="amenities" value="concierge">                Concierge</input >             <input type="checkbox"  name=" amenities" value="pool">               Pool</input >             <input type=" checkbox"  name=" amenities" value=" workout               facilities">Workout Facilities</input >             <input type=" checkbox"  name=" amenities"               value="24-hour room service">24-hour Room Service</input >             <input type="checkbox"  name="amenities" value="restaurant">                Restaurant</input>             <input type="checkbox"  name="amenities"               value=" high speed internet access">High Speed Internet Access</input >             <input type="submit" value="Check Availability">       </form>       </body> </html> 
end example

Viewing this code in a browser, you will see that this is truly a minimalist definition of the elements themselves, taking advantage of the attributes as appropriate. What we have done is made some of the decisions necessary to take a defined element and capture it through an HTML interface. (See Figure 7-16.)

click to expand
Figure 7-16: Browser view of hotel availability request form

The next step is to determine how we want to present this information in an XML format. If you look closely at our base HTML form document in Listing 7-20, you will see that it is already XML compliant. The question that then comes up is "Why not just send this as your response?" The problem is that although this will pass through all applications as valid XML, it is not accessible data. It is, after all, HTML and so is focused on presentation, not data definition. This is a problem if we want to work with individual elements or the components of those elements.

For instance, if the consumer wants to take the captions and values for these elements and place them in separate cells of a table layout, they will not be able to. We need to break this representation down to be able to work with the individual pieces.

You can think of this effort as combining the XML definition of this data and incorporating the HTML presentation elements that make up its interface. Let's look at the numberAdults element as an example. The XML definition of this data is the following:

 <xsd:element name="numberAdults">       <xsd:simpleType>             <xsd:restriction base="xsd:short">                   <xsd:minInclusive value="1"/>                   <xsd:maxInclusive value="4"/>             </xsd:restriction>       </xsd:simpleType> </xsd:element> 

The HTML interface defining this element is the following:

 Number of Adults<br/> <select  name="numAdults">      <option selected="" value="1">1</option>      <option value="2">2</option>      <option value="3">3</option>      <option value="4">4</option> </select> 

What we need to do is identify the elements that make up this interface and incorporate them into this element's data. Looking at the HTML code, we have a caption and four values to choose from. Incorporating this into a data centric XML document might yield the following results:

 <numberAdults maxValues="1">       <name>numberAdults</name>       <caption>Number of Adults</caption>       <option value="1" caption="1"/>       <option value="2" caption="2"/>       <option value="3" caption="3"/>       <option value="4" caption="4"/> </numberAdults> 

You see that we have defined the element, its name, an appropriate caption, and each acceptable value. We could have gotten away with not defining the name, but doing so does afford us the option of changing it later without affecting any existing implementations. This allows us to programmatically reference it as numberAdults regardless of how we name it in the HTML form.

Even though we have defined our values as options, this does not mean that this data must be exposed through a select box. Because this information is defined on a data level, this element could easily be displayed through radio buttons, or even a series of buttons. That is the flexibility we gain by having a data centric interface definition. I also defined a maxValues attribute for the numberAdults element so that it is understood that multiple values for this element are not legal. This eliminates checkboxes and other multiple selection mechanisms from being valid for this element.

Let's walk through another example of this process with the city field. First let's look at the schema definition for the city element, shown here:

 <xsd:element name="city">       <xsd:simpleType>             <xsd:restriction base="xsd:string">                   <xsd:maxLength value="30"/>             </xsd:restriction>       </xsd:simpleType> </xsd:element> 

Now let's look at the HTML-defined interface for the city element, shown here:

 City <input type="text"  name="city" maxlength="30" size="30"/> 

The size that we define in our HTML is extraneous and should really be defined later by our XSL, so we can drop that value. Otherwise, everything else pretty much carries over to our XML interface definition, as shown here:

 <city>       <name>city</name>       <caption>City</caption>       <maxLength>30</maxLength> </city> 

We will now take this same approach and apply it to each element in our form to design the payload portion of the availability response, as shown in Listing 7-21.

Listing 7-21: XML data payload for hotel availability form

start example
 <hotelAvailabilityForm>      <checkInDate>            <name>checkInDate</name>            <caption>Check-In Date</caption>            <checkInMonth maxValues="1">                  <name>checkInMonth</name>                  <option value="1" caption="Jan"/>                  ...                  <option value="12" caption="Dec"/>            </checkInMonth>            <checkInDay maxValues="1">                  <name>checkInDay</name>                  <option value="1" caption="1"/>                  ...                  <option value="31" caption="31"/>            </checkInDay>            <checkInYear maxValues="1">                  <name>checkInYear</name>                  <option value="2001" caption="2001"/>                  <option value="2002" caption="2002"/>            </checkInYear>      </checkInDate>      <checkOutDate>            <name>checkOutDate</name>            <caption>Check-Out Date</caption>            <checkOutMonth maxValues="1">                  <name>checkInMonth</name>                  <option value="1" caption="Jan"/>                  ...                  <option value="12" caption=" Dec"/>            </checkOutMonth>            <checkOutDay maxValues="1">                  <name>checkInDay</name>                  <option value="1" caption="1"/>                  ...                  <option value="31" caption="31"/>            </checkOutDay>            <checkOutYear maxValues="1">                  <name>checkInYear</name>                  <option value="2001" caption="2001"/>                  <option value="2002" caption="2002"/>            </checkOutYear>      </checkOutDate>      <numberAdults maxValues="1">            <name>numberAdults</name>            <caption>Number of Adults</caption>            <option selected="TRUE" value="1" caption="1"/>            <option value="2" caption="2"/>            <option value="3" caption="3"/>            <option value="4" caption="4"/>      </numberAdults>      <numberChildren maxValues="1">            <name>numberChildren</name>            <caption>Number of Children</caption>            <option selected="TRUE" value="0" caption="0"/>            <option value="1" caption="1"/>            <option value="2" caption="2"/>            <option value="3" caption="3"/>            <option value="4" caption="4"/>      </numberChildren>      <city>            <name>city</name>            <caption>City</caption>            <maxLength>30</maxLength>      </city>      <state>            <name>state</name>            <caption>State</caption>            <maxLength>2</maxLength>            <minLength>2</minLength>      </state>      <landmark>            <name>landmark</name>            <caption>Landmark</caption>            <maxLength>50</maxLength>      </landmark>      <approxPrice>            <name>approxPrice</name>            <caption>Price Preference</caption>            <maxLength>4</maxLength>      </approxPrice>      <smoking maxValues="1">            <name>smoking</name>            <caption>Smoking</caption>            <option value="TRUE" caption="Yes"/>            <option value="FALSE" caption="No"/>      </smoking>      <couponCode>            <name>couponCode</name>            <caption>Coupon Code</caption>            <maxLength>10</maxLength>      </couponCode>      <bedSize maxValues="1">            <name>bedSize</name>            <caption>Bed Size</caption>            <option value="King" caption="King"/>            <option value="Queen" caption="Queen"/>            <option value="Double" caption="Double"/>      </bedSize>      <hotelAmenities maxValues="*">            <name>amenity</name>            <caption>Hotel Amenities</caption>            <option value="Concierge" caption="Concierge"/>            <option value="Pool" caption="Pool"/>            <option value="Workout Facilities"caption=" Workout Facilities"/>            <option value="24-hour Room Service" caption="24-hour Room Service"/>            <option value="Restaurant" caption="Restaurant"/>            <option value="High Speed Internet Access" caption="High Speed Internet               Access"/>      </hotelAmenities> </hotelAvailabilityForm> 
end example

Unlike our previous responses, here we are defining XML instead of XML Schemas. This is because these responses are static. There is less need to develop a schema because we are not building this response dynamically. Our applications will basically skip the step of building an XML document against a schema and will reference the XML directly to build the response. That is not to say that we should not develop a schema for our response for integrity's sake. It will help us to utilize this data in other schemas, and it will help us if we want to make changes to this request in the future.

Tip 

If you ever find yourself needing to generate an XML Schema from an existing XML document, you should seriously consider getting one of the tools that has the ability to generate schemas, such as XML Spy. The end result from any tool will almost never be exactly what you want, since there are numerous ways to define a single XML document, but it will provide you with a significant head start to defining your schema.

Unfortunately, generating a schema directly off of this document won't help us to define other responses. This schema is very specific to the elements we defined: couponCode, city, checkInDate, etc. Unless we are defining these elements specifically, this schema is of little use. To build a schema that is generic enough to define all such responses would require making the responses themselves more generic. That would compromise their effectiveness, so we will not take on that effort.

We can, however, use the XML we did produce as a template. If you look through our availability form document, you see that we have two main element types, open entry (city, couponCode) and constrained values (checkInDate, hotelAmenities). All of our input data will fall into these categories, so we can reuse these structures for each element as appropriate. This not only helps reduce our design effort, but also helps us to remain consistent in the definition of our elements.

We now need to define the interaction schema for this availability form request. We will reference the schema we generated from our XML payload (I will forego listing that schema here because it has limited value to you). We will name this interaction availabilityFormInteraction, as shown in Listing 7-22.

Listing 7-22: Availability form interaction

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityForm.xsd"/>       <xsd:element name="availabilityFormInteraction">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element name="availabilityForm" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Notice that in this interaction we have only the response defined. This will require the availabilityFormInteraction to always be present, but to have a null value when it is a request. Next we need to add this interaction to our interface document, reservationWS, as shown in Listing 7-23.

Listing 7-23: The reservationWS interface schema with availability form interaction

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/serviceVariables.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityFormInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/detailInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationInteraction.xsd"/>       <xsd:element name="reservationWS">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="serviceVariables"/>                         <xsd:element ref="payload" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="payload">             <xsd:complexType>                   <xsd:choice>                         <xsd:element ref="availabilityFormInteraction"/>                         <xsd:element ref="availabilityInteraction"/>                         <xsd:element ref="detailInteraction"/>                         <xsd:element ref="reservationInteraction"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Reservation Form

The next interaction we need to address is the reservation form. This is very similar to the availability form in that we are using an XML document to define the data for a suitable UI. We are providing a definition for a data entry form that can be transformed with a stylesheet we reference in the interaction, as shown in Listing 7-24.

Listing 7-24: XML data payload for reservation form

start example
 <reservationForm>       <firstName>             <name>firstName</name>             <caption>First Name</caption>             <maxLength>20</maxLength>       </firstName>       <lastName>             <name>lastName</name>             <caption>Last Name</caption>             <maxLength>30</maxLength>       </lastName>       <homePhone>             <name>homePhone</name>             <caption>Home Phone Number</caption>             <maxLength>15</maxLength>       </homePhone>       <homeAddress>             <address>                   <name>address</name>                   <caption>Address</caption>                   <maxLength>30</maxLength>             </address>             <address>                   <name>address</name>                   <caption>Address</caption>                   <maxLength>30</maxLength>             </address>             <city>                   <name>city</name>                   <caption>City</caption>                   <maxLength>30</maxLength>             </city>             <state>                   <name>state</name>                   <caption>State</caption>                   <maxLength>2</maxLength>                   <minLength>2</minLength>             </state>             <zipCode>                   <name>zipCode</name>                   <caption>Zip-Code</caption>                   <maxLength>5</maxLength>                   <minLength>5</minLength>             </zipCode>       </homeAddress>       <couponCode>             <name>couponCode</name>             <caption>Coupon Code</caption>             <maxLength>10</maxLength>       </couponCode>       <cardNumber>             <name>cardNumber</name>             <caption>Credit Card Number</caption>             <maxLength>16</maxLength>       </cardNumber>       <nameOfOwner>             <name>nameOfOwner</name>             <caption>Name on Credit Card</caption>             <maxLength>50</maxLength>       </nameOfOwner>       <cardIssuer maxValues="1">             <name>cardIssuer</name>             <caption>Card Issuer</caption>             <option value="Visa" caption="Visa"/>             <option value="Mastercard" caption="Mastercard"/>             <option value="American Express" caption="American Express"/>             <option value="Discover" caption="Discover"/>       </cardIssuer>       <ccExpirDate>             <name>ccExpirDate</name>             <caption>Credit Card Expiration Date</caption>             <ccExpirMonth maxValues="1">                   <name>ccExpirMonth</name>                   <option value="1" caption="Jan"/>                   ...                   <option value="12" caption="Dec"/>             </ccExpirMonth>             <ccExpirYear maxValues="1">                   <name>ccExpirYear</name>                   <option value="2001" caption="2001"/>                   ...                   <option value="2006" caption="2006"/>             </ccExpirYear>       </ccExpirDate>       <billingAddress>             <address>                   <name>address</name>                   <caption>Address</caption>                   <maxLength>30</maxLength>             </address>             <address>                   <name>address</name>                   <caption>Address</caption>                   <maxLength>30</maxLength>             </address>             <city>                   <name>city</name>                   <caption>City</caption>                   <maxLength>30</maxLength>             </city>             <state>                   <name>state</name>                   <caption>State</caption>                   <maxLength>2</maxLength>                   <minLength>2</minLength>             </state>             <zipCode>                   <name>zipCode</name>                   <caption>Zip-Code</caption>                   <maxLength>5</maxLength>                   <minLength>5</minLength>             </zipCode>       </billingAddress> </reservationForm> 
end example

Like with the availability form, we will only require the service variables to be present in the request. However, in this case we will be running some validation of this information because it can never be the first request of a session. We know this because all reservations must be preceded by an availability request.

The schema shown in Listing 7-25 will define our reservation form interaction.

Listing 7-25: Reservation form interaction schema

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interface/hrsws/reservationForm.xsd"/>       <xsd:element name="reservationFormInteraction">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="reservationForm" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

Adding this interaction to our service will follow the same pattern as the other interactions, as shown in Listing 7-26.

Listing 7-26: The reservationWS interface schema with reservation form interaction

start example
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/serviceVariables.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityFormInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/availabilityInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/detailInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationFormInteraction.xsd"/>       <xsd:include schemaLocation="http://www.architectingwebservices.com/         interfaces/hrsws/reservationInteraction.xsd"/>       <xsd:element name="reservationWS">             <xsd:complexType>                   <xsd:sequence>                         <xsd:element ref="serviceVariables"/>                         <xsd:element ref="payload" minOccurs="0"/>                   </xsd:sequence>             </xsd:complexType>       </xsd:element>       <xsd:element name="payload">             <xsd:complexType>                   <xsd:choice>                         <xsd:element ref=" availabilityFormInteraction"/>                         <xsd:element ref=" availabilityInteraction"/>                         <xsd:element ref=" detailInteraction"/>                         <xsd:element ref=" reservationFormInteraction"/>                         <xsd:element ref=" reservationInteraction"/>                   </xsd:choice>             </xsd:complexType>       </xsd:element> </xsd:schema> 
end example

The final interaction we have to define is the reservation confirmation. Recall, that we identified this as an interaction because a UI typically confirms the collection of critical information (in this case personal and financial data). The consumer is typically responsible for this, but we are taking an active role in defining the interface for our isolated model, so we must come up with a solution.

Our goal is to simply confirm the data that was entered, so all of this data moving around seems a bit excessive. Rather than defining another interface, it is better to design a method for keeping the data on the client side until the information is confirmed.

We decided earlier that we would do this through the use of client-side script. We will have a pop-up window simply ask for a confirmation, with an option to edit the data if necessary as we saw in Figure 7-6. This will change the overall workflow by eliminating this interaction. (See Figure 7-17.) This helps improve the service's overall performance and reduces the implementation effort.

click to expand
Figure 7-17: Process flow for isolated service without confirmation interaction

This client-side script will be distributed through our style sheets, so we need to keep this functionality in mind when we design the XSL template for our reservation form.

That actually completes the modifications to our service to support the isolated service model. Before moving on to the rest of the interface layer, let's take a look at our updated schema hierarchy diagram in Figure 7-18.

click to expand
Figure 7-18: Physical interface schema hierarchy diagram with isolated interactions

Service Listener

The service listener is responsible for handling the incoming requests and routing them to the appropriate logic. Its sole responsibility is to take the incoming request and identify the pertinent information in it. I talked about this process at a high level in Chapter 2. Now it is time to look at the design of a listener, and specifically the listener for our reservation service.

Designing the listener involves four aspects:

  • Listener technology

  • Authentication method

  • Validation process

  • Data extraction

Listener Technology

We have a few options available for containing the listener itself. Regardless of the method chosen, the underlying requirement is that it resides on an HTTP(S) server. Without this service, a listener will not be able to receive any requests, which is obviously a necessity!

We are building this service on a Microsoft platform, so our HTTP server will be Internet Information Server (IIS). As I stated earlier, we will be establishing two sites for our service, one each for the embedded and isolated models. However, only the listener itself will be duplicated; the rest of the logic and functionality will be reused for both services.

Note 

This presents us with the conundrum of whether we are providing two Web services or one. Does an interface equate to a Web service, or can a single Web service support more than one interface? A good argument can be made either way, and I probably utilize different approaches when they are convenient for the discussion. It really comes down to whose perspective you are taking: the consumer's or the provider's. Officially, I would have to say that the perspective of the consumer is the one that matters most, so each interface should define a Web service.

The recipient of the request can be either an ASP (Active Server Pages) page or an ISAPI (Information Server API) extension. For the sake of keeping this example simple, we will use an ASP page containing VBScript code. For a production system that is expected to support a heavy request load, an ISAPI component will provide a better solution.

Even though the listener itself is an ASP page, it does not require that all logic be contained in it. The same is true of an ISAPI-based design. In fact, it is better to make the listener, regardless of form, as light and generic as possible for performance and reuse purposes.

Exposing a light listener is especially pertinent to this Web service because we will have two different sites to host each model of our service. If our listener contains a lot of logic, that will be duplicated under each site, which makes maintenance less efficient. We should design listeners that receive the request, capture which site it came through, and pass it on to the other functionality in the service.

Now that we have determined what the makeup of our listener will be, let's look at the specific tasks we need to accomplish in it.

Authentication Method

Both the authentication method and validation process are components of our security model, but their responsibility falls in the interface layer. For that reason, I have included their design in this layer of our Web service solution.

We determined in our security model that we would be using a certificate system for identifying our consumers. However, identification does not equal authentication, so we will need to check the identified consumer against a data source. This will validate the consumer as a current, legal user of this service.

For this service, we will use a reference table to contain all of our consumer information. The methodology for referencing this information can take one of two approaches. The first is an optimistic approach that does an initial lookup for every new session and maintains the consumer indirectly through the session. This approach puts a lot of trust into the session, since the session is not validated with the consumer's certificate for every request. The other approach is more pessimistic in that it verifies the consumer against every call in a session. The tradeoff here is a higher level of security but slower performance. Even though the performance of such a solution should be acceptable for a moderate amount of Web service traffic, there are caching technologies that could be used to help with this lookup (such as LDAP or the COM+ shared property manager). For the sake of this exercise, we will just reference the database directly.

Caution 

Choose carefully when considering caching technologies for your Web services. There is always a trade-off of performance for scalability whenever a cache system is utilized and that trade-off will usually be realized very quickly once a threshold is reached.

Validation Process

Once we have determined that the request comes from an eligible consumer, we need to execute a validation of it. This validation includes not only the structure, but potentially the data itself as well. Specifically, we will be ensuring that any session IDs and stages included in our service variables are valid. This should also be self-contained in some form so that it can be reused. Ideally, it is generic enough to handle any request for validation.

As discussed in Chapter 3, the term valid XML is very ambiguous. There are actually different ways of validating XML, and we need to perform each step for the requests coming into our Web service. Furthermore, these steps build on each other in such a way that you cannot perform the second step without verifying the first, and so on. The three steps are the following:

  • XML well-formedness

  • XML validity

  • Data validity

The first step to validating the request is ensuring that a well-formed XML document has been sent in the request. This is a validation not of the content, but simply of the structure of the document and the XML data itself. An illegal XML document could cause catastrophic failure in your application, so you want to catch this as soon as possible. The longer you go without running this check, the more code you will have to produce to handle an error if it occurs. Fortunately, the MSXML parser automatically checks for well-formedness and allows us to handle the error as gracefully as possible.

Once the document's well-formedness has been established, the data in it needs to be validated. This is where our schemas become very valuable. Once the document is loaded into the DOM, we can validate its contents against the appropriate schema. Keep in mind that this validates the data types, data structure, and enumerated data, but not the rest of the data.

Validating the data in the document requires programmatic logic in the application. This is where we verify that the data values in total are valid. An example of an invalid document in this sense is a consumer making a request against another consumer's session. This is something schemas are not capable of handling, and so it falls to our application, and more specifically the data extraction component of our listener.

Data Extraction

There is a necessary blending of activities between the data extraction and validation logic. After all, you have to extract the data to validate it. To keep the two distinct, just think of the validation process as the logical process that incorporates the error handling for the data extraction process.

The data that needs to be extracted varies from call to call because the payloads are different. One of the constants in our Web service is the serviceVariables element. Every call includes this data so that the request session and activity can be quickly identified. For this reason, the data extraction should be separate from the conditional data extraction for maximum efficiency and reusability.

Now that we have identified each of the stages in the service listener, let's put this together in a flow diagram. (See Figure 7-19.) This flow diagram is a significant portion of our listener design because it shows the precedence that must take place in its logic.

click to expand
Figure 7-19: Flow diagram for the service listener

Service Responder

Whereas the service listener is focused on taking the request and validating it, the service responder's sole responsibility is to provide the response to be returned to the consumer. For our hotel reservation service, this response has three steps:

  • Identify the service type

  • Define the workflow

  • Build the response

Identify the Service Type

Because we are supporting two different services through our logic, one of the key pieces of information critical to get to the responder is the service type requested. This tells us whether we are responding to a consumer who is using the embedded service or the isolated service. The service type is critical because it affects the payload, what the workflow is, and what information is provided in the response.

As an outcome of the service type, we may need to modify the stage requested by the consumer. Since the isolated service has five stages and the isolated service three, we have to determine how to handle this. We have already designed the process so that the three overlapping requests are identical. However, stage one of the embedded service is actually stage two of the isolated service. We could try to keep these separate and manage them independently, but that would likely create some confusion during development and testing. Instead, we could map the embedded requests to the isolated requests once as they come in. This remains internal to the service, so there should be no confusion to the consumer. This actually helps to maintain the distinct identities of our two services. We just have to make sure that we map it back correctly when we build our response! We will discuss this in more detail during implementation.

Define the Workflow

The workflow for our service manages the information that is utilized to make requests of data sources, components, and other external resources. As we saw in our payload model, we essentially have a single, large workflow defined in our service. This provides all the functionality needed for an isolated service type request. The embedded service type will then use a subset of these.

Most workflow is specific to each request in the service's process, but some of the workflow can be overhead that applies to all requests. This doesn't apply in our case because the functionality is all request type specific. Let's walk through these workflow steps again to determine what they need to accomplish in their workflow. This includes identifying the key logic as well as any external references.

Availability Form

The availability form is the UI provided to the consumer as assistance to collecting the pertinent information. This is a static response that is not consumer or session specific because it collects the information necessary to simply reference our Web service.

Because this is not a dynamic response, we can define a physical document and have the workflow simply respond to the consumer with it as the bulk of the payload. This document can be referenced from the database, a cache, or even a physical file. This determination should be made based on scalability and performance considerations for the anticipated volume of traffic. For convenience's sake, we will keep the document as a physical file for this Web service.

Another topic for consideration was whether this document should be formatted or reference an XSL stylesheet like the dynamic responses. The idea here is that this dynamic transformation would be overhead for a static document. Even though this is a response defining a UI, I would still like to provide the option to the consumers for taking a data-level definition of the interface to make their own changes to it. Several techniques available are to the consumer for caching this resulting transformation to keep performance from being a big issue. We will look at these in Chapter 8.

Availability Request

This request to check the room availability for our hotel chain ultimately needs to get to our existing hotel reservation system (HRS). It is the data store and logical processor that determines whether there are hotels that match the criteria provided by the user and whether these hotels have rooms available.

If you recall, we actually have to make two requests of the HRS system to accomplish our availability request. There is one class that exposes all the necessary methods for us: reservationSystem.clsHRS. One method (fetchAvailability) takes in the criteria and runs the actual availability search against its database. However, this comes back with very raw data, identifying the hotel by ID, along with some price, room type, and proximity data. We have to take the hotel IDs that are returned and look up the hotel-specific data, such as name and location. That request is made through the fetchHotel method call.

Like I mentioned earlier, we will encapsulate this process into our own function, so we will not be referencing these two methods independently from our workflow. This breaks it out of the rest of our responder for the sake of efficiency. We will call this method wsFetchAvailability, and it will return the data in the appropriate XML format to append to our response payload.

Part of our workflow will involve building the request to the HRS system. Since the interface is a DCOM object call and we are getting the data in XML format, we have a little bit of work to do, especially when you consider that we may have multiple instances of a single element that needs to get transformed into a variant array (addresses, amenities).

Hotel Detail View

The hotel content is something of the service's own creation. Fortunately, this is a fairly simple process that we can execute in a number of ways. We can take the same approach as for the availability form request and reference a static XML document, but that is a tad inefficient!

A much better method is to define a data model to contain this data (which we will do in designing the data layer in the next section), create a stored procedure, and write the simple logic to reference it. The method we define for our hotel content retrieval will be named wsFetchHotelContent, and it will return the data in the appropriate XML document structure to append to our response payload.

Reservation Form

We will handle the reservation form in the same way that we handled the availability form. We will reference a physical file that contains the XML data defining our interface and then use the same process as the other responses in adding the stylesheet reference to the serviceVariables element for transformation on the consumer's side.

Reservation Request

This request also needs to be handled by the existing HRS system, but unlike the availability request, a single method call (makeReservation) takes care of the entire process. Also like the availability request, we need to deal with creating variant arrays from duplicate XML data nodes.

One additional catch to the makeReservation method is that it requires our room type value to be an integer. The fetchAvailability method returned the room type as a string (Suite, Value, etc.). Consider it a quirk in the interface of the HRS system. It just means that we need to have our own internal mappings of the integer values with the string values. There are only four possible values, and it isn't expected to change, so that is not a big task.

To take a better look at all of this functionality in the service responder, we should put together a flow diagram based on the physical components we have identified to visualize the solution. (See Figure 7-20.) This will help us to identify any potential bottlenecks or any excessive shuffling of data between logic.

click to expand
Figure 7-20: Process diagram for the service responder workflow

Build the Response

The final step of the service responder is to build the response to the consumer. Since much of the workflow has built the data to be contained in the payload, the building of the response just needs to append it to the appropriate header data and make any other necessary additions or modifications.

The service variables can typically be referenced from the request itself. After all, the response is still the same session and stage in the process. The one exception to this is when the request is the first of a session. In these cases, no session has been established. We need to create one and set the session element value accordingly.

Another twist in our response build process is the support of two different service models. One service has three steps and the other five. I mentioned earlier that we modified the request coming in appropriately to route the request to the appropriate workflow. Now we simply need to map it back to the consumer's "view" of the service.

The final step in building our response is determining whether to add the reference for the XSL template to the service variables. This check can actually be incorporated into our stage-mapping logic, since the opposite state requires this modification. This way, if the request is embedded, the stage is mapped, and if it is isolated, the XSL reference is added.

This completes the interface layer design. That leaves only the data layer left before we move on to the actual implementation.

Data Layer

Most Web services that expose functionality provided by existing systems have a very small data layer. The only data we need to concern ourselves with maintaining is the information we need to support the Web services process itself. This typically comes down to session data, consumer data, an audit trail, and perhaps content data.

For our reservation Web service we have two main models to design: session and hotel content.

The Session Data Model

The first model to define is the session and consumer relationship. This is how we will maintain the various user sessions with our consumers. This involves two entities, the session and the consumer, which have a one-to-many relationship because each consumer can be supporting multiple users in our service.

Additionally, we need to keep track of the availability requests that come through the system. This contains all the specific information about the trip the user has requested, such as date of arrival and departure, the number of occupants, and other request details. This keeps us from having to overload the room reservation request with every piece of information again. This data can also act as an audit trail for troubleshooting and debugging during testing and implementation.

This information only needs to be persisted on a per session basis, so some type of archival strategy is also called for on this availability request data. Otherwise, one record per session is all that is required and is either created on the first request or updated on subsequent availability requests. Therefore, requests will be tied to the sessions in a one-to-one relationship.

So we have three entities defined in total for our session-consumer model: consumer, session, and availability request. The consumer table will not only act as a reference table for our sessions, but also can be used to validate the consumer requests coming in. For this service, we are using client certificates to identify the consumer. Since we are not identifying the consumer in our service variables, we need to relate these certificates to our consumer data so that we can properly create and maintain our sessions. There are a number of data fields we could cross-reference, but let's save that decision for later. We will just recognize for now that we need a field to cross-reference our consumers with their certificates.

Our session entity will simply reference the consumer to which it belongs and have a key field. Since we know we will want to archive them, let's go ahead and add a date/time stamp as well.

The availability request will be a little larger, since it contains some of the user's search criteria. The search criteria we will capture are the following:

  • Check-in date

  • Check-out date

  • Number of adults

  • Number of children

  • Coupon code

  • Smoking preference

We will also add a search date field so that we can efficiently archive this data as a history of activity on the service. Notice that we left out location, price, bed size, and the hotel amenities from the search criteria. The purpose of storing these criteria is to support a potential reservation request and some of the availability criteria will not be needed. A reservation request will be made through a hotel ID and a room type, which will inherently define that information. For example, a room at a hotel has a given price so we don't need to store it. However, we do need to store the coupon code so we can apply any discounts. Putting all of this together gives us a data model that allows us to keep track of our consumers, users, and their activities. (See Figure 7-21.)

click to expand
Figure 7-21: Session-consumer data model

The Content Data Model

The next model we need to design is the content model. One of our service requests is the retrieval of hotel-specific content, including descriptions and pictures. The existing system does not contain any content, just data, so it is up to us to develop a separate source to contain and reference in our service. This is an excellent example of how you might need, or want, to augment an existing system to create a robust Web service.

For our data, we have a number of options on how we can model this relationship. Each hotel will have five pictures and one description. Of course, experience might tell us that rarely are such business rules adhered to, especially when dealing with marketing material. Inevitably, some hotels are likely to not have all five pictures, or some will have more.

Therefore, we should design a data model that is very flexible. To gain this flexibility, we simply normalize our data by defining the content, not the hotel, as the entity. This turns every piece of content into a record, with hotels having multiple records. We can then externalize the content names so that we can have common identifiers of each content item. (See Figure 7-22.)

click to expand
Figure 7-22: Hotel content data model

Since hotels are "defined" by the reservation system, there really is no need to have a separate hotel entity. We are simply defining content based on external hotel IDs, so defining the content is sufficient.

We will also need some individual reference tables for our Web service. These will be used individually to either help map individual elements or serve as lookup tables. One example of this is the HRS_Errors table. This table contains a list of the error codes the HRS system could return. We will have a corresponding column that contains the description text we want to present back to the consumer/user. This table is not related to any other data entity, but simply enhances the user experience of the service. I will discuss these tables as I walk you through the implementation of the Web service.

This completes the data layer design for our Web service. The data layer was also the last design component remaining in our overall design, so we are now ready to move on to implementing the Web service!




Architecting Web Services
Architecting Web Services
ISBN: 1893115585
EAN: 2147483647
Year: 2001
Pages: 77

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net