The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Design paradigm for asynchronous messaging UI

I'm building a client-server app right now that's using asynchronous messaging, and I'm trying to find examples of other programs that work the same way, so that I can avoid doing stupid things that are not yet obvious to me.

The typical use case is:  the user performs some action in the UI that generates a request message.  At some point (soon, one hopes, but it will not always be), the server processes the request and emits a response.  When the client receives the response, it (generally) performs some kind of update to its model, which then gets reflected in the view.

There are a number of failure modes that I don't quite know how to handle properly:

o What happens when a request doesn't receive a response?
o What happens when the user sends the same request multiple times?
o What happens when the client receives a response it's not expecting?

Additionally, I want to provide the user with some way of being able to tell what's going on without forcing too many details on him.  For instance, I *could* deal with the multiple-request problem by not letting the user make the same request a second time, but then I need to be able to tell the user why the button is disabled.  (It's not a button, and it's not disabled, but I'm trying to keep the description here simple.)

But the thing is, the user doesn't think "I'm making requests and waiting for responses."  The user is (for the most part) either requesting data from the server, or entering data and committing changes back to the server.

I've got technical issues underlying this ironed out.  I'm not worried about race conditions or critical sections.  What I'm worried about is making a UI that makes what's going on comprehensible.

And it occurs to me that there must be other programs out there that do this.  Obviously email clients and web browsers do this, but they handle very different use cases.  (Email clients are explicitly *about* messaging, while my application just uses messaging as a means to an end, and web browsers aren't really merging the responses with locally-maintained state information.)

What are some paradigms that I can use for this kind of application?  I feel like some must be in front of my nose and I'm just not noticing them because I'm too involved in the specific problem I'm trying to solve.
Robert Rossney Send private email
Thursday, June 28, 2007
Our app does something conceptually similar but probably way, way less sophisticated (and less difficult). Basically, we let people launch reports that run on a server somewhere, and whenever one completes (or abends), the UI gets a message telling them to refresh their report status (and possibly send the report to a local printer). This scheme works really well for us and is super reliable. But it doesn't involve any real GUI updating as you imply you want to do.
Greg Send private email
Friday, June 29, 2007
1) "what happens when a request doesn't receive a response?" First, the server should acknowledge the request as soon as possible. Second, the server should update the client on progress periodically (or, at least, send an "I am alive" message periodically). Third, there should be short client-side timeouts on both the ack and progress responces, if a timeout expires then the client should either resend the request or indicate that the server is dead. Finally, there should be a relatively long client-side timeout on the entire request, again, if the timeout expires either the request should be resent or the client should indicate that the server is dead.

2) "What happens when the user sends the same request multiple times?" If you are acking the request and sending progress updates then this should be an infrequent occurance. Under any circumstance, requests should have sequence numbers, multiple identical requests should have identical sequence numbers. Only one request with a given sequence number should be acted upon, the other should be discarded. Usually the request that is acted upon is the first one or the last (most recent) one, but that depends on what the kind of service.

3) "What happens when the client receives a response it is not expecting?" The client should discard any such message, probably without notification to the user (unless there is a security concern, then the user should be alerted in the most unmistakable way possible), but the client should inform the server of the unexpected response and the server should scream bloody murder to the operators.

In general, in any communication protocol, messages should be marked with sequence numbers to prevent duplicate activity (responses should carry the sequence number of the request that triggered them) and all messages should repeat until acknowledged (except, of course, for acknowledgement messages, in order to prevent infinite ack loops). Finally, anything that expects an ack should also have a timeout, after which the other end of the conversation is assumed to be dead.

With a well designed protocol there is not reason that you can't provide full feedback to the user, almost as if the service were entirely local.
Jeffrey Dutky Send private email
Friday, June 29, 2007
Without more specifics, it’s hard to give more than general principles. At a minimum, you should provide feedback on when the user makes a request and when a response is received. The feedback should be tied to the specific action the user made (e.g., be located with the control used to launch the message, as opposed to, say, using the hourglass-and-pointer sprite or a browser-style throbber). In general the intensity, explicitness, and detail of the feedback is proportional to the importance of the feedback to the users (i.e., the degree differences in system responses alters their workflow) and inversely proportional to the speed and reliability of the communication. Furthermore, feedback should generally be mild enough that it’s not distracting when things are normal, but builds or accumulates if something is wrong so that the user is alerted.

Suppose the communication is fast (average <500 ms response), reliable (>99.5%), and feedback is not important (users don’t have to wait for a response before proceeding to the next step in the task, even if the response is delayed for several seconds; if there is a problem, users’ only option is to wait for things to get corrected –no work is loss). For this, you may use subtle graphics changes to your controls for feedback. Maybe when a user adjusts a control that launches a message (e.g., changes the value of a dropdown list box), the border around the control can become a dashed line, suggesting a “tentative” value. When the response is received, the border reverts to a solid line. A control briefly getting a dashed border will barely be noticed, but if the whole screen starts filling with dash-bordered controls, the users should realize something is amiss.

Suppose communication is slow (2000 ms average), unreliable, and feedback is important (in some situations, users need to wait for a response before proceeding; if there is a problem, users have to troubleshoot it themselves). For this, you may use separate adjacent displays to distinguish what the user commands and the actual state of the system. There should be a graphic indication when there is a mismatch to alert the user that a message is being processed (e.g., the display of the commanded value only appears when it’s not the same as the actual value). The feedback may need to indicate if the delay is unusually long (e.g., draw a border around the commanded and actual value displays if no response after 10 seconds). If possible, the feedback should distinguish between the server running slow and not responding at all, perhaps by indicating progress. Reception of unexpected communication may need to be highlighted more, maybe using some brief animation such as a single throb of background color. In the extreme case, if the user must respond immediately to such a communication before doing any other work, a message box may be used.

If the communication is really cranky (many seconds of delay, <90% reliability), you may want to consider presenting users with an explicit message queue so users can monitor message status, cancel or retry troublesome messages, and maybe even edit and save the lot for later re-execution when the communication is better. In such a system, of course, message management becomes a major part of the user’s task.
Michael Zuschlag Send private email
Friday, June 29, 2007
>> There are a number of failure modes that I don't quite know how to handle properly:

o What happens when a request doesn't receive a response?
o What happens when the user sends the same request multiple times?
o What happens when the client receives a response it's not expecting? <<

This is a complex subject. Jeffrey outlines the elements of one particular approach, but there are several others. For an interesting analysis of the issues involved, this is a reasonable introduction:

Note that with a single client, idempotence for your specific app is likely to be much easer to solve than hinted at in the aforementioned essay.

From the UI point of view, you can show the network health and status of messages as long as you're happy to show this level of detail to the end-user.

But it seems that you want to conceal this level of detail. That makes the UI much more difficult, and a good approach is really context-dependent.
Mark Pearce Send private email
Friday, June 29, 2007
I think I misunderstood the third point concerning unexpected responses, and I'm still a little unclear on what you mean: it seems to me that an unexpected response could be one of several things:

1) an error or failure report from the server indicating that the request could not be processed. Personally, I don't think that this is unexpected, but I've seen lots of people write code that seemed to assume that errors never happen.

2) a response indicating some other condition on the server that isn't specifically related to a user request. I don't consider this a response, since it is not related to a request, but more of a server status notification.

3) a response that was intended for another client, but that got, somehow, misdelivered. From the client's point of view this is an outright error. It may indicate that someone is interfering with the communication channel or attempting to masquerade as a legitimate server, which would be a serious security problem. This is how I originally interpreted your question.

As I said, the first case shouldn't be unexpected. You should have designed the client with the assumption that requests might fail. In the second case you probably don't need to do too much to the protocol: if the server is experiencing problems then it probably doesn't really care if the clients got the status message, so the client doesn't need to acknowledge receipt of server status messages and the server doesn't need to resend them to ensure receipt. In the third case you either have a serious flaw in your server code or you have a serious security problem. At the very least the server needs to be informed and log the issue, so it can be addressed.
Jeffrey Dutky Send private email
Friday, June 29, 2007
Matching answers to questions -- we just numbered each Q/A pair on each connection.

You can get responses to questions which "haven't" been asked if you're lazy about the way you handle cancelling the questions. Actually it's easier to do that in many cases.

If the user does an action, you queue up the question.

The user cancels the action, so you send a message to cancel the question...

The problem is that you may not be able to cancel the question; the answer may already be in the outbound communications stack at the point the cancellation arrives at the server. Without delving into the stack to erase data, you can't stop the answer being sent.

If you start messing around answering questions which cancel questions to say whether the cancel worked or not you can end up in horrible recursions. The simple solution is at the cancellation point, you bin the question.

Then when an answer arrives which doesn't have an outstanding question, you just bin it.

Status requests; we had questions which basically subscribe the user to status updates. Each time something happens, a message arrives which says "More of the answer to Q658; this happened. This is not the end, my friend."

The question objects know how to handle replies; in these cases by updating displays and the like.

This also allows progress indicators. "Q355 is being worked on. We're 10% done. More of this answer to come later."

When the user doesn't want the updates, they bin the question and that cancels it which stops the updates arriving.

Normal questions come back with "Answer to Q436; blah blah blah. Question complete."

You can catch repeated questions on the server end by detecting actions which are "in progress" and replying "Please do not push this button again. End of response."
Katie Lucas
Friday, June 29, 2007
I always thought with asynchronous messaging that you want to retrieve the messages asynchronously, but place the messages into a buffer or container. Then when your application is ready to process the request/messages it can do it within safe confined area of the application.

Hell even trying to handle the logic and flow control throughout your program when at any one point or state a message is retrieved and need processing now would become a nightmare.
Entity Send private email
Monday, July 02, 2007
Thanks for all the suggestions and advice.  It was already pretty clear to me that the single-request/single-response model was not going to fly.  My prototype isn't maintaining an internal queue of requests sent just yet, but it will soon.

This particular application is helped (or hindered, depending on your perspective) by the fact that the server application is talking to a legacy system through an API that's not thread-safe.  That's one of the things that made a messaging architecture preferable to, say, a web service:  since the server has to process transactions serially, we might as well use queues to interoperate.
Robert Rossney Send private email
Monday, July 02, 2007
o What happens when a request doesn't receive a response?
Timeout. However request / response is not asynchronous but synchronous since the execution thread on the client waits on the execution thread on the server. See my other comments below.

o What happens when the user sends the same request multiple times?
Unless there is a clear way to determine it's the same request, the server needs to reply to every single request. Otherwise the server responds to just the first one and drops the rest.

o What happens when the client receives a response it's not expecting?

- client subscribes to event/topic on the server.
- client submits a request message to the server (typically on a queue). The server acks or nacks the submission.
- server processes submitted request (it dequeues the message and then processes the contained request; the processing may take a long time). Upon processing completion it posts the result as a client accessible resource and it signals all the event/ topic listeners.
- clients, when signaled, access the result resource the event/topic from the server.

Some small print:
- the clients should not read the response on the listener callback. They should enqueue the result availability and read it asynchronously.
- on the server side the results should be periodically purged.

This is the observer pattern implemented over a network. Some related technologies: JMS, JavaSpaces.
Dino Send private email
Tuesday, July 03, 2007
See Gregor Hohpe's excellent . Curiously I don't find the word "error" on the home page, but I'm sure he covers the kinds of situations you're talking about.

I've been thoroughly entertained by asynch messaging since the early 90s. Before I found Hohpe's page, I wrote my own here:
Stan James Send private email
Thursday, July 05, 2007
Thanks for those links, Stan.  The magic word you were looking for was not "error" but "invalid", which he's got covered.
Robert Rossney Send private email
Monday, July 09, 2007

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
Powered by FogBugz