views:

545

answers:

2

Hi all.

My situation is as follows:

I have a normalized database, in which I hold geographic information about airports. The structure is:

airport --is in--> city --is in--> country --is in--> continent

Now I want to let users administrate this data, without giving them direct access to the database. We need to offer this administration interface via a web service.

Now, when it comes to designing the service, we ran into the discussion about how to define the operations. We came up with different solutions:

Solution A: specific operations

For each of the four tables (airport, city, country, continent) we define 3 operations:

  • insert
  • get
  • update

This would lead to 12 operations with 2 request/response objects = 24 objects

To create an all new airport with all dependencies, at least 4 requests would be necessary.

Solution B: generic

There is only one operation, which is controlled via parameters. This operation is capable of creating everything needed to administer the database.

The operation would decide what needs to be done and executes it. If an error occures, it will roll back everything.

==> 1 Operation = 2 highly complex request/response-objects

Solution C: Meet in the middle 1

One generic operation per table, which is capable of executing get, insert, update, just like solution B, but focused on one table each.

==> 4 operations = 8 complex request/response-objects

Solution D: Meet in the middle 2

One generic operation per action (get, insert, delete), which can work on each table and resolve dependencies.

==> 3 operations = 6 slightly more complex request/response-objects

Example

Since this was rather abstract, hier a simplified example for request-objects for creating (JFK/New York/USA/North America):

Solution A:

Request 1/4:

<insertContinent>North America</insertContinent>

Request 2/4:

<insertCountry continent="North America">USA</insertCountry>

Request 3/4:

<insertCity country="USA">New York</insertCity>

Request 4/4:

<insertAirport city="New York">JFK</insertAirport>

Solution B:

Request 1/1:

<action type="insertCountry" parent="North America">USA</action>
<action type="insertAirport" parent="New York">JFK</action>
<action type="insertContinent" parent="">North America</action>
<action type="insertCity" parent="USA">New York</action>

Solution C:

Request 1/4:

<countryAction type="insert" parent="North America">USA</countryAction>

Request 2/4:

<airportAction type="insert" parent="New York">JFK</airportAction>

Request 3/4:

<continentAction type="insert" parent="">North America</continentAction >

Request 4/4:

<cityAction type="insert" parent="USA">New York</cityAction >

Solution D: Request 1/1:

<insert airport="JFK" city="New York" country="USA" continent="North America" />

Solution D seems rather elegant for me, therefore I tried to put this in XSD:

Code:

<complexType name="NewContinent">
 <sequence>
  <element name="NAME" type="string"></element>
 </sequence>
</complexType>

<complexType name="NewCountry">
 <sequence>
  <element name="ISOCODE" type="string"></element>
  <element name="NAME" type="string"></element>
  <choice>
   <element name="newCONTINENT" type="tns:NewContinent"></element>
   <element name="CONTINENT" type="string"></element>
  </choice>
 </sequence>
</complexType>

<complexType name="NewCity">
 <sequence>
  <element name="IATA" type="string"></element>
  <element name="NAME" type="string"></element>
  <choice>
   <element name="COUNTRY" type="string"></element>
   <element name="newCOUNTRY" type="tns:NewCountry"></element>
  </choice>
 </sequence>

</complexType>

<complexType name="NewAirport">
 <sequence>
  <element name="IATA" type="string"></element>
  <element name="NAME" type="string"></element>
  <choice>
   <element name="CITY" type="string"></element>
   <element name="newCITY" type="tns:NewCity"></element>
  </choice>
 </sequence>

</complexType>

A corresponding request would then look like follows:

<complexType name="Request">
 <choice>
  <element name="AIRPORT" type="tns:NewAirport"></element>
  <element name="CITY" type="tns:NewCity"></element>
  <element name="COUNTRY" type="tns:NewCountry"></element>
  <element name="CONTINENT" type="tns:NewContinent"></element>
 </choice>
</complexType>

Now my question: Is this really the best solution available? Is the XSD enough to understand, what is going on?

Best regards and TIA!

+3  A: 

Presumably you are writing a protocol layer that will understand your different message types. You will also need an application layer to parse the contents of the message. The different approaches you mention will shift the burden of parsing between these two layers. So for example:

Solution A: The protocol layer does all the parsing and returns the data and command. The application layer can just use the data. This is also known as the RPC pattern.

Pros: You can validate your messages. You can map messages directly to application calls.

Cons: If you need to make a change to the interface, your protocol changes.

Solution B: The protocol layer returns two values and a command. The application layer must use the command to parse the values into types.

Pros: The protocol never changes.

Cons: You can't validate messages. Your application code is more complicated.

Solution C: The protocol layer returns two known types and an command that must be parsed. The application layer can just parse the command and use the data.

Pros: I can't think of any, seems like not a very good compromise.

Cons: Leaves the parsing only partially done.

Solution D: The protocol layer returns known types (the way you implemented it) and a generic command. The application layer must look at the data it receives and convert the generic command into a specific command. This is similar to the REST architecture.

Pros: Calls are distinct operations so that you could for example cache get responses.

Cons: Complexity in the application layer

The REST model is usually implemented differently than you have outlined. It uses HTTP GET, POST, PUT, DELETE messages to communicate arbitrary documents. Parameters are given as part of the URL. So for example:

<insert airport="JFK" city="New York" country="USA" continent="North America" />

becomes

<insert URL="airport?city=Chicago">ORD</insert>

Or if you are using HTTP it becomes a POST request to an airport URL with a param of the city with the contents being information about the airport. Note that some of this becomes clearer with more compliated data where you have multiple elements and mixed types. For example if you wanted to send the airport abbreviation, long name, and altitude.

I think the REST architecture could work quite well for the interface you describe. As long as all you need to do is support the CRUD operations. The are many sites that will give you the pros and cons of the REST architectural style.

Personally I prefer the RPC style (Solution A) with some REST-ful attributes. I want the protocol to do the parsing work and validate the messages. This is typically how people implement SOAP web service interfaces.

Your interface may look simple today but tomorrow one of your customers is going to ask you for a new call that doesn't fit the REST model so well and you'll find yourself wedging it into the existing four messages.

EdmundG
+1  A: 

This is an old question, and I'm sure the service has been written a long time ago, but I wanted to contribute an answer anyway.

The RESTful approach would be to define an airport resource, such as this:

<airport href="/airports/JFK">
    <name>JFK</name>
    <city>New York</city>
    <country>USA</country>
    <continent>North America</continent>
</airport>

Or, if you want to use a browser-compatible microformat:

<div class="object airport" href="/airports/JFK">
    <ul class="attributes"> 
        <li class="name">JFK</li>
        <li class="city">New York</li>
        <li class="country">USA</li>
        <li class="continent">North America</li>
    </ul>
</div>

This resource would be located at a URI like /airports/JFK, which would be retrieved with a GET method, updated with a PUT method, and deleted with a DELETE method.

In a design like this, the URI /airports/ would represent a container resource for all of the airports in the database, and URIs like /airports/?city=New+York and /airports/?country=USA would be filters on the container to return a subset of the airports. Both of these would be GET methods, and the resources would contain a list of airport resources as defined above, either in full (since they're small) or with a few useful attributes and the href that points to the full resource for each airport.

Finally, adding a new resource could either be a PUT method on the airport's full URI, or a POST method on /airports/. In both cases the body of the request is the airport resource as shown above. The difference between the methods is who gets to decide the final URI for the airport: the client decides for PUT and the service decides for POST. Which one you use depends upon whether or not your clients can reasonably determine the right URI. Usually the service decides because the URIs contain a numeric unique identifier and the service has to choose that.

Now of course your original question was about SOAP, not REST. I would go ahead and set up a RESTful design as I've described, then describe my resources as complex types using XSD and a SOAP service with actions that duplicate the GET, PUT, DELETE, and POST operations of the RESTful service. This will give you the RPC equivalent of:

class Airport
    has String name
    has String city
    has String country
    has String continent
    method void update(name, city, country, continent)
    method void delete()

class AirportList
    method Airport[] get(opt name, opt city, opt country, opt continent)
    method void add(name, city, country, continent)
DougWebb