Sound advice - blog

Tales from the homeworld

My current feeds

Thu, 2008-Jan-24

Realtime Messaging over Unreliable Networks (discarding intermediate state and intent)

Embedded systems have to work under adverse conditions without human supervision. Embedded components that participate in distributed systems need to work despite arbitrary network delays, and with periods under which available bandwidth is extremely low. In these environments it is important to simplify communication to the basics, and these basics closely align to REST messaging on the Web.

REST is about designing interfaces around state transfer, either to or from a resource using standard methods, and in a standard format. Consider the case of an online jukebox. A client can sample the state of http://jukebox.example.com/current-track/title with a GET request. Having received the response in (say) a text/plain format with "Ring of Fire" as the value, the client is able to interpret the result or present it to a user. A subscription relationship could provide follow-up representations as the track changes over time.

A PUT (or DELETE) request transfers information in the other direction. Instead of simply capturing the state of a particular variable, it also captures client intent. An authenticated and authorised client PUT to http://jukebox.example.com/playing of text/plain "0" may pause the jukebox. The client may PUT "1" or "0" to the URL as its intent changes through time from paused to playing and back again.

All networks are to some extent unreliable. Packets get lost due to congestion or other factors, and this translates to delays and constrained bandwidth over the TCP layer. Reliable embedded systems cannot assume that they will be able to transfer every state they intended. While we can assume that any individual message will arrive eventually at its destination, a sequence of messages that grows at a rate faster than available effective bandwidth may never be fully delivered. Worse still, even temporary poor bandwidth conditions can trigger excessive delays in conveying a server's most recent state or a client's most recent intent if messages sitting in the queue before the most recent state or intent must be processed first.

The nature of a PUT or DELETE request is that previous resource state is obliterated. This poses a curious solution to the poor bandwidth problem, where intermediate intents are simply dropped. Consider an indecisive user who clicks "pause", then "play", then "pause" again. The intermediate "play" request can be completely discarded if it failed to get through quickly. The client can keep a buffer of only the latest state it intends for the variable, only sending and retrying that state. This can be implemented in generic library support code based on an understanding of the generic PUT request's semantics.

A subscription mechanism based on transfer resource state can exploit the same feature: The state of the resource being monitored can change more rapidly than the available effective bandwidth. While a client may benefit from seeing intermediate states, the only state it must see is often the most recent one.

Both changes of intent and changes of state can become more complex when resource state overlaps. For example, it may not be obvious that a PUT to <http://jukebox.example.com/currentSelection> should extinguish an outstanding request to pause the jukebox. Say the selection of a new track automatically starts the jukebox playing from a pause. This is a new intent that should extinguish the old, including any PUT request that the client has so far failed to send.

The simple solution to overlapping resources is to split these resources so that they don't overlap: Require the client to issue both a PUT to http://jukebox.example.com/currentSelection and http://jukebox.example.com/playing in order to achieve the desired compound effect.

This is not to say that our PUT requests cannot have side-effects affecting other resources. For example, the submission of any purchase order will affect accounting records somewhere. Every request is likely to have an affect on server logs, and these too may be accessible as resources.

The distinction between overlapping resources that make intent difficult to isolate and those that are merely normal side-effects is in the methods those additional resources support. It is only intent we should be disambiguating. We should be attempting to ensure that every intent is only conveyed through one resource: That multiple resources don't carry overlapping intents by their PUT requests. It is still ok that resources without PUT implementations overlap.

Clients that send requests with overlapping states need to take additional care when it isn't possible to avoid overlap of intent. In these cases the client must avoid issuing simultaneous requests to the overlapping resources.

REST's standard methods for transferring state and intent provide a platform in which generic code can identify and discard intermediate intent. Designing a system around REST methods thereby improves response times for conveying recent state and intent, along with its other benefits. The concept of conveying intent through PUT requests also influences how we design potentially overlapping resources in our architectures.

Benjamin