Skip to Content
When a client initiates an action on a service by sending it a command message, it is reasonable to assume that that client will want to know about the success, or failure of that action. When working with today’s Web Service technologies, many developers perform these Web Service calls synchronously and have the service throw an exception to provide details about the specific failure. This is especially common when the Web Service call needs to return some other data, for instance the ID of the entity just added. Scalability issues around these kinds of synchronous interactions are leading the industry to more asynchronous message-based communications. The only problem is that the stack-based exception model for dealing with failure breaks in the asynchronous model.  [Originally published here.]

Let’s see some code that demonstrates simple, synchronous communications and handles some error case:

public class UserController
{
     public void AddUser(string username)
     {
          try
          {
               int userId = myServer.MyService.AddUser(username);
               // add user to list of users
          }
          catch(DuplicateUsernameException)
          {
               // notify user and ask to choose a different user name
          }
     }
}

Once we move to an asynchronous interaction model, the thread that makes the Web Service call is no longer waiting for a response – in other words, the call to “AddUser” in the above example has a “void” return type. This begs the question how would the user ID be returned, let alone information about the success, or failure of the call.

The pattern which handles this scenario is called Asynchronous Completion Token. This pattern suggests having the client pass an additional object as a part of its call to the Web Service. This object would contain both the logic and data needed to handle the result of the call. In .NET, asynchronous invocations are supported with the AsyncCallback delegate and its corresponding IAsyncResult interface for getting the data – let’s take a look at the resulting code:

public class UserController
{
     public void AddUser(string username)
     {
          myServer.MyService.AddUser(
               username,
               (AsyncCallback)delegate(IAsyncResult result) {
                    // remember that this code runs on a background thread
                    int userId = Convert.ToInt32(result.AsyncState);
                    // add user to list of users
               }
          );
     }
}

The code defined as the asynchronous callback will be run by a background thread at some time after the original thread that called “AddUser” had already exited the method. The next question is “what happened to the exception?”, or how do we use exceptions in the asynchronous case.

The simple answer is that we can’t use exceptions to handle these errors – simply because there is no thread that is waiting to catch such an exception even if it were thrown. This is strictly a client-side limitation, on the server-side there is no problem in throwing an exception – since it will be caught by the Web Services infrastructure and marshaled to the client. What this means is that we need an alternative mechanism for returning error information to the client. At the most basic level, this means using return codes, integer values representing the various error conditions. Going one small step up, we can use an enumeration to communicate the intent of each value like so:

public enum MyServiceErrorCodes { NoSuchUser, DuplicateUserName, ... }

Unfortunately, we find that the basic .NET types are too generic to support this scenario in that we need to support both the information returned by the Web Service and the error code. If we assume an integer based error code, we can create our own implementation of the Asynchronous Completion Token and use it like this:

public delegate void AsyncCompletionToken(object state, int errorCode);

public class UserController
{
     public void AddUser(string username)
     {
          myServer.MyService.AddUser(
               username,
               (AsyncCompletionToken)delegate(object state, int errorCode) {
                    // remember that this code runs on a background thread
                    if (errorCode == (int)MyServiceErrorCodes.DuplicateUserName)
                         //notify user
                    else
                    {
                         int userId = Convert.ToInt32(state);
                         // add user to list of users
                    }
               }
          );
     }
}

In order to make this code work, we need to change some of the infrastructure-level behaviors of the Web Services stack, something that is much easier to do with Microsoft’s Windows Communication Foundation (WCF) than with previous implementations (like the Web Service Enhancements and the basic XML Web Services of .NET). A coming article will show exactly how to do this.

The important thing to understand when moving to asynchronous communications between clients and Web Services is that the old exception-based models for communicating errors no longer work. This impacts the way client-code is written to a large degree; server code is affected as well – returning error codes rather than throwing exceptions, but this is a relatively minor change. One final note, in both the synchronous and asynchronous cases the errors returned by the Web Service are a part of its contract and need to be versioned carefully. The move to increasingly scalable Web Services comes with an increase in complexity as well. Hopefully this article has helped you better understand the details of how to make asynchronous Web Services work for you.

To report this post you need to login first.

8 Comments

You must be Logged on to comment or reply to a post.

  1. Anton Wenzelhuemer
    Thx for this interesting blog.

    Do you have any substantial evidence for your statement

    Scalability issues around these kinds of synchronous interactions are leading the industry to more asynchronous message-based communications?

    Do you know any reasonable, contemporary use cases where asynchronous services are considered to be sufficient to orchestrate ‘synchronous processes’?

    Wouldn’t it considered to be a step back in time when the price for the move from monolithic to distributed architectures were an increased level of asynchronism in existing processes?

    regards,
    anton

    (0) 
    1. Udi Dahan Post author
      Anton,

      Glad you found the post interesting.

      When dealing with scalability and availability, we need to create a decoupling between when the client requests/commands something, and when, and which, server processes it.

      You’ll find that these interactions are common in Amazon’s and EBay’s architectures. Does that evidence suffice?

      It is not my view that asychronous services orchestrate synchronous processes, at least, not if those processes were external to the service.

      Is this style similar to the mainframe batch-job style of work? In a way, yes. Does that make it monolithic? No. I have seen too many non-mainframe distributed architectures that are both synchronous and monolithic to believe that working asynchronously would create something that wasn’t already there. Actually, I have seen that distributed architectures based an asynchronous interactions are better-factored and less monolithic than their synchronous peers.

      (0) 
      1. Udi Dahan Post author
        That is correct – a synchronous interface provides the simplest model to get things working quickly; ie, the reach. The whole point of EBay’s public API is exactly that – make it easy for developers to get things up and running quickly.

        EBay’s internal architecture is another story altogether. All of the top-end sites I know of choose Availability and Partitioning over Consistency (in the CAP model).

        (0) 
  2. Andre Truong
    I mean if the service is to create or get a piece of information with expectation of a functional response, it wouldn’t make sense functionally speaking to talk about asynchronous operations, right?

    Could this be just a design issue? either way technically speaking you can always have it sync or async as long as it makes sense functionally speaking.

    Well at least that’s how i see it in the abap world. it’s a non technical issue. SAP BAPIs and enterprise services are for the most part synchronous by design but you can always call then asynchronously if you want to.

    I guess it depends on the requirements more than technology. Is it a technical issue within the .NET environment?

    (0) 
    1. Anton Wenzelhuemer
      I do agree with your arguments, andre.

      moreover, in my (non-technical) world I do always expect some kind of synchronous acknowledgment if I interact with some kind of service. at least i want to have some kind of synchronous confirmation that my request has been received, is syntactically correct,whatever and – in rare circumstances – an information that my issue will be processed at a later point in time.

      As a consumer I don’t want to create a callback token but I rather want the service – if necessary – to supply me with an incident ID for a follow-up  status request.

      anton

      (0) 
      1. Udi Dahan Post author
        Anton,

        Would you prefer the ack to arrive quickly, or that the development model is synchronous? In many cases, we use an asynchronous model at the core, and wrap it with a synchronous programming model – trying to get the best of both worlds.

        For distributed choreography, though, we prefer working asynchronously.

        (0) 
    2. Udi Dahan Post author
      It is very much an issue of service factoring. When working with business-level services, we find a publish/subscribe interaction model (EDA if you will) to cover more ground than the synchronous model.

      It is very much NOT a technical issue – it is where Enterprise Architecture and Solutions Architecture meet. The responsibilities of the services map very closely to those of their respective business units.

      (0) 

Leave a Reply