0

I'm working with a RESTful API where a POST operation is used to create a user. A required field, which could be an email or a unique user nickname, is unique across the system. The response to this POST contains a unique id for the user, which is a deterministic hash calculated from this unique field. Therefore, the same unique field will always produce the same id, preserving their one-to-one relationship.

Given this structure, there's an inherent challenge regarding the use of the PUT operation. Traditionally, PUT can replace a resource or create a new one if it doesn't exist. However, in this context, the deterministic and immutable relationship between the id and the unique field seems to complicate the semantics of PUT.

My concerns are:

  1. Given the deterministic nature of the id from immutable properties, is it a good design choice to implement a PUT method for such resources?
  2. If the semantics of PUT are challenged by this design, are there any best practices or potential pitfalls that I should be aware of?
3
  • 1
    Can you explain what you see as the "inherent challenge" here? The deterministic nature of the id would seem to help you use PUT.
    – JimmyJames
    Commented Aug 17, 2023 at 18:11
  • @JimmyJames id is calculated from (possibly immutable) fields by an internal algorithm, which the API user is not supposed to know of. I don't think he is also supposed to know which feilds are used. So I don't feel comfortable requesting the user to provide a field (set of fields) with "exactly the same value". Such reuiqrement would be unfulfillable in such cases when the caller has a permission to replace an existing user, but is not allowed to know the personal details due to privacy reasons. Commented Aug 17, 2023 at 19:09
  • OK, I would actually steer you away from trying to calculate the hash directly on the client. I just wanted to make sure I understand the problem.
    – JimmyJames
    Commented Aug 17, 2023 at 19:11

3 Answers 3

2

If I understand correctly, you are planning to have a resource along the lines of:

https://yourdomain.com/user

Which supports a POST operation which will return a new resource maybe like:

https://yourdomain.com/user/somesortofhash

There's nothing particularly unusual or difficult allowing PUT to be used on resources created with POST. You can even restrict the ability for the PUT to create a new resource by failing the operation.

The only requirement of PUT is that it needs to be idempotent meaning that if you call PUT successfully multiple times with the same input, the resulting state of the resource will be the same. This is a useful feature because if you have some sort of e.g.: network failure, you can simply retry the PUT call.

What PUT requires in this case is that the called know the id of the user. It is probably a bad idea to have clients calculating the hash for the id themselves as discussed in the comments. Primarily because it would lock you into the hashing routine. In the future you might want to use a UUID instead or perhaps you need to modify the hashing routine because you have had collisions. It really is best to not expose that detail to the client and I take it that you don't want to do that anyway. This doesn't preclude the use of PUT, however.

I think your question may come down to how does the client get that id? First of all, if you return the URI for the user resource from the POST, that's one way. In some circumstances that might even be enough. It's typical though for a REST API to provide a way to look up such resource URIs based on information that they have.

One thing to consider with PUT is that it has complete replacement semantics. This is simple to use but you can run into issues such as dirty reads/writes if you have multiple clients attempting to modify the same resources at the same time. For this reason PATCH is sometimes used to allow clients to update part of a resource without replacing it entirely.

5
  • I appreciate your answer. However, I still haven't received a clarification on a situation where the id (lets say UUID) is calculated by the server from (for example) an email. So, in order to allow PUT /users/someexistingid I would require the API user to also provide an email in the body and is exaclty mapped 1-to-1 with someexistingid. The problem is that id is deterministic and based on email. And there's only one situation when the user will not have a problem: when he passes id and email like he has received them from the server. Without email operation is impossible. Commented Aug 17, 2023 at 19:54
  • "I would require the API user to also provide an email in the body and is exaclty mapped 1-to-1 with someexistingid" The id would be in the PUT URI. When the POST creates a new resource, it determines the id based on whatever server-side logic you need and creates a child resource. Does that help?
    – JimmyJames
    Commented Aug 17, 2023 at 21:44
  • so you have id int PUT URI. And have email in the body, which does not correspond to an id that would be generated by the server. What then? Error email is invalid? Or return a new id that corresponds to the new email (makes no sense for PUT)? Commented Aug 17, 2023 at 22:35
  • 1
    @SergeyKolesnik, if the 1-to-1 logic of email to id needs to remain true for the lifetime of the resource, then a PUT request with a change to the email should get an error response, because it would put the resource in an internally inconsistent state. What the clients need to know is which attributes/fields should be considered to be read-only. Commented Aug 18, 2023 at 9:43
  • @SergeyKolesnik I see. You could throw an error as Bart notes. You might also consider dispensing with ids derived from data fields and use something like a UUID instead.
    – JimmyJames
    Commented Aug 18, 2023 at 14:35
1

Three variations of PUT:

  1. You accept the representation of the resource provided in the request as is, and keep the URI unchanged. This is the primary interaction that PUT was designed for.

  2. You use the information in the request to recalculate the representation of the resource yourself, and keep the URI unchanged. Not quite so easy - we need to be a little bit careful with things like Validation headers, see RFC 9110.

If you need to try to change the URI... that's where the dragons lurk in the hungriest dark.

In the case where you expect clients to be bespoke implementations designed to interface specifically with your API, you probably don't have much of a problem - you tell the client implementers how to identify the new URI to be used, and get on with your life.

But if people are connecting to your API using general purpose components (like a web browser), then communicating the URI change to the client gets a little bit spooky. The closest real thing you have are redirects, but automatic redirection may cross you up (especially since for some redirect messages clients have a bit too much freedom about which message they use).


Being concerned here is good, and you should investigate carefully to make sure that you understand the tradeoffs involved, and whether "clever" is really the right answer (in both short and long term).

1

I would just return a well-described 400 Bad request or 409 Conflict in case the unique field is changed. And/or only accept PATCHes that don't include that field.

1
  • Or just "never make id dependable in the first place" Commented Aug 18, 2023 at 10:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.