As LastUser
and LastUpdate
are not modifiable by the client, I'd remove them from the representation of your resource altogether. Let me explain my reasoning with an example.
Let's say that our typical example API will return the following representation to the client when asked to provide a single resource:
GET /example/123
<?xml version="1.0" encoding="UTF-8" ?>
<example>
<id>123</id>
<lorem>ipsum</lorem>
<dolor>sit amet</dolor>
<lastUser uri="/user/321">321</lastUser>
<lastUpdate>2011-04-16 20:00:00 GMT</lastUpdate>
</example>
If a client wants to modify the resource, it would presumably take the whole representation and send it back to the API.
PUT /example/123
<?xml version="1.0" encoding="UTF-8" ?>
<example>
<id>123</id>
<lorem>foobar</lorem>
<dolor>foobaz</dolor>
<lastUser>322</lastUser>
<lastUpdate>2011-04-16 20:46:15 GMT+2</lastUpdate>
</example>
Since the API generates values for lastUser
and lastUpdate
automatically and cannot accept data provided by the client, the most appropriate response would be 400 Bad Request
or 403 Forbidden
(since the client cannot modify these values).
If we want to be compliant with REST and send a full representation of the resource when doing a PUT request, we need to remove lastUser
and lastUpdate
from the representation of the resource. This will allow clients to send the full entity via PUT:
PUT /example/123
<?xml version="1.0" encoding="UTF-8" ?>
<example>
<id>123</id>
<lorem>foobar</lorem>
<dolor>foobaz</dolor>
</example>
The server would accept a full representation now that it doesn't contain lastUpdate
and lastUser
.
The question that remains is how to provide clients with access to lastUpdate
and lastUser
. If they don't need it (and these fields are required just internally by the API), we are fine and our solution is perfectly RESTful. If however clients need access to this data, the cleanest approach would be to use HTTP headers:
GET /example/123
...
Last-Modified: Sat, 16 Apr 2011 18:46:15 GMT
X-Last-User: /user/322
...
<?xml version="1.0" encoding="UTF-8" ?>
<example>
<id>123</id>
<lorem>foobar</lorem>
<dolor>foobaz</dolor>
</example>
Using a custom HTTP header is not ideal because user agents need to be taught on how to read it. If we want to provide clients with access to the same data in a more easier way, the only thing that we can do is to put the data into the representation, and we are facing the same problem as in your original question. I would at least try to mitigate it somehow. If the content type used by the API is XML, we can put the data into node attributes instead of exposing them directly as node values, i.e.:
GET /example/123
...
Last-Modified: Sat, 16 Apr 2011 18:46:15 GMT
...
<?xml version="1.0" encoding="UTF-8" ?>
<example last-update="2011-04-16 18:46:15 GMT" last-user="/user/322">
<id>123</id>
<lorem>foobar</lorem>
<dolor>foobaz</dolor>
</example>
This way we'll at least avoid the problem where a client would attempt to submit all XML nodes in a follow-up PUT request. This won't work with JSON, and the solution is still a bit on the edge of idempotency (since the API would still have to ignore the XML attributes when processing the request).
Even better, as Jonah pointed out in the comments, if clients need access to lastUser
and lastUpdate
, these can be exposed as a new resource, linked from the original one e.g. as follows:
GET /example/123
<?xml version="1.0" encoding="UTF-8" ?>
<example>
<id>123</id>
<lorem>foobar</lorem>
<dolor>foobaz</dolor>
<lastUpdateUri>/example/123/last-update</lastUpdateUri>
</example>
... and then:
GET /example/123/last-update
<?xml version="1.0" encoding="UTF-8" ?>
<lastUpdate>
<resourceUri>/example/123</resourceUri>
<updatedBy uri="/user/321">321</updatedBy>
<updatedAt>2011-04-16 20:00:00 GMT</updatedAt>
</lastUpdate>
(The above can be also nicely expanded to provide a full audit log with individual changes, providing a resource changelog is available.)
Please note:
I agree with Darrel Miller's take on the question, but I wanted to provide a different approach on top of it. Note that this approach is not backed-up by any standards/RFCs/etc, it's just a different take on the problem.