Whatever can go wrong … Error Handling and RESTful Services

December 7, 2009

In the previous post I described using the Apache Wink Client facilities to create a JUnit test for an operation provided by my example Library RESTful Service. The heart of test was

        String resoruceUri =
"http://localhost:9085/LibraryWink/library/editions/0863699936"
        Resource editionResource
                   = libraryClient.resource(resoruceUri);

        BookEdition edition = editionResource.get(
                      new EntityType<BookEdition>(){}
                     );
        assertEquals("0863699936", edition.getIsbn());

where we create a URI for a particular Edition of a Book from its ISBN and attempt to GET the representation of that Edition’s state, eventually verifying that the expected data is retrieved.

One of the beneficial effects of writing Unit Tests is that it focuses the mind on edge cases and error conditions. On seeing this test, we wonder “What should happen if the ISBN is not found?”. That question leads us to exploit several additional features of the the Apache Wink programming model in both service implementation and test client. 

Service Implementation Errors (1) – HTTP Response Codes

The service can deal with the case of an unknown ISBN by returning an HTTP Response Code 404, which is defined in the HTTP protocol as Not Found.  This is simple and unambiguous, consistent with the spirit of the RESTful use of Web principles, and the client needs no additional information. Other errors require different handling as I’ll explain later.

One other point, there is an alternative Response Code that we might use in some situations: 410, Gone. This might be useful in cases where a record has been in the system and has been marked as deleted.

So, how does a JAX-RS service return a Response Code such as 404 or 410? Again, the JAX-RS programming model makes implementation easy, but we do need to adjust the service method’s return type to accommodate the two possibilities of success (in which case a representation of an Edition is returned) and a Not Found response.

   @Path("{isbn}")
  @GET
  @Produces( MediaType.APPLICATION_JSON)
  public Response getEditionByIsbn(
         @PathParam("isbn") String isbn)
  { 
     BookEdition edition 
          = EditionStore.getInstance().getEdition(isbn); 
     if ( edition != null){
        return Response.ok(edition).build(); 
     } else {
        return Response.status(404).build(); 
     }
  }

Hence I specify a return type of Response. Then in the implementaiton use either the Response.ok() or Response.status() factory methods to create the ResponseBuilder object. This builder object can be used to specify various attributes of the response and finally is used to build() the Response. Note the “chaining” approach to using the ResponseBuilder.

(For brevity, I used the literal 404, the enum javax.ws.rs.core.Response.Status.NOT_FOUND would make for more readable code.)

That’s now enabled the service to send a 404 response when an unknown ISBN is requested. So, back to the test client to check for that response.

Client Error Handling (1) – HTTP Responses

The Wink APIs offer a ClientResponse object, used like this

    @Test
    public void testFindBad(){
        Resource editionResource = makeEditionResource("SILLY");
        ClientResponse response = editionResource.get();       
        assertEquals(404, response.getStatusCode());
    }

If the service returns success HTTP Response Code, such as 200 OK, or 201 Created then the ClientResponse get() method gives access to the entity data

    BookEdition edition = editionResource.get(
                     new EntityType<BookEdition>(){} );

Summary so far: the Response class gives the service provider the capability of returning meaningful response codes and its client counterpart ClientResponse enables clients to detect these response codes. Now let’s move on to another class of problem, I call these Transient Errors.

Service Implementation Errors (2) – Transient Errors

Service implementations are likely to depend upon other software components, which may fail. For example a dependency on an Database whose server might be unavailable. I term such error scenarios “Transient Errors”. The implication being that the same request by the client, if resubmitted in the future will succeed. As a service designer we must consider how to deal with such error conditions.

Just one aside about this concept, I would also treat some errors detected by  the service’s own implementation as transient. For example suppose that the implementation had some defensive programming, such as a default case in a switch statement .

  switch ( getRole() ) { 
   Role.Tinker:  // code 
   Role Tailor: // code 
   default: // unexpected, happens if code is defective 
}

What to do in the default case? I treat this as a a Transient Error, it will be resolved by installing a corrected version of the service implementation, then if the client retries their request it will succeed. From the client’s perspective this is equivalent to a Database being unavailable and then restarted – eventually a retry succeeds.

So, given that the service implementation detects a Transient Error condition what should it do? I see two responsibilities:

  1. On the server log suitable diagnostics in order to enable operators to detect and rectify the problem.
  2. Return a meaningful error to the client.

The production of diagnostics is a system-wide issue that should be addressed by explicit architectural and design policies, by default I tend to use java.util.logging but I’ll not dig further into that area here. I do want to look a little more deeply at the second item: the returning of error information to the client. I will explore three different approaches.

First, we can simply throw an exception:

    throw new WebApplicationException(
              Status.SERVICE_UNAVAILABLE
              );

This is will result in a simple response to the client with a 503 Response Code. In many situations this is sufficient. The client doesn’t know what is broken, but can at least call a help desk and report the problem. However sometimes giving a little more information can either assist in error reporting or serve to clarify the nature of the problem. Reading the HTTP specification concerning response codes we see the recommendation:

Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. User agents SHOULD display any included entity to the user. These response codes are applicable to any request method.

That leads us to the second approach. In the server we can produce an error response with a String entity payload like this

    return Response.serverError()
                   .status(Status.SERVICE_UNAVAILABLE)
                   .entity("Database XXX unavailable")
                   .build();

And then in the client access the message:

    response.getEntity(String.class)

This approach is likely to be sufficient for GET operations. However when we come to POSTs and PUTs more detailed responses are needed. That leads to the third possibility, using a structured Entity response rather than a simple message. First let’s build the test for POSTing of a new Edition, then look at the responses the create request might give.

Unit Test for POST

The test is constructed as follows. First I do some preparatory work:

  • create a suitable method and annotate it as a JUnit test
  • create the client Resource object for the Editions URI
  • create the Bean containing the details for the new Edition

    @Test
    public void testPost() {

    Resource editionResource = libraryClient.resource(
                     "http://localhost:9085/LibraryWink/library/editions”
                     );

    BookEdition theEdition = new BookEdition(
              /* title, isbn etc */
              );

Then I use the chained resource builder approach to specify that I am sending and accepting JSON  content, and pass the Java Bean as the edition data

    ClientResponse response = editionResource 
       .contentType(MediaType.APPLICATION_JSON) 
       .accept(MediaType.APPLICATION_JSON)                    
       .post(theEdition);

That calls the RESTful service operation and makes the response available. Now we can add assertions about the response code

      assertEquals(
               Status.OK.getStatusCode(), 
               response.getStatusCode()
     );

And we can obtain the entity from the response and make assertions about its content too, that’s just the same as the GET example at the start of this post.

Now, what about error conditions. This is first and foremost a question of service design: what error conditions might there be in this case? What responses should the service give.

Service Errors in a POST operation

Just as with the GET case, infrastructure errors may occur and these can be treated as Transient Errors and reported using either of the two techniques I described: throwing a WebApplicationException or creating a serverError response, and populating it with an error message.

There are two other conditions to consider:

  1. Suppose that a record for the ISBN already exists: what should the service do?
  2. Suppose that the supplied data is invalid: for example in this case an invalid ISBN might be supplied. More generally, we can conceive of quite complex field-level validation and cross-field validation.

Duplicate Records

Error handling in a distributed, loosely-coupled, system is greatly simplified if services are idempotent. That is, a request may safely be issued more than once. This means that if a client submits a request and then does not see a timely response (perhaps due to a network failure) or the response is lost (perhaps due to a client crash) the client may safely resubmit the request.

If we have adopted the principle of idempotence then it follows that  an insertion encountering a duplicate record is not necessarily an error. It might simply mean that the client sent an insertion, which worked, but failed to see the response and hence has resubmitted the request. However, even in this case there are two possibilities to consider, depending upon whether the  state exactly matches the request.

Existing Record Matches

In this case the system is already in the requested state, most likely because the current request is a resend of a previously successful request. It is reasonable to treat this as a success. The next case is less obvious, this is when the system record state differs from the request content.

Existing Record Does Not Match

There are many possible scenarios that might lead to situation where there is an existing record in place that does not match the current request:

  • A different request (perhaps from a different user’s UI) has created a different record. The difference might be fundamental (a completely different book) or some small semantically trivial difference (“JRR Tolkien” versus “J.R.R Tolkein”)
  • Our previous request succeeded but then subsequently a different request updated the record
  • The current request is mistaken, it’s using the ISBN of a different Edition

In all these case it seems clear that it is unreasonable for the system, without Human intervention, to decide to replace the current record state with that requested. Hence the system cannot be taken to the requested state and we would return an error code. More on the error code in just a moment, but first an aside about an extension to this line of thinking.

Interleaved Requests

The underlying assumption behind the scenarios I explored above is that the system may receive requests in an order different from that in which they were issued and that optimistic locking approach will apply when designing RESTful services. That is we will not design the distributed system along these lines:

  1. User in UI attempts to GET a record
  2. System returns 404, NOT FOUND and LOCKS preventing anyone else from inserting
  3. User POSTs new record, which therefore cannot encounter a duplicate

Although such an approach would simplify our reasoning we find that it simply does not scale and requires excessive coupling between components. Instead I assume:

  • Requests from two users will be delivered in an arbitrary order
  • Requests from the SAME user may be delivered out of order

The second condition enables us to use highly scalable infrastructure design: we can have stateless, clustered servers, multiple routers, and use store-and-forward asynchronous transport. Generally, we find that scalability and reliability are easier to achieve if we use idempotent services that do not strictly order requests. [There are business cases where request ordering is essential, and in these cases some aspects of service design may be simplified at the expense of greater constraints on the infrastructure.]

An implication of the arbitrary ordering of requests is that the service may need to provide a way of ensuring sensible ordering of request processing. Consider a service that controls a mobile phone, allowing a customer to enable and disable a phone. A customer loses their phone, sends a DISABLE request. Then very soon afterwards they find the phone, and so send an ENABLE request. It’s clear that it would be very annoying for the customer to have those two requests processed out of order … the phone end-state would be DISABLED. With additional information in the request such as the time of the request the service can identify a “stale” request and hence intelligent (in)action.

Handling Duplicates in the Service Implementation

To return to the service implementation, I now add duplicate detection and return suitable error conditions. First the main flow of the service implementation:

    try {
            BookEdition added 
                          =  EditionStore.getInstance().addEdition(edition);
            return Response.ok(added).build();
        } catch (DuplicateException e) {
                   BookEdition alreadyThere = e.getAlreadyThere();

This now catches a DuplicateException from the addEdition() method, which contains the state of the pre-existing record. Thats now extracted into the alreadyThere variable.

Now by using the equals() method, we can determine whether this record matches the requested state.

            if ( edition.equals(alreadyThere )){
                return Response.ok(alreadyThere).build();
            } else {

If the state does match then we treat this as a succesful (idempotent) insertion. We return the alreadyThere value, accommodating the possibility that the persisted state is enriched beyond the values specified in the insertion request.

Now for the case where the pre-existing record does not match the requested insertion. In this case I think that a 409- CONFLICT is a reasonable  response code, and we note that the HTTP specification says for a 409:

The response body SHOULD include enough information for the user to recognize the source of the conflict. Ideally, the response entity would include enough information for the user or user agent to fix the problem; however, that might not be possible and is not required.

Hence we need to return something more than a string. I propose to return an object that contains both the requested data and the currently persisted state. It’s pretty obvious why we would return the current state, but why also include the rest? The idea is that we want the services to be stateless in the sense that the response can be understood in isolation, without reference to conversational state. Our response includes enough context so that the client can understand the response as it stands.

So I want to return an object of some complexity. I view “Already Inserted a Different Value” as a special case of an Optimistic Lock violation, and hence I’m going to use a more general purpose object to hold the two values of interest, the current request and the value we find in the store.

     @XmlAccessorType(XmlAccessType.PROPERTY)
     @XmlRootElement(name = "OptimisticFailure")
     public class OptimisticFailure {
  
         private BookEdition m_requested;
         private BookEdition m_unexpectedValue;

         // and getters, setters and zero-arg constructor

Note that we can imagine presenting this information to the user, saying “You asked to create a record like that, but we found one like this. Would you like to make any amendments?”

So, how do I code the return of such an object as part of the error processing? For this I use a specialised Exception Class.

Throwing Custom Exceptions

First I defined a Custom Exception, this is derived from WebApplicationException

    public class LibraryDuplicateInsertionException 
                extends WebApplicationException {
  
    private OptimisticFailure m_failureDetails;
    public OptimisticFailure getFailureDetails() {
        return m_failureDetails;
    }

    public LibraryDuplicateInsertionException(
                           BookEdition request,
                           BookEdition alreadyThere ) {
        m_failureDetails
                  = new OptimisticFailure(request, alreadyThere);
    }
}

I can then complete the service implementation:

            if ( edition.equals(alreadyThere )){
                return Response.ok(alreadyThere).build();
            } else {
                throw new LibraryDuplicateInsertionException(
                                                     edition, alreadyThere
                                                     );
            }

Before running this code one further task remains, and Exception Mapping Provider is needed.

Exception Mapping Providers

An Exception Mapping Provider is responsible for creating a Response object from the Exception information. JAX-RS specifies that the provider must implement the Interface ExceptionMapper<T> and so I created this Provider class:

@Provider
public class LibraryDuplicateInsertionExceptionMapper  
  implements ExceptionMapper<LibraryDuplicateInsertionException>
{
    @Override
    public Response toResponse(
               LibraryDuplicateInsertionException ex)
   {
         return Response.status(Status.CONFLICT)
                .entity(ex.getFailureDetails())
                .build();
    }
}

Note:

  • The use of the @Provider annotation
  • the toResponse() method has responsibility for transforming the exception to a suitable response. This is a very simple example giving a single response code 409 – CONFLICT and returning a simple payload; the Exception was designed to have suitable data available.
  • The OptimisticFailure  returned by getFailureDetails(). already had JAXB annotations

Finally I announce this new provider to the Wink runtime. You may recall that Wink servlet’s parameter refers to the WEB-INF/application property file, which originally contained just the name of the ResourceManager class.  I add the name of the new provider class to this file.

    org.djna.library.service.BookEditionResources

    org.djna.library.service.LibraryDuplicateInsertionExceptionMapper

I can now execute test sequences such as

  1. Delete record with ISBN 1234  – this is idempotent and so always succeeds whether or not the record exists.
  2. Insert record with ISBN 1234 – should succeed, no record can exist
  3. Insert identical record with ISBN 1234 – as it is identical, although it is a duplicate insertion is treated as succesful
  4. Insert a record with ISBN 1234 but differences in other fields. This fails with 409 – CONFLICT and a payload containing current record and new requested record.

Validation Errors

It should be clear that validation errors can be addressed in the same way, returning a complex object that contains the error details. I think a simple collection of Name/Value pairs may be the simplest way to capture a set of validation results. For validation of simple Bean the name can be either a specified field or “global” for more general validation errors.

7 Responses to “Whatever can go wrong … Error Handling and RESTful Services”

  1. Lior said

    interesting post, thanks.
    we are also using wink to develop our RESTful API, but we found the described testing style a bit cumbersome, since you need to have a running server in order to execute these tests. so running this kind of tests as part of the build process requires deploying and configuring the server to run.
    I was wondering if you can recommend a “pure” unit testing techniques that doesn’t require running server, e.g. using mock objects.

    • djna said

      Thank you for your comments. I do agree with you that a true Unit Testing approach is desirable. I haven’t thought about this yet and don’t have any off-the-shelf answer for you. However it’s such a good question that I’m going spend a little while today thinking … more later.

      • Ben F said

        Lior, when I played with Wink, I used EasyMock to act as the server. With EasyMock, I could specify how Request and/or Response should behave. This arrangement was much more convenient and a joy to have as insurance during a time of rapid development.

        Dave, excellent posts. I have one thought… any strong reason to use HTTP status codes inside the RESTful Java class? I did the same to start with, but then I thought why not throw exceptions instead… e.g., ServiceUnavailableException, and have this exception mapped to a HTTP status code elsewhere. My impression is that it’s nicer to do it the Java way when handling the RESTful call, instead of muddling Java exceptions with HTTP codes in the code. Have the two separated. Stick to just Java exceptions, and HTTP codes can be mapped to the exceptions separately. I found this useful because it was starting to get messy when I started to add in NullPointerException, RuntimeException, etc., conversion to HTTP codes, all over the place. I had cases where one RESTful method calls other RESTful methods (e.g, recursive, group, etc.), so this method receives exceptions instead of having to get around HTTP codes. More convenient.

        Of course your taste/milage may vary, etc., and I don’t claim to be expert on this matter.

        Ben

      • djna said

        Thanks Ben. I’ve expanded the error handling posting to include the throwing of Exceptions when there’s a significant payload to return. You make a very good point that in a general programming model the use of Exceptions is preferable to building error responses directly in the service routine. I will expand on this point in a future posting.

  2. Roland Barcia said

    For duplicate record, you have to consider weather we are talking about the case where the ID is generated by the provider. In the consumer ID generated case (POST /resource/id), duplicate records are less of an issue. A 409 code would be ok if someone tries to POST with the same ID.

    The provider generated case, there is a proposal for called POST EXACTLY ONCE (POE). A special header the consumer sends with the initial request. The provider should store this so any subsequent request would return.

    http://www.imc.org/atom-protocol/mail-archive/msg08072.html

    http://hughw.blogspot.com/2005/07/reliable-post.html

    • djna said

      Thanks Roland. I think that for a service to be fully idempotent it must somehow be able to identify repeated requests. Clearly POE is one way to achieve this, a user supplied id is another, otherwise some otehr “protocol” between client and server is needed – for example the server needs to keep a record of all requests and spot duplicates that way.

  3. [...] of postings I was considering various ways of Testing RESTful services, and specifically described using JUnit to test the service, exploiting the Apache Wink Client libraries. I was interested to see a comment [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: