Well, in a REST interface, following HTTP where ever possible, I would return a 201 and an URI in the Location header field to the newly created Resource. Here is what Status Code Definitions says:
10.2.2 201 Created
The request has been fulfilled and resulted in a new resource being
created. The newly created resource can be referenced by the URI(s)
returned in the entity of the response, with the most specific URI for
the resource given by a Location header field. The response SHOULD
include an entity containing a list of resource characteristics and
location(s) from which the user or user agent can choose the one most
appropriate. The entity format is specified by the media type given in
the Content-Type header field. The origin server MUST create the
resource before returning the 201 status code. If the action cannot be
carried out immediately, the server SHOULD respond with 202 (Accepted)
response instead.
If something went wrong, I would argue you shouldn't return -1
as others have said, but simply a Client or Server Error Code (4xx or 5xx). For example, if a user is not allowed to create some new resource, you would simply return a "401 Unauthorized", nothing more and nothing less.
If you really think about it, if a record has not changed, it is in a sense "cached" already, because that updated_at
timestamp has not changed; thus, your intuition about only fetching records that have been modified is the best way to go about this "caching". However, I wouldn't really call it "caching", but rather "selective retrieval".
However, as @Joeri Sebrechts mentioned in his comment, using HTTP heads in a non-standard fashion is a really good way to annoy maintainers of your code as they struggle to figure out why you're using If-Modified-Since
like a query parameter to filter records. In fact, that's exactly why he suggested using a query parameter - they're used exactly for this purpose - and I fully agree.
So the solution here is to:
- Initially, fetch all records that you need (e.g. at startup)
- store this value on the client as a timestamp -
changed-after
(or whatever you want to name it - when doing the GET
- make sure that the record
id
is included so you can do some fancy merging with existing records later
- When the client needs to retrieve new records or update the list, simply send another GET to e.g.
/records?changed-after=THE_STORED_TIMESTAMP
- your API will only retrieve records with
updated_at > changed-after
- send those records back to the client
- On the client, do a merge operation on your existing list of records
- do not delete records from the list
- simply take the set of new records, find them in the old list, and replace them
- leave the rest of the list unmodified
Some other applications use websockets to communicate changes to clients; e.g. the server detects a change in a record and pings all clients that an update is available for retrieval. That would be, in my opinion, the more "efficient" way to do things in the event that you have millions upon millions of records that might take a long time to query, and you have the bandwidth available for websockets. Instead of having clients constantly querying for updates that may or may not be available (and the possibility of those queries being expensive), you simply have the server tell clients when they need to update.
However, we don't know anything about the quantity and complexity of your data, but the simple fact that you have a high-latency and low-bandwitdth situation kind of eliminates the possibility of using websockets, so the query parameter update_at
filter seems to be the most appropriate approach.
PS - If you're really really really tight on data, you could even implement a changelog of your records to know which fields changed, allowing you to selectively send only the fields that were actually updated, instead of the entire record. Some frameworks/languages have libraries that do this, e.g. Rails' Paper Trail. If you think that the need for very low bandwidth usage is worth adding such a dependency, I would highly recommend it. Sometimes these libraries make it ridiculously easy, like Paper Trails' methods to diff versions which gives you only the data that was changed. So you could send only that dat, along with the record's id
, and selectively merge on the client on a field basis instead of a whole record basis. Neat!
Best Answer
One thing we've done where I work is to have a "storage" service API. Basically, you POST a JSON object to the service, and it returns a UUID. You send the UUID as a query parameter on any subsequent API call and it will get the parameters/data from the storage service. It's especially handy if you will be making multiple calls with the same data, as you only have to send it once.