zalando / restful-api-guidelines

A model set of guidelines for RESTful APIs and Events, created by Zalando
https://opensource.zalando.com/restful-api-guidelines/
Creative Commons Attribution 4.0 International
2.65k stars 390 forks source link

Better guidance for PUT vs. POST for creation #391

Closed ePaul closed 6 years ago

ePaul commented 6 years ago

We currently don't give a clear guidance on the usage of HTTP methods for resource creation. Currently our APIs have a wild mixture of both.

A naive POST has a problem in case of a connection issue that the client doesn't know whether it was successful, and retry might give a duplicated resource. For a PUT you generally need to know the resource's ID before creation.

How can we make this clearer?

Some ideas:

tkrop commented 6 years ago

I do not see a general problem with POST and would not favor PUT as proposed by @tfrauenstein.

There is a simple foreign key pattern to achieve idempotent resource creation in POST, that is a naturally available candidate for most use cases. Often a parent resource or process is sponsoring the creation of a new resource, that will provide identifying properties for the foreign key.

In addition, I think:

  1. The logical distinction between create=POST and update=PUT creates an easier semantic of response codes and headers than the create-or-update=PUT which requires a more complex conditional request definition.
  2. It is preferable to not implement update=PUT at all as long as the service can handle all changes of a resources based on simple immutable process resource request, e.g. order-cancellations.
  3. Similar to business logic the responsibility of identifiers should reside (i.e. be managed) on server-side to allow for maximum freedom within API evolution. In the past there was a similar rule for SOAP services and SPROCs to implement identifiers client agnostic as strings - content was only controlled by DB servers.
  4. The foreign key pattern is preferable over any consumer based identifier pattern, because it is more general and easier to apply without exception.

In the end, I would favor PUT over POST only in cases, where

  1. a natural consumer based key exist, e.g. for a related sub-resource,
  2. an unconditional insert-on-update is possible, i.e. making the distinction between create and update unnecessary, and
  3. a high throughput distributed resource creation is necessary.
whiskeysierra commented 6 years ago

I do not see a general problem with POST and would not favor PUT

I agree. Those that favor PUT over POST generally base their opinion on the fact that PUT is required to be idempotent, but POST is not. But not guaranteed to be idempotent is not the same as guaranteed not to be idempotent. Nothing stops us from designing and implementing POST operations as idempotent.

The logical distinction between create=POST and update=PUT creates an easier semantic of response codes and headers

Why easier? Wouldn't it be 201 and 200 anyway whether it's POST+PUT or just PUT?

than the create-or-update=PUT which requires a more complex conditional request definition.

That is a statement against conditional requests, not necessarily against PUT. We could still use the foreign key pattern with PUT, couldn't we?

It is preferable to not implement update=PUT at all as long as the service can handle all changes of a resources based on simple immutable process resource request, e.g. order-cancellations.

Can you sketch how that would look and feel compared to traditional PUT?

Similar to business logic the responsibility of identifiers should reside (i.e. be managed) on server-side to allow for maximum freedom within API evolution.

I agree, but again that's an argument against client-generated identifiers not necessarily against PUT. I've seen a couple of APIs that have a special endpoint/resource to generate identifiers that can then be used with PUT. Potentially inefficient, because for the initial creation one needs to issue 2 requests, but that can be mitigated...

a high throughput distributed resource creation is necessary.

..., e.g. by giving blocks of identifiers to clients that they can keep and consume locally.

tkrop commented 6 years ago

than the create-or-update=PUT which requires a more complex conditional request definition. That is a statement against conditional requests, not necessarily against PUT. We could still use the foreign key pattern with PUT, couldn't we?

@whiskeysierra yes, this is a statement against overusing conditional requests while overloading PUT.

I like the idea of using the foreign key pattern for PUT, but I think that 409 - conflict matching the foreign key of a resource with a different id is a bit weird.

Can you sketch how that would look and feel compared to traditional PUT?

My example is to substitute PUT /order/<id> with POST /cancellations { "order_id": <id> }. As the result a business process is initiated that changes the state of the order resource as well as the state of the cancellation step by step until finished. The process can be monitored by GET /cancellations/<cid> or by GET /cancellations?order_id=<id>. The pattern can be applied recursive to each sub-process supported by the service.

Potentially inefficient, because for the initial creation one needs to issue 2 requests, ...

Yes, it can be mitigated, but still this management of two request loads off responsibility to the consumer it should not take care of - if possible - and makes each consumer more difficult, and it turns the balance for me to favor POST.

To make it clear I do not see a one-size-fits-all guideline here, but we could collect some best practices as in PATCH.

dehora commented 6 years ago

The guidelines should recommend PUT only when the update action will be (not required to be, but will be) idempotent and the submitted entity is a complete representation of the resource's proposed next state. This is an edge case for most people's needs from APIs.

PATCH on the other hand is not the answer to partial state updates. It should not be recommended because it requires representation media types dedicated to that purpose that have clear and complete semantics for the needs of designers. The JSON-* media types don't have this as long as they don't support arrays. The PATCH RFC that dredged up the PATCH method (it was dropped in RFC2616) solves a narrower and less relevant set of problems. And as a practical engineering and accessibility matter, PATCH's coupling to a media type representation puts too much burden on clients - this is why you don't see it in de-facto use for "real world" APIs built by businesses that live or die by the adoption of their APIs. Much the same can be said for PUT which requires clients to handle full representations and not accidentally leave data behind. The implication is that PATCH does not support the top level principle of API as a Product, and PUT isn't much better.

For state transition (REST focused) or mutations (CRUD focused) the guidelines should recommend POST as optimal for the style of APIs the guidelines design for and the problems its users have.

I would much rather see a set of guidelines that focused on uniform create and update semantics via the representations akin to GraphQL's or Google's API guideline's approach (ie better data models than the PATCH RFC's JSON media types) and/or define practical options like idempotency keys or post once exactly header controls, versus trying to force PUT and PATCH effort onto client developers. There's nothing wrong with POST.

dehora commented 6 years ago

But not guaranteed to be idempotent is not the same as guaranteed not to be idempotent. Nothing stops us from designing and implementing POST operations as idempotent.

Strong agreement with this important distinction.

ePaul commented 6 years ago

Please let stick to the question of create use cases, updates are a separate issue.

meshcalero commented 6 years ago

My thought are:

whiskeysierra commented 6 years ago

establishing POST / PUT as Create / Update is a good practice; I believe we should keep it

I'd follow @dehora here. PUT is only useful in relatively rare cases, because of its replace semantic. As a service provider I'm encouraging my clients to only use those properties of my resources that they actually care about. That keeps their code simple and they don't need to understand properties that are of no interest to them. That approach works OK with POST and PATCH, but it won't work with PUT. I consider the subset of resources and properties that a client uses as their contract. PUT therefore artificially forces clients to blow up their contract.

ID-generation should be a service responsibility

Agreed.

(at least for top-level resources)

Why that distinction?

Where idempotent create is required, allowing clients to pass along a transaction id (see scribe), that allows a service to identify retries is a reasonable solution

Agreed. Maybe we should differentiate between what @tkrop refers to as the foreign-key pattern (I prefer the term remote id for this, but that doesn't really matter) and what Stripe refers to as idempotency keys. Foreign keys are a good natural fit when a client wants to connect 1 resource with another. It's a one-time usable thing for the whole lifecycle of the resource. Idempotency keys are a pure technical thing and can be used for any kinds of operation.

although I don't like their "same response"

Totally agree. Idempotent != same response. I believe we make that statement somewhere already.

As POST requests are never implicitly re-send, we could even ask clients to mark initial a initial create with an additional header. That way services would only check if a specific transaction has already been used in case of a retry.

Isn't that exactly what Idempotency-Key is? Or did you refer to that?

bofcarbon1 commented 5 years ago

Having been building and consuming web service APIs I've found some challenges as the script languages evolve to make calls with the PUT option. I recall some frustration in Express services with @angular/http with an Observable. I ended up using a POST for a create and update data request.

With the improvements in URL pattern annotation in .NET Web API, Express and even Java Spring Boot request mapping I found I could shift the focus of identifying what the method or function is specifically doing to the URL. It simplifies things for me on both ends. I don't have to worry about separation of the ID from the content body that PUT requires in the http call. I know I can retrieve the ID from the content body and apply a rule like is null or zero to determine if the request makes more sense for an add or update. That's what I do when I use a repository tool like Entity Framework for the data resource processing. The model context tools usually require an ID designation to resolve key constraints.

On the client side I find that when I put the URL address in my http request call and it includes 'add' or 'update' in the string I know I'll be making the right call. I don't worry about POST versus PUT and protocol like I used to. Purest may call me lazy for that but I feel that less complexity is more value. I also prefer explicit self documenting http API calls.

Seems to be working nicely for me. Right now I'm working with Java Spring Boot Strap MongoRepository and Request Mapping with a React Native UI. The Fetch commands are new to me. The name fetch sounds like it would do a GET request by default. There is a POST option and I have not found a PUT option for it. That kind of tells me they would just suggest using the POST for add or update requests in Fetch. Granted I am new to React and my research has led me to blogs and tutorials that have suggested an old friend Ajax or one I have not checked out Axios.

tkrop commented 5 years ago

@bofcarbon1 Thanks for your contribution.

If I understand you correctly, you are using POST to create and update resources by adding verbs as like add and update to your the resource paths, and still read resources using GET. Correct?

This is a totally acceptable RPC pattern (REST level 0) attached to the idea of resource (REST level 1) as described in Richardson Maturity Model.

Anyhow, We want developers in our company to design their APIs aiming for REST level 2, because we think it provides a clearer semantic that is able to evolve with new requirements.

bofcarbon1 commented 5 years ago

Yes pretty much. Glad that I'm reading the right books or blogs to keep me close to that REST level compliance. Its funny how you can jump into API building with a technology you don't touch all the time and forget. Tutorials always start with an example of a GET, PUT, POST and DELETE. Java Spring Boot does have the PUT option. So I built a method and tested it with JUnit all fine. Then I went to my React UI and started making my API calls. I had learned how to use this 'Fetch' option to make the call in JavaScript. Looked a lot like an Ajax script API call. Fetch did have a POST option. Blogs out on the internet only showing POST examples for data repository data source changes. I just got a clean test on the POST call. Because I had a working option on the UI that satisfied me I looked back at my Java API and said. 'yeah do I really want to keep this PUT pattern for my update or just use POST instead. My choice was much about practical options. Because I did not have a separate PUT and POST I could not leave the URL string part a simple '/' like it was for both. Then I remembered that for my .NET and Express web service APIs I used more specific URL pattern matching. For .NET Web APIs I think I am still taking the PUT protocol with the key index and the content body sent as arguments in the call. Good stuff.