Chapter 4. CRUD Web Services

IN THE PRECEDING CHAPTER, we saw how GET and POST can be used to tunnel information to remote services through URIs and how POST can be used to transfer XML documents between services. However, as more interesting distributed system scenarios emerge, we rapidly reach the limit of what we can accomplish with those verbs. We need to expand our vocabulary in order to support more advanced interactions.

In this chapter, we’ll introduce two new HTTP verbs to our repertoire: PUT and DELETE. Alongside GET and POST, they form the set of verbs required to fully support the Create, Read, Update, Delete (CRUD) pattern for manipulating resources across the network.

Note

From here onward, we consider the network and HTTP as an integral part of our distributed application, not just as a means of transporting bytes over the wire.

Through CRUD, we’ll take our first steps along the path to enlightenment using HTTP as an application protocol instead of a transport protocol, and see how the Web is really a big framework for building distributed systems.

Modeling Orders As Resources

In Restbucks, orders are core business entities, and as such, their life cycles are of real interest to us from a CRUD perspective. For the ordering parts of the Restbucks business process, we want to create, read, update, and delete order resources like so:

  • Orders are created when a customer makes a purchase.

  • Orders are frequently read, particularly when their preparation status is inquired.

  • Under certain conditions, it may be possible for orders to be updated (e.g., in cases where customers change their minds or add specialties to their drinks).

  • Finally, if an order is still pending, a customer may be allowed to cancel it (or delete it).

Within the ordering service, these actions (which collectively constitute a protocol) move orders through specific life-cycle phases, as shown in Figure 4-1.

Possible states for an order
Figure 4-1. Possible states for an order

Each operation on an order can be mapped onto one of the HTTP verbs. For example, we use POST for creating a new order, GET for retrieving its details, PUT for updating it, and DELETE for, well, deleting it. When mixed with appropriate status codes and some commonsense patterns, HTTP can provide a good platform for CRUD domains, resulting in really simple architectures, as shown in Figure 4-2.

CRUD ordering service high-level architecture
Figure 4-2. CRUD ordering service high-level architecture

While Figure 4-2 exemplifies a very simple architectural style, it actually marks a significant rite of passage toward embracing the Web’s architecture. In particular, it highlights the use of URIs to identify and address orders at Restbucks, and in turn it supports HTTP-based interactions between the customers and their orders.

Since CRUD services embrace both HTTP and URIs, they are considered to be at level two in Richardson’s maturity model. Figure 4-3 shows how CRUD services embrace URIs to identify resources such as coffee orders and HTTP to govern the interactions with those resources.

CRUD services reach level two on Richardson’s maturity model
Figure 4-3. CRUD services reach level two on Richardson’s maturity model

Level two is a significant milestone in our understanding. Many successful distributed systems have been built using level two services. For example, Amazon’s S3 product is a classic level two service that has enabled the delivery of many successful systems built to consume its functionality over the Web. And like the consumers of Amazon S3, we’d like to build systems around level two services too!

Building CRUD Services

When you’re building a service, it helps to think in terms of the behaviors that the service will implement. In turn, this leads us to think in terms of the contract that the service will expose to its consumers. Unlike other distributed system approaches, the contract that CRUD services such as Restbucks exposes to customers is straightforward, as it involves only a single concrete URI, a single URI template, and four HTTP verbs. In fact, it’s so compact that we can provide an overview in just a few lines, as shown in Table 4-1.

Table 4-1. The ordering service contract overview

Verb

URI or template

Use

POST

/order

Create a new order, and upon success, receive a Location header specifying the new order’s URI.

GET

/order/{orderId}

Request the current state of the order specified by the URI.

PUT

/order/{orderId}

Update an order at the given URI with new information, providing the full representation.

DELETE

/order/{orderId}

Logically remove the order identified by the given URI.

The contract in Table 4-1 provides an understanding of the overall life cycle of an order. Using that contract, we can design a protocol to allow consumers to create, read, update, and delete orders. Better still, we can implement it in code and host it as a service.

Note

What constitutes a good format for your resource representations may vary depending on your problem domain. For Restbucks, we’ve chosen XML, though the Web is able to work with any reasonable format, such as JSON or YAML.

Creating a Resource with POST

We first saw HTTP POST in Chapter 3, when we used it as an all-purpose transfer mechanism for moving Plain Old XML (POX) documents between clients and servers. In that example, however, the semantics of POST were very loose, conveying only that the client wished to “transfer a document” to the server with the hope that the server would somehow process it and perhaps create a response document to complete the interaction.

As the Restbucks coffee ordering service evolves into a CRUD service, we’re going to strengthen the semantics of POST and use it as a request to create an order resource within the service. To achieve this, the payload of the POST request will contain a representation of the new order to create, encoded as an XML document. Figure 4-4 illustrates how this works in practice.

Creating an order via POST
Figure 4-4. Creating an order via POST

In our solution, creating an order at Restbucks requires that we POST an order representation in XML to the service.[28] The create request consists of the POST verb, the ordering service path (relative to the Restbucks service’s URI), and the HTTP version. In addition, requests usually include a Host header that identifies the receiving host of the server being contacted and an optional port number. Finally, the media type (XML in this case) and length (in bytes) of the payload is provided to help the service process the request. Example 4-1 shows a network-level view of a POST request that should result in a newly created order.

Example 4-1. Creating a coffee order via POST
POST /order HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 239

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

Once the service receives the request, its payload is examined and, if understood, dispatched to create a new order resource.

Note

The receiving service may choose to be strict or lax with respect to the syntactic structure of the order representation. If it is strict, it may force compliance with an order schema. If the receiving service chooses to be lax (e.g., by extracting the information it needs through XPath expressions), its processing logic needs to be permissive with respect to the representation formats that might be used.

Robust services obey Postel’s Law,[29] which states, “be conservative in what you do; be liberal in what you accept from others.” That is, a good service implementation is very strict about the resource representations it generates, but is permissive about any representations it receives.

If the POST request succeeds, the server creates an order resource. It then generates an HTTP response with a status code of 201 Created, a Location header containing the newly created order’s URI, and confirmation of the new order’s state in the response body, as we can see in Example 4-2.

Example 4-2. Response to successful order creation
HTTP/1.1 201 Created
Content-Length: 267
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:45:03 GMT
Location: http://restbucks.com/order/1234

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
  <status>pending</status>
</order>

The Location header that identifies the URI of the newly created order resource is important. Once the client has the URI of its order resource, it can then interact with it via HTTP using GET, PUT, and DELETE.

While a 201 Created response is the normal outcome when creating orders, things may not always go according to plan. As with any computing system—especially distributed systems—things can and do go wrong. As service providers, we have to be able to deal with problems and convey helpful information back to the consumer in a structured manner so that the consumer can make forward (or backward) progress. Similarly, as consumers of the ordering service, we have to be ready to act on those problematic responses.

Fortunately, HTTP offers a choice of response codes, allowing services to inform their consumers about a range of different error conditions that may arise during processing. The Restbucks ordering service has elected to support two error responses when a request to create a coffee order fails:

  • 400 Bad Request, when the client sends a malformed request to the service

  • 500 Internal Server Error, for those rare cases where the server faults and cannot recover internally

With each of these responses, the server is giving the consumer information about what has gone wrong so that a decision can be made on how (or whether) to make further progress. It is the consumer’s job to figure out what to do next.

Note

500 Internal Server Error as a catchall error response from the ordering service isn’t very descriptive. In reality, busy baristas might respond with 503 Service Unavailable and a Retry-After header indicating that the server is temporarily too busy to process the request. We’ll see event status codes and how they help in building robust distributed applications in later chapters.

When the ordering service responds with a 400 status, it means the client has sent an order that the server doesn’t understand. In this case, the client shouldn’t try to resubmit the same order because it will result in the same 400 response. For example, the malformed request in Example 4-3 doesn’t contain the drink that the consumer wants, and so cannot be a meaningful coffee order irrespective of how strict or lax the server implementation is in its interpretation. Since the <name> element is missing, the service can’t interpret what kind of drink the consumer wanted to order, and so an order resource cannot be created. As a result, the service must respond with an error.

Example 4-3. A malformed order request
POST /order HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 216

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

On receiving the malformed request, the server responds with a 400 status code, and includes a description of why it rejected the request,[30] as we see in Example 4-4.

Example 4-4. Response to a malformed order request
HTTP/1.1 400 Bad Request
Content-Length: 250
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:48:11 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <!-- Missing drink type -->
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

To address this problem, the consumer must reconsider the content of the request and ensure that it meets the criteria expected by the ordering service before resubmitting it. If the service implementers were being particularly helpful, they might provide a textual or machine-processable description of why the interaction failed to help the consumer correct its request, or even just a link to the service’s documentation. The ordering service won’t create an order in this case, and so retrying with a corrected order is the right thing for the consumer to do.

In the case of a 500 response, the consumer may have no clear understanding about what happened to the service or whether the request to create an order succeeded, and so making forward progress can be tricky. In this case, the consumer’s only real hope is to try again by repeating the POST request to lodge an order.

Note

In the general case, consumers can try to recompute application state by GET ting the current representations of any resources whose URIs they happen to know. Since GET doesn’t have side effects (that consumers can be held accountable for), it’s safe to call repeatedly until a sufficiently coherent picture of the system state emerges and forward or backward progress can be made. However, at this stage in our ordering protocol, the consumer knows nothing other than the entry point URI for the coffee ordering service, http://restbucks.com/order, and can only retry.

On the server side, if the ordering service is in a recoverable state, or may eventually be, its implementation should be prepared to clean up any state created by the failed interaction. That way, the server keeps its own internal order state consistent whether the client retries or not.

Implementing create with POST

Now that we have a reasonable strategy for handling order creation, let’s see how to put it into practice with a short code sample (see Example 4-5).

Example 4-5. A Java servlet implementation for creating a coffee order
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
  try {
    Order order = extractOrderFromRequest(request);
    if(order == null) {
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    } else {
      String internalOrderId = saveOrder(order);
      response.setHeader("Location", computeLocationHeader(request,
                         internalOrderId));
      response.setStatus(HttpServletResponse.SC_CREATED);
    } catch(Exception ex) {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}

The Java code in Example 4-5 captures the pattern we’re following for processing a POST request on the service side. We extract an order from the POST request content and save it into a database. If that operation fails, we’ll conclude that the request from the consumer wasn’t valid and we’ll respond with a 400 Bad Request response, using the value SC_BAD_REQUEST. If the order is successfully created, we’ll embed that order’s URI in a Location header and respond with a 201 status (using the SC_CREATED value) to the consumer. If anything goes wrong, and an Exception is thrown, the service returns a 500 response using the SC_INTERNAL_SERVER_ERROR value.

Reading Resource State with GET

We’ve already seen how GET can be used to invoke remote methods via URI tunneling, and we’ve also seen it being used to recover from 500 response codes during order creation. From here onward, we’ll be using GET explicitly for retrieving state information—resource representations—from services. In our case, we are going to use GET to retrieve coffee orders from Restbucks that we’ve previously created with POST.

Using GET to implement the “R” in CRUD is straightforward. We know that after a successful POST, the service creates a coffee order at a URI of its choosing and sends that URI back to the consumer in the HTTP response’s Location header. In turn, that URI allows consumers to retrieve the current state of coffee order resources, as we see in Figure 4-5.

Reading a coffee order with GET
Figure 4-5. Reading a coffee order with GET

Performing GET on an order’s URI is very simple. At the HTTP level, it looks like Example 4-6.

Example 4-6. Requesting an order via GET
GET /order/1234 HTTP/1.1
Host: restbucks.com

If the GET request is successful, the service will respond with a 200 OK status code and a representation of the state of the resource, as shown in Example 4-7.

Example 4-7. Reading an order with GET
HTTP/1.1 200 OK
Content-Length: 241
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:48:10 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
  <status>paid</status>
</order>

The response from the server consists of a representation of the order created by the original POST request, plus some additional information such as the status, and a collection of useful metadata in the headers. The first line includes a 200 OK status code and a short textual description of the outcome of the response informing us that our GET operation was successful. Two headers follow, which consumers use as hints to help parse the representation in the payload. The Content-Type header informs us that the payload is an XML document, while Content-Length declares the length of the representation in bytes. Finally, the representation is found in the body of the response, which is encoded in XML in accordance with the Content-Type header.

A client can GET a representation many times over without the requests causing the resource to change. Of course, the resource may still change between requests for other reasons. For example, the status of a coffee order could change from “pending” to “served” as the barista makes progress. However, the consumer’s GET requests should not cause any of those state changes, lest they violate the widely shared understanding that GET is safe.

Since Restbucks is a good web citizen, it’s safe to GET a representation of an order at any point. However, clients should be prepared to receive different representations over time since resource state—that is, the order—changes on the server side as the barista prepares the coffee. To illustrate the point, imagine issuing the GET request from Example 4-6 again a few minutes later. This time around, the response is different because the order’s status has changed from paid to served (in the <status> element), since the barista has finished preparing the drink, as we can see in Example 4-8.

Example 4-8. Rereading an order with GET
HTTP/1.1 200 OK
Content-Length: 265
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:58:21 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
  <status>served</status>
</order>

In our CRUD ordering service, we’re only going to consider two failure cases for GET. The first is where the client requests an order that doesn’t exist, and the second is where the server fails in an unspecified manner. For these situations, we borrow inspiration from the Web and use the 404 and 500 status codes to signify that an order hasn’t been found or that the server failed, respectively. For example, the request in Example 4-9 identifies an order that does not (yet) exist, so the service responds with the 404 Not Found status code shown in Example 4-10.

Example 4-9. Requesting an order that doesn’t exist via GET
GET /order/123456789012345667890 HTTP/1.1
Host: restbucks.com
Example 4-10. Ordering service does not recognize an order URI and responds with 404 Not Found
HTTP/1.1 404 Not Found
Date: Sat, 20 Dec 2008 19:01:33 GMT

The 404 Not Found status code in Example 4-10 informs the consumer that the specified order is unknown to the service.[31] On receipt of a 404 response, the client can do very little to recover. Effectively, its view of application state is in violent disagreement with that of the ordering service. Under these circumstances, Restbucks consumers should rely on out-of-band mechanisms (such as pleading with the barista!) to solve the problem, or try to rediscover the URI of their order.

If the consumer receives a 500 Internal Server Error status code as in Example 4-11, there may be a way forward without having to immediately resort to out-of-band tactics. For instance, if the server-side error is transient, the consumer can simply retry the request later.

Example 4-11. Ordering service indicates unexpected failure with a 500 response
HTTP/1.1 500 Internal Server Error
Date: Sat, 20 Dec 2008 19:24:34 GMT

This is a very simple but powerful recovery scenario whose semantics are guaranteed by the behavior of GET. Since GET requests don’t change service state, it’s safe for consumers to GET representations as often as they need. In failure cases, consumers simply back off for a while and retry the GET request until they give up (and accept handing over control to some out-of-band mechanism) or wait until the service comes back online and processing continues.

Implementing read with GET

The code in Example 4-12 shows how retrieving an order via GET can be implemented using JAX-RS [32] in Java.

Example 4-12. Server-side implementation of GET with JAX-RS
@Path("/order")
public class OrderingService {
  @GET
  @Produces("application/xml")
  @Path("/{orderId}")
  public String getOrder(@PathParam("orderId") String orderId) {
    
      Order order = OrderDatabase.getOrder(orderId);
      
      if (order == null) {
        throw new WebApplicationException(Response.Status.NOT_FOUND);
      } else {
      try {
        return xstream.toXML(order);
      } catch (Exception e) {
    }
  }
  // Remainder of implementation omitted for brevity
}

In Example 4-12, the root path where our service will be hosted is declared using the @Path annotation, which in turn yields the /order part of the URI. The getOrder(…) method is annotated with @GET, @Produces, and @Path annotations that provide the following behaviors:

  • @GET declares that the getOrder(…) method responds to HTTP GET requests.

  • @Produces declares the media type that the method generates as its return value. In turn, this is mapped onto the HTTP Content-Type header in the response. Since the ordering service uses XML for order resource representations, we use application/xml here.

  • @Path declares the final part of the URI where the method is registered, using the URI template /{orderId}. By combining this with the root path declared at the class level, the service is registered at the URI /order/{orderId}.

The orderId parameter to the getOrder(…) method is automatically bound by JAX-RS using the @PathParam annotation on the method’s orderId parameter to match the @Path annotation attached to the method. Once this is all configured, the JAX-RS implementation extracts the order identifier from URIs such as http://restbucks.com/order/1234 and makes it available as the String parameter called orderId in the getOrder(…) method.

Inside the getOrder(…) method, we try to retrieve order information from the database keyed by the orderId parameter. If we find a record matching the orderId, we encode it as an XML document using XStream[33] and return the document. This relinquishes control back to JAX-RS, which in turn packages the XML-encoded order into an HTTP response and returns it to the consumer. If we can’t find the order in the database, the implementation throws a WebApplicationException with the parameter NOT_FOUND, which results in a 404 Not Found response code being returned to the consumer. If something unpredicted goes wrong, such as the loss of database connectivity, we throw a WebApplicationException but with a 500 Internal Server Error status code indicated by the INTERNAL_SERVER_ERROR code. Either way, JAX-RS takes care of all the plumbing for us, including the creation of a well-formed HTTP response.

Note

It’s interesting that the JAX-RS implementation for GET in Example 4-10 deals with a substantial amount of plumbing code on our behalf when compared to the bare servlet implementation in Example 4-3. However, it’s also important to note that we don’t have to use frameworks such as JAX-RS to build CRUD services, since servlets (and other HTTP libraries) can work just as well.

Updating a Resource with PUT

For the uninitiated, HTTP can be a strange protocol, not least because it offers two ways of transmitting information from client to server with the POST and PUT verbs. In their landmark book,[34] Richardson and Ruby established a convention for determining when to use PUT and when to use POST to resolve the ambiguity:

  • Use POST to create a resource identified by a service-generated URI.

  • Use POST to append a resource to a collection identified by a service-generated URI.

  • Use PUT to create or overwrite a resource identified by a URI computed by the client.

This convention has become widely accepted, and the Restbucks ordering service embraces it by generating URIs for orders when they’re created by POST ing to the well-known entry point: http://restbucks.com/order. Conversely, when updating orders via PUT, consumers specify the URIs. Figure 4-6 shows how using different verbs disambiguates the two different cases and simplifies the protocol.

PUT request and responses
Figure 4-6. PUT request and responses

In Figure 4-6, consumers know the URI of the order they want to update from the Location header received in the response to an earlier POST (create) request. Using that URI, a consumer can PUT an updated order representation to the ordering service. In accordance with the HTTP specification, a successful PUT request won’t create a new resource, but will instead update the state of the identified resource to reflect the data in the request representation.

Example 4-13 shows how a request for an update looks on the wire. While the HTTP headers should look familiar, in this case the HTTP body contains an XML representation of the original order with the contents of the <milk> element for the cappuccino changed to be skim rather than whole.

Example 4-13. Updating an order
PUT /order/1234 HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 246

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
</order>

Note

PUT expects the entire resource representation to be supplied to the server, rather than just changes to the resource state. Another relatively unknown HTTP verb, PATCH, has been suggested for use in situations—typically involving large resource representations—where only changes are provided. We’ll use PUT for now, but we’ll also cover the use of PATCH in the next chapter.

When the PUT request is accepted and processed by the service, the consumer will receive either a 200 OK response as in Example 4-14, or a 204 No Content response as in Example 4-15.

Whether 200 is used in preference to 204 is largely an aesthetic choice. However, 200 with a response body is more descriptive and actively confirms the server-side state, while 204 is more efficient since it returns no representation and indicates that the server has accepted the request representation verbatim.

Example 4-14. Successful update with a 200 response
HTTP/1.1 200 OK
Content-Length: 275
Content-Type: application/xml
Date: Sun, 30 Nov 2008 21:47:34 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>preparing</status>
</order>
Example 4-15. Successful update with a 204 response
HTTP/1.1 204 No Content
Date: Sun, 30 Nov 2008 21:47:34 GMT

On receiving a 200 or 204 response, the consumer can be satisfied that the order has been updated. However, things can and do go wrong in distributed systems, so we should be prepared to deal with those eventualities.

The most difficult of the three failure response codes from Figure 4-6 is where a request has failed because of incompatible state. An example of this kind of failure is where the consumer tries to change its order after drinks have already been served by the barista. To signal conflicting state back to the client, the service responds with a 409 Conflict status code, as shown in Example 4-16.

Example 4-16. Order has already been served as a take-away
HTTP/1.1 409 Conflict
Date: Sun, 21 Dec 2008 16:43:07 GMT
Content-Length:271

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
     <item>
      <milk>whole</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>served</status>
</order>

In keeping with the HTTP specification, the response body includes enough information for the client to understand and potentially fix the problem, if at all possible. To that end, Example 4-16 shows that the ordering service returns a representation of the current state of the order resource from the service. In the payload, we can see that the <status> element contains the value served, which indicates that the order cannot be altered. To make progress, the consumer will have to interpret the status code and payload to determine what might have gone wrong.

Note

We might reasonably expect that either 405 Method Not Allowed or 409 Conflict would be a valid choice for a response code in situations where PUTting an update to a resource isn’t supported. In this instance, we chose 409 since PUT may be valid for some updates that don’t violate business rules. For example, it might still be permitted to change the order from drink-in to take-away during the order’s life cycle since it’s just a matter of changing cups.

As with errors when processing POST and GET, a 500 response code is equally straightforward when using PUT—simply wait and retry. Since PUT is idempotent—because service-side state is replaced wholesale by consumer-side state—the consumer can safely repeat the operation as many times as necessary. However, PUT can only be safely used for absolute updates; it cannot be used for relative updates such as “add an extra shot to the cappuccino in order 1234.” That would violate its semantics.

Note

PUT is one of the HTTP verbs that has idempotent semantics (along with GET and DELETE in this chapter). The ordering service must therefore guarantee that PUTting the same order many times has the same side effects as PUTting it exactly once. This greatly simplifies dealing with intermittent problems and crash recovery by allowing the operation to be repeated in the event of failure.

If the service recovers, it simply applies any changes from any of the PUT requests to its underlying data store. Once a PUT request is received and processed by the ordering service, the consumer will receive a 200 OK response.

Implementing update with PUT

Now that we understand the update process, implementation is straightforward, especially with a little help from a framework. Example 4-17 shows an implementation of the update operation using the HTTP-centric features of Microsoft’s WCF. The service contract—the set of operations that will be exposed—is captured by the IOrderingService interface. In turn, the IOrderingService is adorned by a [ServiceContract] attribute that binds the interface to WCF so that the underlying framework can expose implementing classes as services.[35] For our purposes, the most interesting aspect of this code is the [WebInvoke] attribute, which, when used in tandem with an [OperationContract] attribute, declares that the associated method is accessible via HTTP.

Example 4-17. WCF ServiceContract for updating an order with PUT
[ServiceContract]
public interface IOrderingService
{
  [OperationContract]
  [WebInvoke(Method = "PUT", UriTemplate = "/order/{orderId}")]
  void UpdateOrder(string orderId, Order order);

  // Remainder of service contract omitted for brevity
}

The [WebInvoke] attribute takes much of the drudgery out of plumbing together URIs, entity body payloads, and the methods that process representations. Compared to lower-level frameworks, the WCF approach removes much boilerplate plumbing code.

In Example 4-17, the [WebInvoke] attribute is parameterized so that it responds only to the PUT verb, at URIs that match the URI template /order/{orderId}. The value supplied in {orderId} is bound at runtime by WCF to the string parameter orderId, which is then used to process the update.

When invoked, the representation in the HTTP body is deserialized from XML and dispatched to the implementing method as an instance of the Order type. To achieve this, we declare the mapping between the on-the-wire XML and the local Order object by decorating the Order type with [DataContract] and [DataMember] attributes, as shown in Example 4-18. These declarations help the WCF serializer to marshal objects to and from XML. Once the WCF serializer completes the deserialization work, all we need to implement is the update business logic, as shown in Example 4-19.

Example 4-18. Marking up an order for use with WCF
[DataContract(Namespace = "http://schemas.restbucks.com/order", Name = "order")]
public class Order
{

  [DataMember(Name = "location")]
  public Location ConsumeLocation
  {
    get { return location; }
    set { location = value; }
  }

  [DataMember(Name = "items")]
  public List<Item> Items
  {
    get { return items; }
    set { items = value; }
  }

   [DataMember(Name = "status")]
   public Status OrderStatus
   {
     get { return status; }
     set { status = value; }
   }
   // Remainder of implementation omitted for brevity
}
Example 4-19. WCF implementation for updating an order
public void UpdateOrder(string orderId, Order order)
{
  try
  {
    if (OrderDatabase.Database.Exists(orderId))
    {
      bool conflict = OrderDatabase.Database.Save(order);
      if (!conflict)
      {
        WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.NoContent;
      }
      else
      {
        WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.Conflict;
      }
    }
    else
    {
      WebOperationContext.Current.OutgoingResponse.StatusCode =
      HttpStatusCode.NotFound;
    }
  }
  catch (Exception)
  {
    WebOperationContext.Current.OutgoingResponse.StatusCode =
    HttpStatusCode.InternalServerError;
  }
}

The code in Example 4-19 first checks whether the order exists in the database. If the order is found, it is simply updated and a 204 No Content status code is returned to the consumer by setting the WebOperationContext.Current.OutgoingResponse.StatusCode property.

If there’s a conflict while trying to update the order, a 409 Conflict response and a representation highlighting the inconsistency will be returned to the consumer.

Note

It’s worth noting that the only identifier we have for the order comes from the URI itself, extracted by WCF via the {orderId} template. There’s no order ID embedded in the payload, since it would be superfluous. Following this DRY (Don’t Repeat Yourself) pattern, we avoid potential inconsistencies between the domain model and the resources the service exposes, and keep the URI as the authoritative identifier, as it should be.

If we can’t find the entry in the database, we’ll set a 404 Not Found response to indicate the order resource isn’t hosted by the service. Finally, if something unexpected happens, we’ll catch any Exception and set a 500 Internal Server Error status code on the response to flag that the consumer should take some alternative (recovery) action.

Removing a Resource with DELETE

When a consumer decides that a resource is no longer useful, it can send an HTTP DELETE request to the resource’s URI. The service hosting that resource will interpret the request as an indication that the client has become disinterested in it and may decide that the resource should be removed—the decision depends on the requirements of the service and the service implementation.

Note

Deleting a resource doesn’t always mean the resource is physically deleted; there are a range of outcomes. A service may leave the resource accessible to other applications, make it inaccessible from the Web and maintain its state internally, or even delete it outright.

Figure 4-7 highlights the use of DELETE in the Restbucks ordering service where DELETE is used to cancel an order, if that order is in a state where it can still be canceled. For example, sending DELETE to an order’s URI prior to preparation should be successful and the client should expect a 204 No Content response from the service as a confirmation.

DELETE request and responses
Figure 4-7. DELETE request and responses

Conversely, if the order has already been prepared, which means it can’t be deleted, a 405 Method Not Allowed response would be used. If the service is unavailable to respond to our DELETE request for some other reason, the client can expect a 503 Service Unavailable response and might try the request again later.

On the wire, DELETE requests are simple, consisting only of the verb, resource URI, protocol version, and HOST (and optional PORT) header(s), as shown in Example 4-20.

Example 4-20. Removing an order with DELETE
DELETE /order/1234 HTTP/1.1
Host: restbucks.com

Assuming the ordering service is able to satisfy the DELETE request, it will respond affirmatively with a 204 No Content response, as shown in Example 4-21.

Example 4-21. Order successfully removed
HTTP/1.1 204 No Content
Date: Tue, 16 Dec 2008 17:40:11 GMT

Note

Some services may elect to return the final state of the deleted resource on the HTTP response. In those cases, 204 isn’t appropriate, and a 200 OK response along with Content-Type and Content-Length headers and a resource representation in the body is used.

Failure cases tend to be intricate with DELETE requests, since they might have significant side effects! One such failure case is shown in Example 4-22, where the client has specified a URI that the server cannot map to an order, causing the ordering service to generate a 404 Not Found response.

Example 4-22. The requested order doesn’t exist
HTTP/1.1 404 Not Found
Content-Length: 0
Date: Tue, 16 Dec 2008 17:42:12 GMT

Although this is a simple response to understand—we see it all too often on the human Web, after all—it’s troubling from a programmatic perspective because it means the consumer has stale information about order resource state compared to the service.

We might take one of several different recovery strategies when we get a 404 Not Found response. Ordinarily, we might prefer a human to resolve the problem through some out-of-band mechanism. However, in some situations, it may be practical for the consumer to recompute application state by retrieving representations of the resources it knows about and attempt to make forward progress once it’s synchronized with the service.

Restbucks archives all orders after they have been served for audit purposes. Once archived, the order becomes immutable, and any attempts to DELETE an archived order will result in a 405 Method Not Allowed response from the ordering service, as shown in Example 4-23.

Example 4-23. Order has been archived
HTTP/1.1 405 Method Not Allowed
Allow: GET
Date: Tue, 23 Dec 2008 16:23:49 GMT

The response in Example 4-23 informs the client that while the order resource still exists, the client is not allowed to DELETE it. In fact, the Allow header is used to convey that GET is the only acceptable verb at this point in time and that requests using any other verb will be met with a 405 Method Not Allowed response.

Note

The Allow header can be used to convey a comma-separated list of verbs that can be applied to a given resource at an instant.

An implementation for DELETE using the HttpListener from the .NET Framework is shown in Example 4-24. Like the servlet implementation in Example 4-5, this example shows that it’s possible to develop services with just an HTTP library, and that we don’t always have to use sophisticated frameworks.

Example 4-24. Using HttpListener to delete an order
static void DeleteResource(HttpListenerContext context)
{
  string orderId = ExtractOrderId(context.Request.Url.AbsolutePath);

  var order = OrderDatabase.Retrieve(orderId);

  if (order == null)
  {
    context.Response.StatusCode = HttpStatusCode.NotFound;
  }
  else if (order.CanDelete)
  {
    OrderDatabase.archive(orderId);
    context.Response.StatusCode = HttpStatusCode.NoContent;
  }
  else
  {
    context.Response.StatusCode = HttpStatusCode.MethodNotAllowed;
  }

  context.Response.Close();
}

In Example 4-24, an HTTPListenerContext instance provides access to the underlying HTTP request and response messages. Using the request URI, we extract an order identifier and then determine whether it corresponds to a valid order. If no order is found, we immediately set the HTTP response to 404 and call Close() on the response object to return control to the web server, which in turn returns a well-formed 404 Not Found response message to the consumer.

If we can find the resource, we check whether we’re allowed to delete it. If we are, we logically remove the associated order before returning a 204 No Content response to the client. Otherwise, we set the response code to 405 and let the client know they can’t delete that resource.

Safety and Idempotency

We saw in Chapter 3 that GET is special since it has the properties of being both safe and idempotent. PUT and DELETE are both idempotent, but neither is safe, while POST is neither safe nor idempotent. Only GET returns the same result with repeated invocations and has no side effects for which the consumer is responsible.

With GET, failed requests can be repeated without changing the overall behavior of an application. For example, if any part of a distributed application crashes in the midst of a GET operation, or the network goes down before a response to a GET is received, the client can just reissue the same request without changing the semantics of its interaction with the server.

In broad terms, the same applies to both PUT and DELETE requests. Making an absolute update to a resource’s state or deleting it outright has the same outcome whether the operation is attempted once or many times. Should PUT or DELETE fail because of a transient network or server error (e.g., a 503 response), the operation can be safely repeated.

However, since both PUT and DELETE introduce side effects (because they are not safe), it may not always be possible to simply repeat an operation if the server refuses it at first. For instance, we have already seen how a 409 response is generated when the consumer and service’s view of resource state is inconsistent—merely replaying the interaction is unlikely to help. However, HTTP offers other useful features to help us when state changes abound.

Aligning Resource State

In a distributed application, it’s often the case that several consumers might interact with a single resource, with each consumer oblivious to changes made by the others. As well as these consumer-driven changes, internal service behaviors can also lead to a resource’s state changing without consumers knowing. In both cases, a consumer’s understanding of resource state can become misaligned with the service’s resource state. Without some way of realigning expectations, changes requested by a consumer based on an out-of-date understanding of resource state can have undesired effects, from repeating computationally expensive requests to overwriting and losing another consumer’s changes.

HTTP provides a simple but powerful mechanism for aligning resource state expectations (and preventing race conditions) in the form of entity tags and conditional request headers. An entity tag value, or ETag, is an opaque string token that a server associates with a resource to uniquely identify the state of the resource over its lifetime. When the resource changes—that is, when one or more of its headers, or its entity body, changes—the entity tag changes accordingly, highlighting that state has been modified.

ETag s are used to compare entities from the same resource. By supplying an entity tag value in a conditional request header—either an If-Match or an If-None-Match request header—a consumer can require the server to test a precondition related to the current resource state before applying the method supplied in the request.

Note

ETags are also used for cache control purposes, as we’ll see in Chapter 6.

To illustrate how ETags can be used to align resource state in a multiconsumer scenario, imagine a situation in which a party of two consumers places an order for a single coffee. Shortly after placing the order, the first consumer decides it wants whole milk instead of skim milk. Around the same time, the second consumer decides it, too, would like a coffee. Neither consumer consults the other before trying to amend the order.

To begin, both consumers GET the current state of the order independently of each other. Example 4-25 shows one of the consumer’s requests.

Example 4-25. Consumer GETs the order
GET /order/1234 HTTP/1.1
Host: restbucks.com

The service’s response contains an ETag header whose value is a hash of the returned representation (Example 4-26).

Example 4-26. Service generates a response with an ETag header
HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: 275
ETag: "72232bd0daafa12f7e2d1561c81cd082"

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>pending</preparing>
</order>

Note

The service computes the entity tag and supplies it as a quoted string in the ETag header prior to returning a response. Entity tag values can be based on anything that uniquely identifies an entity: a version number associated with a resource in persistent storage, one or more file attributes, or a checksum of the entity headers and body, for example. Some methods of generating entity tag values are more computationally expensive than others. ETags are often computed by applying a hash function to the resource’s state, but if hashes are too computationally expensive, any other scheme that produces unique values can be used. Whichever method is used, we recommend attaching ETag headers to responses wherever possible.

When a consumer receives a response containing an ETag, it can (and should) use the value in any subsequent requests it directs to the same resource. Such requests are called conditional requests. By supplying the received entity tag as the value of an If-Match or If-None-Match conditional header, the consumer can instruct the service to process its request only if the precondition in the conditional header holds true.

Of course, consumers aren’t obliged to retransmit ETags they’ve received, and so services can’t expect to receive them just because they’ve been generated. However, consumers that don’t take advantage of ETags are disadvantaged in two ways. First, consumers will encounter increased response times as services have to perform more computation on their behalf. Second, consumers will discover their state has become out of sync with service state through status codes such as 409 Conflict at inconvenient and (because they’re not using ETags) unexpected times. Both of these failings are easily rectified by diligent use of ETags.

An If-Match request header instructs the service to apply the consumer’s request only if the resource to which the request is directed hasn’t changed since the consumer last retrieved a representation of it. The service determines whether the resource has changed by comparing the resource’s current entity tag value with the value supplied in the If-Match header. If the values are equal, the resource hasn’t changed. The service then applies the method supplied in the request and returns a 2xx response. If the entity tag values don’t match, the server concludes that the resource has changed since the consumer last accessed it, and responds with 412 Precondition Failed.

Note

Services are strict about processing the If-Match header. A service can’t (and shouldn’t) do clever merges of resource state where one coffee is removed and another, independent coffee in the same order is changed to decaf. If two parts of a resource are independently updatable, they should be separately addressable resources. For example, if fine-grained control over an order is useful, each cup of coffee could be modeled as a separate resource.

Continuing with our example, the first consumer does a conditional PUT to update the order from skim to whole milk. As Example 4-27 shows, the conditional PUT includes an If-Match header containing the ETag value from the previous GET.

Example 4-27. The first consumer conditionally PUTs an updated order
PUT /order/1234 HTTP/1.1
Host: restbucks.com
If-Match: "72232bd0daafa12f7e2d1561c81cd082"

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>whole</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>pending</preparing>
</order>

Because the order hadn’t been modified since the first consumer last saw it, the PUT succeeds, as shown in Example 4-28.

Example 4-28. The conditional PUT succeeds
HTTP/1.1 204 No Content
ETag: "6e87391fdb5ab218c9f445d61ee781c1"

Notice that while the response doesn’t include an entity body, it does include an updated ETag header. This new entity tag value reflects the new state of the order resource held on the server (the result of the successful PUT).

Oblivious to the change that has just taken place, the second consumer attempts to add its order, as shown in Example 4-29. This request again uses a conditional PUT, but with an entity tag value that is now out of date (as a result of the first consumer’s modification).

Example 4-29. The second consumer conditionally PUTs an updated order
PUT /order/1234 HTTP/1.1
Host: restbucks.com
If-Match: "72232bd0daafa12f7e2d1561c81cd082"

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>2</quantity>
      <size>large</size>
    </item>
  </items>
  <status>pending</status>
</order>

The service determines that the second consumer is trying to modify the order based on an out-of-date understanding of resource state, and so rejects the request, as shown in Example 4-30.

Example 4-30. The response indicates a precondition has failed
HTTP/1.1 412 Precondition Failed

When a consumer receives a 412 Precondition Failed status code, the correct thing to do is to GET a fresh representation of the current state of the resource, and then use the ETag header value supplied in this response to retry the original request, which is what the second consumer does in this case. Having done a fresh GET, the consumer sees that the original order had been modified. The second consumer is now in a position to PUT a revised order that reflects both its and the first consumer’s wishes.

Our example used the If-Match header to prevent the second consumer from overwriting the first consumer’s changes. Besides If-Match, consumers can also use If-None-Match. An If-None-Match header instructs the service to process the request only if the associated resource has changed since the consumer last accessed it. The primary use of If-None-Match is to save valuable computing resources on the service side. For example, it may be far cheaper for a service to compare ETag values than to perform computation to generate a representation.

Note

If-None-Match is mainly used with conditional GETs, whereas If-Match is typically used with the other request methods, where race conditions between multiple consumers can lead to unpredictable side effects unless properly coordinated.

Both If-Match and If-None-Match allow the use of a wildcard character, *, instead of a normal entity tag value. An If-None-Match conditional request that takes a wildcard entity tag value instructs the service to apply the request method only if the resource doesn’t currently exist. Wildcard If-None-Match requests help to prevent race conditions in situations where multiple consumers compete to PUT a new resource to a well-known URI. In contrast, an If-Match conditional request containing a wildcard value instructs the service to apply the request only if the resource does exist. Wildcard If-Match requests are useful in situations where the consumer wishes to modify an existing resource using a PUT, but only if the resource hasn’t already been deleted.

Note

As well as ETag and its associated If-Match and If-None-Match headers, HTTP supports a timestamp-based Last-Modified header and its two associated conditional headers: If-Modified-Since and If-Unmodified-Since. These timestamp-based conditional headers act in exactly the same way as the If-Match and If-None-Match headers, but the conditional mechanism they implement is accurate only to the nearest second—the limit of the timestamp format used by HTTP. Because timestamps are often cheaper than hashes, If-Modified-Since and If-Unmodified-Since may be preferable in solutions where resources don’t change more often than once per second.

In practice, we tend to use timestamps as cheap ETag header values, rather than as Last-Modified values. By using ETags from the outset, we ensure that the upgrade path to finer-grained ETags is entirely at the discretion of the service. The service can switch from using timestamps to using hashes without upsetting clients.

Consuming CRUD Services

Services are one side of distributed systems, but to perform useful work they need consumers to drive them through their protocols. Fortunately, many frameworks and libraries support CRUD Web Services, and it’s worthwhile to understand a little about what they offer.

A Java-Based Consumer

In the Java world, we might use the Apache Commons HTTP client[36] to implement the Create part of the protocol by POSTing an order to the ordering service, as shown in Example 4-31.

Example 4-31. Client-side order creation in Java
  public String placeOrder(Order order, String restbucksOrderingServiceUri)
                                throws BadRequestException, ServerFailureException,
                                       HttpException, IOException {

  PostMethod post = new PostMethod(restbucksOrderingServiceUri);
  // Use an existing XStream instance to generate XML for the order to transmit
  RequestEntity entity = new ByteArrayRequestEntity(
                                xstream.toXML(order).getBytes());
  post.setRequestEntity(entity);

  HttpClient client = new HttpClient();

  try {
    int response = client.executeMethod(post);

    if(response == 201) {
      return post.getResponseHeader("Location").getValue();
    } else if(response == 400) {
      // If we get a 400 response, the caller's gone wrong
      throw new BadRequestException();
    } else if(response == 500 || response == 503) {
      // If we get a 5xx response, the caller may retry
      throw new ServerFailureException(post.getResponseHeader("Retry-After"));
    }
    // Otherwise abandon the interaction
    throw new HttpException("Failed to create order. Status code: " + response);
  } finally {
    post.releaseConnection();
  }
}

The implementation in Example 4-31 shows the construction of a POST operation on the ordering service, using a PostMethod object. All we need to do is to populate the HTTP request with the necessary coffee order information by setting the request entity to contain the bytes of an XML representation of the order. To keep things simple for ourselves, we use the XStream library to encode the order resource representation in XML.

Having populated the HTTP request, we instantiate an HttpClient and execute the PostMethod, which POSTs the order to the Restbucks ordering service. Once the method returns, we examine the response code for a 201 Created status and return the contents of the Location header, which will contain the URI of the newly created order. We can use this URI in subsequent interactions with Restbucks. If we don’t get a 201 response, we fail by throwing an HTTPException, and assume that order creation has failed.

A .NET Consumer

On the .NET platform, we can opt for the framework’s built-in XML and HTTP libraries. The code in Example 4-32 represents how a client can send an order update to the Restbucks ordering service via HTTP PUT.

Example 4-32. .NET client code for order update via PUT
public void UpdateOrder(Order order, string orderUri)
{
  HttpWebRequest request = WebRequest.Create(orderUri) as HttpWebRequest;
  request.Method = "PUT";
  request.ContentType = "application/xml";

  XmlSerializer xmlSerializer = new XmlSerializer(typeof(Order));
  xmlSerializer.Serialize(request.GetRequestStream(), order);

  request.GetRequestStream().Close();

  HttpWebResponse response = (HttpWebResponse)request.GetResponse();

  if (response.StatusCode != HttpStatusCode.OK)
  {
    // Compensation logic omitted for brevity
  }
}

In Example 4-32, we use an HTTPWebRequest instance to handle the HTTP aspects of the interaction. First we set the HTTP verb PUT via the Method property and subsequently set the Content-Type header to application/xml through the ContentType property. We then write an XML-serialized representation of the order object that was given as an argument to the UpdateOrder() method. The XmlSerializer transforms the local object instance into an XML document, and the Serialize() method writes the XML to the request’s stream. Once we’re done populating the request stream, we simply call Close(). Under the covers, the framework sets other headers such as Content-Length and Host for us, so we don’t have to worry about them.

To send the request we call the GetResponse() method on the request object, which has the effect of transmitting an HTTP PUT to the URI supplied as an argument to the updateOrder() method. The response from the ordering service is returned as an HttpWebResponse and its StatusCode property triggers any further processing.

One final job that we need to undertake is to mark up the Order type so that the XmlSerializer knows how to transform Order instances to and from XML representations. The code snippet in Example 4-33 shows the .NET attributes that we need to apply for our client-side plumbing to be complete.

Example 4-33. An XML-serializable order
 [XmlRoot(Namespace = "http://schemas.restbucks.com/order")]
 [XmlType(TypeName = "order")]
 public class Order
 {

 [XmlElement(ElementName = "location")]
 public Location ConsumeLocation
 {
   get; set;
 }
 // Remainder of type omitted for brevity
}

Consuming Services Automatically with WADL

Although the patterns for writing clients in .NET and Java are easy to understand and implement, we can save ourselves effort—and, in some cases, generate code automatically—using service metadata. Up to this point, much of the work we’ve done in building our ordering service and its consumers has been plumbing code. But for some kinds of services,[37] a static description can be used to advertise the addresses and representation formats of the resources the service hosts. This is the premise of the Web Application Description Language, or WADL.

A WADL contract is an XML document that describes a set of resources with URI templates, permitted operations, and request-response representations. As you’d expect, WADL also supports the HTTP fault model and supports the description of multiple formats for resource representations. Example 4-34 shows a WADL description of the Restbucks ordering service.

Example 4-34. Ordering service WADL example
<?xml version="1.0" encoding="utf-8"?>
<application
  xmlns:xsd=http://www.w3.org/2001/XMLSchema
  xmlns="http://research.sun.com/wadl/2006/10"
  xmlns:ord="http://schemas.restbucks.com/order">

  <grammars>
    <include href="order.xsd"/>
  </grammars>

  <resources base="http://restbucks.com/">
    <resource path="order">
      <method name="POST">
        <request>
          <representation mediaType="application/xml" element="ord:order"/>
        </request>

        <response>
          <representation status="201"/>
          <fault mediaType="application/xml" element="ord:error" status="400"/>
          <fault mediaType="application/xml" element="ord:error" status="500"/>
        </response>
      </method>
    </resource>
    <resource path="order/{orderId}">
      <method name="GET">
        <response>
          <representation mediaType="application/xml" element="ord:order"/>
          <fault mediaType="application/xml" element="ord:error" status="404"/>
          <fault mediaType="application/xml" element="ord:error" status="500"/>
        </response>
      </method>
      <method name="PUT">
        <request>
          <representation mediaType="application/xml" element="ord:order"/>
        </request>
        <response>
          <representation status="200"/>
          <fault mediaType="application/xml" element="ord:error" status="404"/>
          <fault mediaType="application/xml" element="ord:error" status="409"/>
          <fault mediaType="application/xml" element="ord:error" status="500"/>
        </response>
      </method>
      <method name="DELETE">
        <response>
          <representation status="200"/>
          <fault mediaType="application/xml" element="ord:error" status="404"/>
          <fault mediaType="application/xml" element="ord:error" status="405"/>
          <fault mediaType="application/xml" element="ord:error" status="500"/>
        </response>
      </method>
    </resource>
  </resources>
</application>

The <application> element is the root for the WADL metadata. It acts as the container for schemas that describe the service’s resource representations in the <grammars> element and the resources that are contained within the <resources> element. The <grammars> element typically refers to XML Schema schemas (which we have defaulted to for Restbucks) that describe the structure of the resource representations supported by the service, though other schema types (e.g., RELAX NG) are supported too. Consumers of the service can use this information to create local representations of those resources such as orders and products.

In a WADL description, the <resources> element is where most of the action happens. It provides a static view of the resources available for consumption. It uses a templating scheme that allows consumers to infer the URIs of the resources supported by a service. Calculating URIs can be a little tricky since WADL relies on a hierarchy of resources, with each URI based on the parent URI’s template plus its own. In Example 4-34, we have two logical resources: http://restbucks.com/order for POST and http://restbucks.com/order/{orderId} for the other verbs. The resource URIs are computed by appending the path of the <resource> element to the path defined in the base attribute of the <resources> element.

Note

In addition to dealing with URI templates and query strings, WADL also has a comprehensive mechanism for building URIs. WADL can deal with form encoding and handling a range of URI structures, including matrix URIs.[38]

The <method> element allows WADL to bring together the request and response resource representations and HTTP verbs to describe the set of permissible interactions supported by the service. The Restbucks ordering service is described in terms of two separate resource paths. We first define the order resource (<resource path=“order”>), which only allows POST requests (<method name=“POST”>) and requires that the payload of those requests be XML representations of an order. We also describe the possible ways the Restbucks ordering service can reply to a POST request (in the <response> element) depending on the outcome of processing the submitted order. In this case, the possible response code is 201, 400, or 500.

Using a URI template, a second set of resources—the orders that Restbucks has created—is advertised by the element <resource path=“order/{orderId}”>. Like the POST method element, each subsequent <method> element describes the possible responses and faults that the ordering service might return. Additionally, the PUT <method> element declares that an XML order representation must be present as the payload of any PUT requests.

While it’s helpful that we can read and write WADL by hand (at least in simple cases), the point of WADL is to help tooling automate as much service plumbing as possible. To illustrate how WADL can be consumed by an automation infrastructure, the authors of WADL have created the WADL2Java[39] tool.[40] WADL2Java allows us to create consumer-side Java that minimizes the code we have to write in order to interact with a service described in WADL. The Java code in Examples Example 4-35 and Example 4-36 shows the consumer-side API that Java programmers can use to interact with a WADL-decorated ordering service.

Example 4-35. WADL-generated endpoint
public class Endpoint {
  public static class Orders {

    public DataSource postAsIndex(DataSource input)
       throws IOException, MalformedURLException {
      // Implementation removed for brevity
    }
  }

  public static class OrdersOrderId {

    public OrdersOrderId(String orderid)
        throws JAXBException {
      // Implementation removed for brevity
    }

    // Getters and setters omitted for brevity

    public DataSource getAsApplicationXml()
      throws IOException, MalformedURLException {
      // Implementation removed for brevity
    }

    public Order getAsOrder()
      throws ErrorException, IOException, MalformedURLException, JAXBException {
      // Implementation removed for brevity
    }

    public DataSource putAsIndex(DataSource input)
      throws IOException, MalformedURLException {
      // Implementation removed for brevity
    }

    public DataSource deleteAsIndex()
      throws IOException, MalformedURLException {
      // Implementation removed for brevity
    }
  }
}

In Java, resources are locally represented by classes such as Order, shown in Example 4-36, which allow us to inspect and set values in the XML representations exchanged with the ordering service.

Example 4-36. Java representation of an order resource
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "location",
    "items",
    "status"
})
@XmlRootElement(name = "order")
public class Order {

    @XmlElement(required = true)
    protected String location;
    @XmlElement(required = true)
    protected Order.Items items;
    @XmlElement(required = true)
    protected String status;

    // Getters and setters only, omitted for brevity

 }

WADL can be useful as a description language for CRUD services such as the ordering service. It can be used to automatically generate plumbing code with very little effort, compared to manually building clients. Since the client and server collaborate over the life cycle of a resource, its URI, and its representation format, it does not matter whether the plumbing is generated from a metadata description. Indeed, WADL descriptions may help expedite consumer-side maintenance when changes happen on the server side.

Note

As we will see in the next chapter, the Web uses hypermedia to provide contracts in a much more loosely coupled way than WADL. But for CRUD-only services, WADL can be a useful tool.

CRUD Is Good, but It’s Not Great

Now that we’ve completed our tour of CRUD services, it’s clear that using HTTP as a CRUD protocol can be a viable, robust, and easily implemented solution for some problem domains. In particular for systems that manipulate records, HTTP-based CRUD services are a straightforward way to extend reach over the network and expose those applications to a wider range of consumers.[41]

Since we can implement CRUD services using a small subset of HTTP, our integration needs may be completely satisfied with few CRUD-based services. Indeed, this is typically where most so-called RESTful services stop.[42] However, it’s not the end of our journey, because for all their strengths and virtue of simplicity, CRUD services are only suited to CRUD scenarios. More advanced requirements need richer interaction models and, importantly, will emphasize stronger decoupling than CRUD allows.

To decouple our services from clients and support general-purpose distributed systems, we need to move away from a shared, tightly coupled understanding of resource life cycles. On the human Web, this model has long been prevalent when using hyperlinks to knit together sequences of interactions that extend past CRUD operations. In the next chapter, we’re going to replicate the same hypermedia concept from the Web to create robust distributed systems.



[28] We’ve adopted the convention used in RESTful Web Services (http://oreilly.com/catalog/9780596529260/) by Leonard Richardson and Sam Ruby (O’Reilly), where POST is used for creation and the server determines the URI of the created resource.

[30] This is demanded of us by the HTTP specification.

[31] If we wanted to be more helpful to the consumer, our service could provide a helpful error message in the HTTP body.

[32] Oracle Corp. website. “JAX-RS (JSR 311): The Java API for RESTful Web Services”; see http://jcp.org/en/jsr/detail?id=311.

[34] RESTful Web Services (http://oreilly.com/catalog/9780596529260/), published by O’Reilly.

[35] WCF implements the same model for all kinds of remote behavior, including queues and WS-* Web Services. This lowest-common-denominator approach seeks to simplify programming distributed systems. Unfortunately, it often hides essential complexity, so use it with care!

[37] CRUD services are great candidates for describing with WADL. Hypermedia services—as we will see in the next chapter—use different mechanisms to describe the protocols they support.

[40] Other tools also exist; for example, REST Describe at http://tomayac.de/rest-describe/latest/RestDescribe.html.

[41] Naively exposing systems that have not been built for network access is a bad idea. Systems have to be designed to accommodate network loads.

[42] We’re being generous here, since most so-called RESTful services tend to stop at tunneling through HTTP!

Get REST in Practice now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.