I'm using the endpoint to perform an action, not transfer of state.
Actually, you are transferring state. The client is sending the proposed new user entity to the server. The server has its own rules to enforce, however, so it must refuse to create a user if their unique identifier already exists.
There is already an HTTP error code for that. As @tom pointed out, the 409 Conflict
response code is acceptable for that. It basically means What you asked me to do is perfectly legal in from your standpoint. I am simply unable to fulfill your request because it is in conflict with something outside of your control
versus some of the other response codes which focus on inappropriate client requests.
As far as your concern about using REST to perform an action in this case, I don't agree, but I am glad you are thinking about that. I have seen developers create "REST services" named things like GET /whatever/updateWidget
, which is clearly not a RESTful way of thinking.
You are performing an action only in the sense that you are creating a new resource. But that resource is based on the state you transferred. You have to create it somehow.
As long as you return a meaningful error code like 409
, I do not see any problems with your approach whatsoever.
In this particular example, I think most people would choose option 1. And indeed it feels more natural to me too. Buy why?
Not sure there's going to be a definitive answer to this, but my guess: because password, the entity, has a relatively straight forward representation as a document; HTTP's raison d'etre is document transfer, after all. It's pretty well designed for that.
Additionally, there's a simplicity to password - current state is a simple value type, and consequently there's no confusion about what "modify" should mean. Having only a single method available to mutate the state of the resource doesn't cause any problems because there's only one way you would want to: by completely replacing one representation with another, which is exactly the semantic of the PUT method in HTTP.
Generally speaking, a password change is not idempotent.
Yes, but that's a moot point here; the choice of method in this case is more driven by what should happen if a request is not acknowledged. Do we get to retry? If the message travels through an intermediate, is it allowed to retry the message rather than immediately returning a failure to the client?
(Admittedly, if we are throwing secrets around, we should be using an encrypted channel, which means that the intermediates can't see what's going on)
We aren't sending the request twice because we want the change applied twice, we are sending it twice to ensure that it is delivered at least once.
To ensure that the side effect on the domain doesn't get repeated, use a "conditional put", so that the server can distinguish between two copies of the same request, and two different requests that happen to contain the same representation of the password.
What if we provide a history of password changes as a new end point: /user/{id}/password_changes?
That seems like a reasonable service.
Yup. I would probably lean toward the spelling /user/{id}/password/history
; it feels natural to make the audit log subordinate to the password resource.
And then do we just disallow other http actions to password_changes (PUT, POST, DELETE)? That seems inconsistent... or maybe not?
I don't see a consistency issue. Your domain model is the book of record for password changes; that history is not subject to override by the clients -- the domain reserves that privilege for itself. So the resource is read only. Ta-da?
all that said
We're creating a "password_change". Thus we should be POST to /user/{id}/password_change, or something similar. In this model, the change to the "user" resource is just a side effect of creating a "password_change".
This isn't wrong either; if you twist it around a little bit, you are basically describing AtomPub; you are publishing an entry (the PasswordChanged event) into a feed (the history), and the new "state" of the resource is derived from looking at the history. In other words, RESTful event-sourcing.
It's a bit crooked; the usual idiom is that commands are sent to an aggregate
in the domain model, and the state changes that are caused by the command are published to a stream, which is exposed via an atom feed. In this use case, we turn it around, somewhat, by the fact that the human being, rather than the domain, is the book of record. Maybe.
Jim Webber describes REST application protocols as an analog to a 1950s style office, where you get things done by passing documents into inboxes. You deliver a ChangePasswordRequest to the server, and as a side effect the domain model updates itself, which has the cascading side effect of changing the state of other resources, like the audit log.
That's perhaps overkill for a simple ReplacePassword protocol, but is useful to keep in mind when things get to be more complicated.
Best Answer
POST is the "append" verb, and also the "processing" verb. PUT is the "create/update" verb (for known identifiers), and almost looks like the right choice here, because the full target URI is known.
projectId
andsiteId
already exist, so you don't need to "POST to a collection" to produce a new ID.The problem with PUT is that it requires the body be the representation of the resource you're PUTting. But the intent here is to append to the "project/sites" collection resource, rather than updating the Site resource.
What if someone PUTs a full JSON representation of an existing Site? Should you update the collection and update the object? You could support that, but it sounds like that's not the intent. As you said,
Rather, I'd try POSTing the
siteId
to the collection, and rely on the "append" and "process" nature of POST:Since you're modifying the sites collection resource and not the Site resource, that's the URI you want. POST can know to "append/process" and add the element with that id to the project's sites collection.
That still leaves the door open to creating brand new sites for the project by fleshing out the JSON and omitting the id. "No id" == "create from scratch". But if the collection URI gets an id and nothing else, it's pretty clear what needs to happen.
Interesting question. :)