views:

223

answers:

7

We have a discussion going on in my team at the moment, and I'd be interested in other views. Suppose we have a RESTful web service whose role is to annotate documents by applying a variety of analysis algorithms and services. The basic interaction in clear: we have a resource which is the document collection; the client POSTs a new document to the collection, gets back the URI of the new document, then can GET that docURI to get the document back or GET {docURI}/metadata to see the general metadata, {docURI}/ne for named entities, etc. The problem is that some of the analyses may take a long time to complete. Suppose the client GETs the metadata URI before the analysis is complete, because it wants to be able to show partial or incremental results in the UI. Repeating the GET in future may yield more results.

Solutions we've discussed include:

  • keeping the HTTP connection open until all analyses are done (which doesn't seem scalable)
  • using content-length and accept-range headers to get incremental content (but we don't know in advance how long the final content will be)
  • providing an Atom feed for each resource so the client subscribes to update events rather than simply GETting the resource (seems overly complicated and possibly resource hungry if there are many active documents)
  • just having GET return whatever is available at the time (but it still leaves the problem of the client knowing when we're finally done) [edited to remove reference to idempotency following comments].

Any opinions, or suggestions for alternative ways to handle long-lived or asynchronous interactions in a RESTful architecture?

Ian

+2  A: 

I would implement it the following way:

1) client requests metadata
2) server returns either actual data (if it's already available) or NotReady marker
3) client asks server when data will be available (this step can be merger with previous)
4) server returns time interval (there might be some heuristics for total number of executing jobs, etc)
5) client waits for specified period of time and go to step 1

This way you can provide data to clients as soon as possible. You can shape server load by tweaking delay interval returned at step 4)

aku
Thanks for the response. This would have the data available to the client all at once when it's ready, whereas I'd like to present data incrementally to the UI as it becomes available.
Ian Dickinson
Ian Dickinson, not necessary. You can return partial data but mark them as Incomplete and provide polling interval.
aku
+3  A: 

providing an Atom feed for each resource so the client subscribes to update events rather than simply GETting the resource (seems overly complicated and possibly resource hungry if there are many active documents)

Have you considered SUP?

If polling is an option, why bother with a feed? Why not just have the clients poll the resource itself?

Could you cut down on unnecessary polling by including an estimated time for completion of the analyses?

Jim
Hi Jim,Thanks for the pointer to SUP - I'll do some reading. The reason for considering a feed rather than just straight polling is that would make it easier for the client to determine the recent changes and present those differently in the UI.
Ian Dickinson
Obviously you'd know better than I would, but I wouldn't have thought identifying the updated fields would be difficult for a client to do. Surely they can compare the received data to what they already have?
Jim
Yes, the client can do the comparison itself (the results are RDF documents not fields, but the point is still valid). I'm not committed one way or the other yet, but just wanted to mention one reason why a feed might have an advantage. Ad hoc mashups might be another, though I'm not yet convinced!
Ian Dickinson
+2  A: 

Use HTTP 202 Accepted.

Also, check out RESTful Web Services - it's where I learned about the above.

Hank Gay
On the same link, 206 Partial Content seems applicable.
JeroenWyseur
@JeroenWyseur: No, "The request MUST have included a Range header field".
+2  A: 
  • just ignoring idempotency and having GET return whatever is available at the time (but it still leaves the problem of the client knowing when we're finally done).

Does a GET that returns different results over time really mean that it is not idempotent? The spec says:

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request

That is, multiple calls to GET may return different results, so long as the calls themselves are side-effect free.

In which case, perhaps your REST method could use the conditional GET and caching mechanisms to indicate when it is done:

  • While the analysis is in progress, a GET {docURI}/metadata response could have:
    • an Expires header set to a few seconds in the future.
    • no ETag header.
  • Once the analysis is done, responses for that resource have:
    • no Expires header.
    • an ETag. Subsequent requests with the ETag should return 304 Not Modified.

NB you may want to consider the other response headers involved in caching, not just Expires.

This "feels" like a RESTful design - you can imagine a web browser doing the right thing as it made consecutive requests to this resource.

Matt Quail
Matt (and Jim too) - Yes, you're right about the idempotency, thanks for the clarification. I'll edit my question to remove that potential source of confusion!
Ian Dickinson
+1  A: 

"just having GET return whatever is available at the time" makes a ton of sense. Except, when they're polling, you don't want to keep returning stuff they already know. The answers get longer each time they poll.

You need them to provide you with their "what I've seen so far" in the GET request. This gives you idempotency. If they ask for chunk 1, they always get the same answer. Once they've seen chunk 1, they can ask for chunk 2.

The answer doesn't get bigger. More pieces become available. A "collection-level" GET provides the size of the response. You have "detail-level" GETs for each piece that's available.

Essentially, this is an algorithm like the TCP/IP acknowledgment. When they ack a piece, you send the next piece. If there is a next piece, otherwise you send a 200-nothing new to report.

The "problem of the client knowing when we're finally done" is imponderable. They can't know and you can't predict how long it will take.

You don't want them doing "busy waiting" -- polling to see if you're done yet -- that's a pretty big load on your server. If they're impatient. You can throttle their requests. You can send them a "check back in x seconds" where x gets progressively bigger.

You can even use a Unix-style scheduler algorithm where their score goes down when they poll and up if they don't poll for X seconds.

The alternative is some kind of queue where you post the results back to them. To do this, they'd have to provide a URI that you could POST to tell them you're done.

Or, they use Atom for a lightweight polling architecture. While Atom seems complex -- and it still involves polling -- you provide a minimal Atom answer ("not changed yet") until you're done, when you provide ("new results") so they can do the real heavyweight get. This is for all-or-nothing, instead of the incremental response technique above.

You can, also, think of the "collection-level" GET as your Atom status on the process as a whole.

S.Lott
Regarding "you don't want to keep returning stuff they already know" - HTTP already takes care of this with 304 responses. Regarding "what I've seen so far" - HTTP already takes care of this with If-None-Match. You're reinventing the wheel.
Jim
Not reinventing. Those are fine ways to encode the results. However, you still have to actually compute the results, determine what the client needs to know so you can decide between a 200 or a 304.
S.Lott
+1  A: 

One alternative solution that may or may not be suitable in your case is to add a new endpoint called "AnnotationRequests". Post the document (or a link to it) to the AnnotationRequests endpoint and it should return a Location (e.g. http://example.org/AnnotationRequest/2042) that will allow your client to poll the status of the process. When the process is complete, the "AnnotationRequest" representation can contain a link to the completed document.

One nice side effect of this is you can do a GET on AnnotationRequests so see documents that are currently being processed. It is up to you to decide how long you want to keep the AnnotationRequests around. It may be valuable to keep a complete history of when they were requested, by who and how long each took, or could throw them away periodically.

Darrel Miller
Nice suggestion, thanks Darrel
Ian Dickinson
+1  A: 

You may want to check out Udi Dahan's nServiceBus.

David Robbins
Looks interesting. Anyone know of a Java equivalent?
Ian Dickinson