Part of our RESTful API will allow users to register an item with a serial number. As the serial number is not globally unique it cannot be used as the identifier of the resource, so we'll use a POST to the parent resource which will generate an identifier, e.g.
POST /my/items
<item serial-number="ABCDEF" />
In the case where the item is not already registered, the HTTP semantics are well defined. We return a Location header, and the registered item as the entity body, e.g.
HTTP 201 Created
Location: /my/items/1234
<item id="1234" serial-number="ABCDEF" />
However, in the case where the item is already registered, the API should be idempotent and return the previously registered item without creating a new one. My best guess is that it should then return a 200 OK status code, and use the Content-Location header to indicate where the item actually came from, e.g.
HTTP 200 OK
Content-Location: /my/items/1234
<item id="1234" serial-number="ABCDEF" />
Would this seem reasonable? I'm not entirely clear whether the Location or Content-Location is more suitable in the second case.
Yet another approach is to use a separate client generated key to provide idempotency. In this way the client generates a key and adds it to the request using a custom header (e.g. Idempotency-Key). Now the server can persist the idempotency key and reject any further requests using the same key.
The POST method is not idempotent. To be idempotent, only the state of the server is considered. The response returned by each request may differ: for example, the first call of a DELETE will likely return a 200 , while successive ones will likely return a 404 .
Idempotency means that multiple identical requests will have the same outcome. So it does not matter if a request is sent once or multiple times. The following HTTP methods are idempotent: GET, HEAD, OPTIONS, TRACE, PUT and DELETE.
Idempotency is achieved simply by allowing the client to supply the unique message identifier instead of assigning it within the Ably service on receipt of the message.
I had similar requirement recently. For idempotent operations PUT is the best way. You're right there is a mismatch between the external-id and the internal one. I solved it by creating a dedicated resource for the external id target:
PUT /api-user/{username}/items/{serialNumber}
Internally I resolve it as a CREATE in case I have no item for 'username' and 'ABCDEF' serial number or UPDATE in case I do.
In case it was a CREATE I return 201 for an UPDATE 200. Further more the returned payload contains both homegrown id and the external serial number like you suggested in your payload.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With