API Design meets Protocol Design

In my previous post I showed that software interfaces used in distributed applications have to satisfy two conditions:

  1. should be remotely accessible
  2. should be suitable for use in high latency environments which permit partial failures, have no shared state and no reliable synchronization mechanism

I also argued that technology alone – no matter which technology you choose – only helps you with the first requirement. The concepts, principles, and methods of protocol design can help you meet the second condition.

I’d understand if you were a bit surprised. Protocol design is indeed applicable because API and protocol are two alternative – but equally correct – ways of describing software interfaces. Wikipedia defines API as “… a particular set of rules and specifications that software programs can follow to communicate with each other” and protocol as “… a formal description of digital message formats and the rules for exchanging those messages in or between computing systems and in telecommunications.” We certainly tend to focus more on programming language constructs like methods with parameters when describing APIs and on processes exchanging messages when describing protocols, but ultimately these concepts describe, from a different point of view, how programs communicate with each other.

Consider the familiar real-time stock quote service as an example. We can describe it equally well by showing its API in a programming language (here, in Java):

Listing 1: Java client library interface

/**
* My sample real time stock quotes service
**/
public interface StockQuotes {

   /**
   * Returns a stock quote
   * @param ticker is the symbol of the stock
   * @return the current price of the stocks
   * @throws RemoteException if there is a communication error
   **/
   public float getQuote(String ticker) throws RemoteException;
}

or by showing the messages (requests) it accepts and replies it provides (in our case, a simple RESTful protocol)

Listing 2: REST request and reply

GET /quote/?ticker=AAPL HTTP/1.1
Host: StockQuotes.com
Accept: application/json

returns

HTTP/1.1 200 OK
Date: Fri, 17 Feb 2012 16:59:59 GMT
Content-Type: application/json
Content-Length: 29

{“ticker”:”AAPL”,
“price”:512.68}

The API description feels more natural when working via a client library while the description of the HTTP messages is useful for accessing the service from exotic environments (say Adobe Flash or SAP’s ABAP) for which no client library is provided. The same functionality could be also described as a SOAP Web Service with the following WSDL:

Listing 3: WSDL description

<?xml version="1.0"?>
<definitions name="StockQuoteServiceDefinitions">
   <!-- Namespace declarations omitted for brevity -->
   <message name="GetQuoteRequest">
      <part name="ticker" type="xsd:string"/>
   </message>
   <message name="GetQuoteResponse">
      <part name="price" type="xsd:float"/>
   </message>
   <portType name="StockQuotePortType">
      <operation name="getQuote">
         <input message="tns:GetQuoteRequest"/>
         <output message="tns:GetQuoteResponse"/>
      </operation>
   </portType>
   <binding name="DefaultBinding" type="tns:StockQuotePortType">
      <!-- Details of document literal binding style omitted for brevity -->
   </binding>
   <service name="StockQuoteService">
      <documentation>My sample real time stock quote service</documentation>
      <port name="StockQuotePort" binding="tns:DefaultBinding">
         <soap:address location="http://www.StockQuotes.com/quote/">
      </port>
   </service>
</definitions>

Frameworks like Microsoft’s WCF or Java’s JAX-WS can automatically generate a client library (called a binding) from this description. The generated code looks just like a regular API to the caller, but implements its functionality by making XML (SOAP) requests over HTTP to a server. It would be pointless to debate whether this WSDL describes an API or a protocol. To support automatically generated client bindings it has to describe both.

You might wonder: if API and protocol are equally appropriate for describing software interfaces, why is it that expressions like “Web Services API”, “SOAP API”, “RESTful API”, “Web API”, “Hypermedia API”, or  “Mobile API” are commonplace while few people ever talk about protocols? I don’t claim to know the answer. I can only tell you why I also frequently use the word API when I actually mean protocol.

I’ve noticed that the term protocol is very closely associated with ubiquitous low-level transport protocols like TCP/IP and I’m often concerned that it is not immediately obvious that I’m talking about high-level application protocols like SMTP, HTTP, Atom or LDAP. Since the latter are used just like APIs to build client applications for accessing email, browsing the web, reading news, or looking up someone’s phone number in a corporate directory, I simply call them APIs. It is technically correct and unlikely to be misunderstood. It is certainly more convenient than spelling out “application protocol” every time.

I also sometimes worry that developers may think I want them to write a ton of code, parse complex messages, and handle complex interaction patterns. If they had a college course on protocols, they were probably thought about message framing, byte stuffing, and many other low level details. This is not what I typically mean. Protocols are rarely built from scratch. They are layered on top of existing protocols, extend existing protocols, or constrain (further specify) existing protocols. Plus we have all the various tools. I use the word API in casual conversations to convey a subtle “this is not as hard as you might think” message.

Just to be clear, I’m not saying that calling your remote interface an API is a mistake. It is not. My point is that thinking of it as an application protocol is helpful, especially when you are designing it. It is helpful because it forces you to take the differences between local and remote communication seriously.

When I think in terms of requests and replies instead of function calls, it is hard for me to ignore latency or the possibility of partial failures. When I think in terms of data sent as messages instead of objects, I have no expectations of shared state. When I think in terms of independent processes, I know that there are no reliable methods of synchronizing them. When I think in a language-neutral way, it is a lot less likely that I assume language features which may not be available to some clients or may not work remotely.

The protocol viewpoint also helps me maintain realistic – some may even say skeptical – expectations about tools and technologies I’m using. I rely on them to take care of low-level protocol details and automatically generate repetitive, boilerplate code. I accept, however, that pretty much everything else is my responsibility.

In my next post I’ll talk about how I apply protocol design concepts, principles, and methods to the concrete case of RESTful design and how this helps me understand – and chose from – the many contradictory advice that are out there.

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.

Advertisements

Challenges of remote interface design

If you listened to some middleware vendors, you’d believe that distributed applications can be easily built by making local APIs remotely accessible. Indeed, as this 10 minute video tutorial illustrates, you can do the latter by simply adding a few annotations to your code. Vendors have products to sell and their focus on promoting ease of use is not entirely surprising.

The problem is that this approach does not work. It was convincingly demonstrated in classic papers like “A Critique of the Remote Procedure Call Paradigm” by Professors Andrew S. Tanenbaum and Robert van Renesse (1988) or more recently by Jim Waldo and his colleagues at Sun Microsystems Laboratories in “A Note on Distributed Computing” (1994).

A simple example will illustrate why. Consider a local FIFO task queue used in desktop applications to maintain the responsiveness of the user interface while processing computation-intensive tasks (Listing 1). The user interface thread creates and places tasks in the queue and a background thread processes them asynchronously (Listing 2).

Listing 1:

/**
 * A FIFO queue of tasks to be executed by a background thread
 * Note: This queue implementation is not thread safe
 */
public class TaskQueue {

    /**
     * @return True if the the queue is empty, False otherwise
     */
    public boolean isEmpty() {...}

    /**
     * Places a task into the queue
     * @param task the task to execute
     */
    public void putTask(Task task) {...}

    /**
     * Retrieves the next task from the queue
     * @return a task to execute
     */
    public Task getTask() {...}
}

Listing 2:

/**
 * Simple client showing the use of the task queue
 */
public class Client {

    final TaskQueue queue = new TaskQueue();

    /**
     * Called when the user chooses to print from the GUI
     */
    public void onPrint() {
        Task printTask = new PrintTask();
        synchronized (queue) {
            queue.putTask(printTask);
            queue.notifyAll();
        }
    }

    /**
     * This method runs in its own thread
     * @throws InterruptedException signals the thread to exit
     */
    public void processTasks() throws InterruptedException {
        Task task;
        while (true) {
           synchronized (queue) {
               while (queue.isEmpty()) {
                  queue.wait();
             }
             task = queue.getTask();
           }
           task.run();
        }
    }
}

Let’s say that we want to move the processing of tasks to a different computer. As platform vendors would quickly point out, making the queue interface (Listing 1) remotely accessible is easily solved with a wide variety of technologies. The difficult problems are latency, possibility of partial failures, lack of shared memory, and synchronization.

Network latency degrades the performance of some API calls by orders of magnitude, rendering them practically unusable. The local queue works well because the overhead of a call to the local putTask() method is both small and predictable. If the queue is on a different computer, a remote call is neither quick nor predictable. Transient network events like packet loss or congestion may block a call for a significant time. Because it no longer prevents the user interface from freezing up, moving the queue to a server renders it useless.

Partial failures lead to non-deterministic behavior and potential data loss. Assume that we kept the queue on the client to eliminate the latency issue discussed above. What happens if the network fails while the server calls the remote getTask() method? If the failure happens before the call is processed by the queue, the server can safely retry it. However, if the failure occurs after the task was removed from the queue, but before it was delivered to the server, the task is lost, and a retry will get the next task from the queue. Since we can no longer use the queue to reliably submit tasks for processing, keeping the queue on the client does not work either.

Lack of shared memory means that object references, pointers, or global state cannot be handled transparently. When a task is transferred to a different computer, the question arises what to do with the other in-memory objects it references. While sometimes it makes sense to move the referenced objects with the task, just as often we needed to look up the corresponding objects on the server. No technology can automatically handle all situations correctly, meaning that unless we change our queue and task implementations, some tasks will fail to execute on a remote server.

The absence of reliable remote synchronization primitives makes even relatively simple local behaviors difficult to replicate in distributed environments. The two local threads use the built-in Java synchronization primitives to communicate with each other (Listing 2). These synchronization methods do not work remotely. Server code written as shown won’t work. We need to rewrite it, perhaps polling the queue at regular intervals. But now the calls to the queue are no longer synchronized, and unless we make the queue implementation fully re-entrant, fatal data corruption will likely occur.

So what all of this boils down to in the end? It shows that all technologies used to make local APIs remotely accessible leak. By leaking I mean that they change the API behavior. And two APIs with different behaviors are not the same API, even if they look the same. Joel Spolsky calls this the Law of Leaky Abstractions, explaining that “one reason the law of leaky abstractions is problematic is that it means that abstractions do not really simplify our lives as much as they were meant to. Code generation tools which pretend to abstract out something, like all abstractions, leak, and the only way to deal with the leaks competently is to learn about how the abstractions work and what they are abstracting. So the abstractions save us time working, but they don’t save us time learning.”

The “don’t save us time learning” part is the reason why I don’t like calling the design of remote software interfaces “API design”. While technically correct, this choice of words encourages a false and somewhat dangerous sense of familiarity and comfort. I’ve found a better concept, one which does not swipe under the rug the important issues of latency, partial failures, lack of shared memory and synchronization. I’ll talk about this concept in the next installment.

 

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.

2.1.8. Favor abstract classes over interfaces for decoupling API from implementation

Note: I’ve picked this Java API Design Checklist item to let you know that  I’ve been quietly adding similar details to the checklist and will add more in the future. To see these details, open the list (also available from the main menu at the top of the page) and click on [explain] where available.

Rationale:

Safety and Evolution. Abstract classes can be used everywhere Java interfaces can with the limitation that a class can only extend a single (abstract) superclass while it can implement multiple interfaces. In most APIs Java’s lack of support for multiple class inheritance is not an issue because the interfaces are not intended to be implemented by clients. In these cases abstract classes are the better design choice.

In Java you cannot prevent clients from implementing public interfaces. When you use an interface for a method parameter type, the compiler only guarantees that it implements the correct method signatures; you can never be sure it also implements the correct behavior. You are left with only two choices: either you perform extensive runtime checks or blindly trust the implementation, risking serious consequences, such as data corruption. Unlike interfaces, abstract classes can have code to enforce constraints on behavior. For example, you can prevent clients from extending abstract classes, check preconditions, verify postconditions, and guarantee invariants as illustrated by the examples bellow. You cannot do any of these with interfaces.

Do this:

public abstract class ApiClass {

    //Package scope constructor prevents clients
    //from extending this class
    ApiClass() {}
}

Do this:

public abstract class ApiClass {

    //This public method guarantees preconditions and postconditions
    //for any abstract method implementation
    public final Collection find(String query) {
        if(query == null) {
            throw new NullPointerException("Null query parameter");
        }
        if(query.isEmpty()) {
            throw new IllegalArgumentException("Empty query parameter");
        }
        Collection result = findImplementation(query);
        if(result == null) {
            result = new ArrayList();
        }
        return result;
    }

    protected abstract Collection findImplementation(String query);
}

Do this:

public abstract class ApiClass {

    /*
    * Garanteed invariant:
    *   value != null &&
    *   value != this.lastModified &&
    *   value.before(new Date()) &&
    *   oldValue.before(newValue) &&
    *   value unchanged => class unchanged
    */
    public final Date getLastModified() {return (Date)lastModified.clone();}

    public final void anyMutatorMethod(Param param) {
        anyMutatorMethodImplementation(param);
        updateLastModified();
    } 

    protected final void updateLastModified() {
        Date newValue = new Date();
        if(lastModified.before(newValue)) {
            lastModified = newValue;
        }
    }

    protected abstract void anyMutatorMethodImplementation(Param param); 

    private Date lastModified = new Date();
}

Exceptions:

You should use abstract classes only when they are not intended for client extension. You may provide abstract classes for client extension, but you should not force clients to extend them, since Java does not support multiple inheritance. Use Java interfaces instead and provide abstract classes which implement them for convenience. You can see this approach used in the Java collections library, which contains pairs like Collection and AbstractCollection, List and AbstractList, and so on. Clients are encouraged to extend the abstract classes and even when they decide to implement the interfaces directly, they can use the provided abstract classes as guidance for a correct implementation.

For simple interfaces, such as callbacks, there is no need to provide corresponding abstract classes.

Of course, whenever possible, concrete classes are preferable to abstract classes.

What’s with the Creative Commons license?

As you might have noticed, I added a “This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.” notice to all content on this site. What does this mean?

Some of you are hosting full, abridged, or translated (hi there, in China!) versions of my posts on your own site. This notice says that you are more than welcome to do so, provided that:

  • You mention my name, Ferenc Mihaly, as the author
  • You provide a link back to the original content on this site

Most of you are already doing this, which is appreciated. This is it, in a nutshell. And thanks for your interest!

1.1.1. Favor placing API and implementation into separate packages

Note: I’ve picked this Java API Design Checklist item as an illustrative example, not because it is of a particular importance. I’ve added similar details to other checklist items as well and will add more in the future. To see such details, open the list (also available from the main menu at the top of the page) and click on [explain] where available.

Rationale:

Simplicity, Consistency, Safety and Evolution. Java only supports public and package scoped classes. You should obviously never mix public implementation classes with APIs (see checklist item). You can only place package scoped classes into API packages if you are certain they will be never needed in any other (implementation) package. Otherwise developers may inadvertently change their access to public, breaking the encapsulation of the API.

More importantly, Java module systems like OSGi use package boundaries for additional class loader isolation, dependency management and versioning. You won’t be able to take advantage of it if you combine API and implementation into one package.

Do this:

package com.company.product;

public class ApiClass {
   private ImplementationClass m;
}

package com.company.product.internal;

public class ImplementationClass {...}

Don’t do this:

package com.company.product;

public class ApiClass {

   private ImplementationClass m;
}

class ImplementationClass {...} //package scoped

Exceptions:

Very rarely, a small number of package scoped classes are useful when a separate implementation package adds no benefits, only complexity.

 

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.