REST API-실제 사례가 포함 된 PUT 대 PATCH
우선, 몇 가지 정의 :
PUT는 섹션 9.6 RFC 2616에 정의되어 있습니다 .
PUT 메소드는 동봉 된 엔티티가 제공된 Request-URI 아래에 저장되도록 요청합니다. Request-URI가 이미 존재하는 자원을 참조하는 경우, 동봉 된 엔티티 는 원 서버에있는 자원 의 수정 된 버전으로 간주되어야합니다 . Request-URI가 기존 리소스를 가리 키지 않고 해당 URI가 요청하는 사용자 에이전트에 의해 새 리소스로 정의 될 수있는 경우 원본 서버는 해당 URI로 리소스를 생성 할 수 있습니다.
PATCH는 RFC 5789에 정의되어 있습니다 .
PATCH 메소드 는 요청 엔티티에 설명 된 일련의 변경 사항 이 Request-URI로 식별되는 리소스에 적용되도록 요청합니다.
또한 RFC 2616 섹션 9.1.2 에 따르면 PUT는 Idempotent이지만 PATCH는 그렇지 않습니다.
이제 실제 예를 살펴 보겠습니다. /users
데이터로 POST를 수행 {username: 'skwee357', email: 'skwee357@domain.com'}
하고 서버가 리소스를 생성 할 수있을 때 201 및 리소스 위치 (가정하자 /users/1
)로 응답하고 GET에 대한 다음 호출 /users/1
은 {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}
.
이제 이메일을 수정하고 싶다고 가정 해 보겠습니다. 이메일 수정은 "일련의 변경"으로 간주되므로 /users/1
" 패치 문서 "로 PATCH해야합니다 . 제 경우에는 json이 될 것 {email: 'skwee357@newdomain.com'}
입니다. 그런 다음 서버는 200을 반환합니다 (권한이 정상이라고 가정). 이것은 저에게 첫 번째 질문을 제공합니다
- PATCH는 멱 등성이 아닙니다. RFC 2616 및 RFC 5789에서 그렇게 말했습니다. 그러나 동일한 PATCH 요청 (새 이메일로)을 발행하면 동일한 리소스 상태를 얻게됩니다 (내 이메일이 요청 된 값으로 수정 됨). PATCH가 멱 등성이 아닌 이유는 무엇입니까?
PATCH는 비교적 새로운 동사 (2010 년 3 월에 도입 된 RFC)이며 "패치"또는 필드 집합 수정 문제를 해결합니다. PATCH가 도입되기 전에는 모두 PUT을 사용하여 리소스를 업데이트했습니다. 그러나 PATCH가 도입 된 후 PUT가 무엇에 사용되는지 혼란스러워합니까? 그리고 이것은 두 번째 (그리고 주요) 질문으로 이어집니다.
- PUT과 PATCH의 실제 차이점은 무엇입니까? 특정 리소스에서 전체 엔티티 를 대체 하는 데 PUT가 사용될 수있는 곳에서 읽었 으므로 PATCH와 같은 속성 세트 대신 전체 엔티티를 보내야합니다. 그러한 경우의 실제적인 사용법은 무엇입니까? 특정 리소스 URI에서 엔티티를 언제 교체 / 덮어 쓰고 싶습니까? 이러한 작업이 엔티티를 업데이트 / 패칭하는 것으로 간주되지 않는 이유는 무엇입니까? PUT에 대해 내가 보는 유일한 실제 사용 사례는 컬렉션에 대해 PUT를 발행하는 것입니다. 즉
/users
, 전체 컬렉션을 교체하는 것입니다. PATCH가 도입 된 후 특정 엔티티에 대해 PUT를 발행하는 것은 의미가 없습니다. 내가 잘못?
참고 : REST에 대해 처음 읽었을 때 멱등 성은 제대로 이해하기 위해 혼란스러운 개념이었습니다. 추가 의견 (및 Jason Hoetger의 답변 )이 보여 주듯이 원래 답변에서 여전히 옳지 않았습니다 . 한동안 Jason을 효과적으로 표절하는 것을 피하기 위해이 답변을 광범위하게 업데이트하는 것을 거부했지만 지금은 (댓글에서) 요청을 받았기 때문에 편집하고 있습니다.
내 답변을 읽은 후 Jason Hoetger 의이 질문에 대한 훌륭한 답변 도 읽어 보시고 단순히 Jason에게서 훔치지 않고 더 나은 답변을 만들려고 노력할 것입니다.
PUT가 멱등 성인 이유는 무엇입니까?
RFC 2616 인용에서 언급했듯이 PUT는 멱 등성으로 간주됩니다. 리소스를 PUT하면 다음 두 가지 가정이 적용됩니다.
컬렉션이 아니라 엔티티를 참조하는 것입니다.
제공하는 법인이 완전합니다 ( 전체 법인).
귀하의 예 중 하나를 살펴 보겠습니다.
{ "username": "skwee357", "email": "skwee357@domain.com" }
/users
제안한대로이 문서를에 게시하면 다음과 같은 엔티티를 다시 가져올 수 있습니다.
## /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
나중에이 엔티티를 수정하려면 PUT 및 PATCH 중에서 선택합니다. PUT는 다음과 같습니다.
PUT /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // new email address
}
PATCH를 사용하여 동일한 작업을 수행 할 수 있습니다. 다음과 같이 보일 수 있습니다.
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
이 둘 사이의 차이점을 즉시 알 수 있습니다. PUT에는이 사용자의 모든 매개 변수가 포함되었지만 PATCH에는 수정중인 매개 변수 만 포함되었습니다 ( email
).
PUT를 사용하는 경우 완전한 엔터티를 보내는 것으로 가정하고 전체 엔터티 는 해당 URI의 기존 엔터티를 대체 합니다. 위의 예에서 PUT 및 PATCH는 동일한 목표를 달성합니다. 둘 다이 사용자의 이메일 주소를 변경합니다. 그러나 PUT은 전체 엔터티를 대체하여 처리하는 반면 PATCH는 제공된 필드 만 업데이트하고 나머지는 그대로 둡니다.
PUT 요청에는 전체 엔터티가 포함되므로 동일한 요청을 반복적으로 발행하면 항상 동일한 결과를 가져야합니다 (이제 보낸 데이터는 엔터티의 전체 데이터가 됨). 따라서 PUT는 멱 등성입니다.
잘못된 PUT 사용
PUT 요청에서 위의 PATCH 데이터를 사용하면 어떻게됩니까?
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PUT /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"email": "skwee357@gmail.com" // new email address... and nothing else!
}
(이 질문의 목적을 위해 서버에 특정 필수 필드가 없으며 이것이 발생하도록 허용한다고 가정하고 있습니다. 실제로는 그렇지 않을 수 있습니다.)
우리는 PUT를 사용했지만 공급 만했기 email
때문에이 엔티티에서는 이것이 유일한 것입니다. 이로 인해 데이터가 손실되었습니다.
이 예제는 설명을 위해 여기에 있습니다. 실제로이 작업을 수행하지 마십시오. 이 PUT 요청은 기술적으로 멱 등성이지만 이것이 끔찍하고 깨진 아이디어가 아니라는 의미는 아닙니다.
PATCH는 어떻게 멱 등성을 가질 수 있습니까?
위의 예에서 PATCH 는 멱 등성 이었습니다 . 변경했지만 동일한 변경을 반복하면 항상 동일한 결과를 반환합니다. 이메일 주소를 새 값으로 변경했습니다.
GET /users/1
{
"username": "skwee357",
"email": "skwee357@domain.com"
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // email address was changed
}
PATCH /users/1
{
"email": "skwee357@gmail.com" // new email address... again
}
GET /users/1
{
"username": "skwee357",
"email": "skwee357@gmail.com" // nothing changed since last GET
}
내 원래 예, 정확성을 위해 수정 됨
원래는 멱 등성이 아닌 것으로 생각되는 예가 있었지만 오해의 소지가 있거나 잘못되었습니다. 나는 예제를 유지하지만 다른 것을 설명하기 위해 그것들을 사용할 것입니다. 동일한 엔티티에 대한 여러 PATCH 문서, 다른 속성 수정은 PATCH를 비멱 등하게 만들지 않습니다.
과거에 사용자가 추가되었다고 가정 해 보겠습니다. 이것은 당신이 시작하는 상태입니다.
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
PATCH 후에 수정 된 엔티티가 있습니다.
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // the email changed, yay!
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
그런 다음 반복적으로 PATCH를 적용하면 동일한 결과를 계속 얻을 수 있습니다. 이메일이 새 값으로 변경되었습니다. A가 들어가고 A가 나오므로 이것은 멱 등성입니다.
한 시간 후, 커피를 마시고 휴식을 취하고 나면 다른 누군가가 자신의 패치를 가지고 온다. 우체국에서 약간의 변화가있는 것 같습니다.
PATCH /users/1
{"zip": "12345"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com", // still the new email you set
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345" // and this change as well
}
우체국의이 PATCH는 이메일과는 관련이 없기 때문에 우편 번호 만 반복적으로 적용하면 동일한 결과를 얻을 수 있습니다. 우편 번호가 새 값으로 설정됩니다. A가 들어가고 A가 나오므로 이것도 멱 등성입니다.
다음날 PATCH를 다시 보내기로 결정합니다.
PATCH /users/1
{"email": "skwee357@newdomain.com"}
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@newdomain.com",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "12345"
}
당신의 패치는 어제와 같은 효과를 가지고 있습니다 : 그것은 이메일 주소를 설정했습니다. A가 들어갔고 A가 나왔으므로 이것도 멱 등성입니다.
내 원래 답변에서 내가 잘못한 것
나는 중요한 구별을하고 싶다 (원래 대답에서 내가 틀린 부분). 많은 서버가 수정 사항 (있는 경우)과 함께 새 엔티티 상태를 다시 전송하여 REST 요청에 응답합니다. 따라서이 응답 을 받으면 지난번에받은 우편 번호가 아니기 때문에 어제받은 것과 다릅니다 . 그러나 귀하의 요청은 우편 번호와 관련이 없으며 이메일에만 관련되었습니다. 따라서 PATCH 문서는 여전히 멱 등성입니다. PATCH에서 보낸 이메일은 이제 엔티티의 이메일 주소입니다.
그렇다면 PATCH는 언제 멱 등성이 아닐까요?
For a full treatment of this question, I again refer you to Jason Hoetger's answer. I'm just going to leave it at that, because I honestly don't think I can answer this part better than he already has.
Though Dan Lowe's excellent answer very thoroughly answered the OP's question about the difference between PUT and PATCH, its answer to the question of why PATCH is not idempotent is not quite correct.
To show why PATCH isn't idempotent, it helps to start with the definition of idempotence (from Wikipedia):
The term idempotent is used more comprehensively to describe an operation that will produce the same results if executed once or multiple times [...] An idempotent function is one that has the property f(f(x)) = f(x) for any value x.
In more accessible language, an idempotent PATCH could be defined as: After PATCHing a resource with a patch document, all subsequent PATCH calls to the same resource with the same patch document will not change the resource.
Conversely, a non-idempotent operation is one where f(f(x)) != f(x), which for PATCH could be stated as: After PATCHing a resource with a patch document, subsequent PATCH calls to the same resource with the same patch document do change the resource.
To illustrate a non-idempotent PATCH, suppose there is a /users resource, and suppose that calling GET /users
returns a list of users, currently:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]
Rather than PATCHing /users/{id}, as in the OP's example, suppose the server allows PATCHing /users. Let's issue this PATCH request:
PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]
Our patch document instructs the server to add a new user called newuser
to the list of users. After calling this the first time, GET /users
would return:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
{ "id": 2, "username": "newuser", "email": "newuser@example.org" }]
Now, if we issue the exact same PATCH request as above, what happens? (For the sake of this example, let's assume that the /users resource allows duplicate usernames.) The "op" is "add", so a new user is added to the list, and a subsequent GET /users
returns:
[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
{ "id": 2, "username": "newuser", "email": "newuser@example.org" },
{ "id": 3, "username": "newuser", "email": "newuser@example.org" }]
The /users resource has changed again, even though we issued the exact same PATCH against the exact same endpoint. If our PATCH is f(x), f(f(x)) is not the same as f(x), and therefore, this particular PATCH is not idempotent.
Although PATCH isn't guaranteed to be idempotent, there's nothing in the PATCH specification to prevent you from making all PATCH operations on your particular server idempotent. RFC 5789 even anticipates advantages from idempotent PATCH requests:
A PATCH request can be issued in such a way as to be idempotent, which also helps prevent bad outcomes from collisions between two PATCH requests on the same resource in a similar time frame.
In Dan's example, his PATCH operation is, in fact, idempotent. In that example, the /users/1 entity changed between our PATCH requests, but not because of our PATCH requests; it was actually the Post Office's different patch document that caused the zip code to change. The Post Office's different PATCH is a different operation; if our PATCH is f(x), the Post Office's PATCH is g(x). Idempotence states that f(f(f(x))) = f(x)
, but makes no guarantes about f(g(f(x)))
.
I was curious about this as well and found a few interesting articles. I may not answer your question to its full extent, but this at least provides some more information.
http://restful-api-design.readthedocs.org/en/latest/methods.html
The HTTP RFC specifies that PUT must take a full new resource representation as the request entity. This means that if for example only certain attributes are provided, those should be remove (i.e. set to null).
Given that, then a PUT should send the entire object. For instance,
/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}
This would effectively update the email. The reason PUT may not be too effective is that your only really modifying one field and including the username is kind of useless. The next example shows the difference.
/users/1
PUT {id: 1, email: 'newemail@domain.com'}
Now, if the PUT was designed according the spec, then the PUT would set the username to null and you would get the following back.
{id: 1, username: null, email: 'newemail@domain.com'}
When you use a PATCH, you only update the field you specify and leave the rest alone as in your example.
The following take on the PATCH is a little different than I have never seen before.
http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
The difference between the PUT and PATCH requests is reflected in the way the server processes the enclosed entity to modify the resource identified by the Request-URI. In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version. The PATCH method affects the resource identified by the Request-URI, and it also MAY have side effects on other resources; i.e., new resources may be created, or existing ones modified, by the application of a PATCH.
PATCH /users/123
[
{ "op": "replace", "path": "/email", "value": "new.email@example.org" }
]
You are more or less treating the PATCH as a way to update a field. So instead of sending over the partial object, you're sending over the operation. i.e. Replace email with value.
The article ends with this.
It is worth mentioning that PATCH is not really designed for truly REST APIs, as Fielding's dissertation does not define any way to partially modify resources. But, Roy Fielding himself said that PATCH was something [he] created for the initial HTTP/1.1 proposal because partial PUT is never RESTful. Sure you are not transferring a complete representation, but REST does not require representations to be complete anyway.
Now, I don't know if I particularly agree with the article as many commentators point out. Sending over a partial representation can easily be a description of the changes.
For me, I am mixed on using PATCH. For the most part, I will treat PUT as a PATCH since the only real difference I have noticed so far is that PUT "should" set missing values to null. It may not be the 'most correct' way to do it, but good luck coding perfect.
The difference between PUT and PATCH is that:
- PUT is required to be idempotent. In order to achieve that, you have to put the entire complete resource in the request body.
- PATCH can be non-idempotent. Which implies it can also be idempotent in some cases, such as the cases you described.
PATCH requires some "patch language" to tell the server how to modify the resource. The caller and the server need to define some "operations" such as "add", "replace", "delete". For example:
GET /contacts/1
{
"id": 1,
"name": "Sam Kwee",
"email": "skwee357@olddomain.com",
"state": "NY",
"zip": "10001"
}
PATCH /contacts/1
{
[{"operation": "add", "field": "address", "value": "123 main street"},
{"operation": "replace", "field": "email", "value": "abc@myemail.com"},
{"operation": "delete", "field": "zip"}]
}
GET /contacts/1
{
"id": 1,
"name": "Sam Kwee",
"email": "abc@myemail.com",
"state": "NY",
"address": "123 main street",
}
Instead of using explicit "operation" fields, the patch language can make it implicit by defining conventions like:
in the PATCH request body:
- The existence of a field means "replace" or "add" that field.
- If the value of a field is null, it means delete that field.
With the above convention, the PATCH in the example can take the following form:
PATCH /contacts/1
{
"address": "123 main street",
"email": "abc@myemail.com",
"zip":
}
Which looks more concise and user-friendly. But the users need to be aware of the underlying convention.
With the operations I mentioned above, the PATCH is still idempotent. But if you define operations like: "increment" or "append", you can easily see it won't be idempotent anymore.
Let me quote and comment more closely the RFC 7231 section 4.2.2, already cited in earlier comments :
A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.
(...)
Idempotent methods are distinguished because the request can be repeated automatically if a communication failure occurs before the client is able to read the server's response. For example, if a client sends a PUT request and the underlying connection is closed before any response is received, then the client can establish a new connection and retry the idempotent request. It knows that repeating the request will have the same intended effect, even if the original request succeeded, though the response might differ.
So, what should be "the same" after a repeated request of an idempotent method? Not the server state, nor the server response, but the intended effect. In particular, the method should be idempotent "from the point of view of the client". Now, I think that this point of view shows that the last example in Dan Lowe's answer, which I don't want to plagiarize here, indeed shows that a PATCH request can be non-idempotent (in a more natural way than the example in Jason Hoetger's answer).
Indeed, let's make the example slightly more precise by making explicit one possible intend for the first client. Let's say that this client goes through the list of users with the project to check their emails and zip codes. He starts with user 1, notices that the zip is right but the email is wrong. He decides to correct this with a PATCH request, which is fully legitimate, and sends only
PATCH /users/1
{"email": "skwee357@newdomain.com"}
since this is the only correction. Now, the request fails because of some network issue and is re-submitted automatically a couple of hours later. In the meanwhile, another client has (erroneously) modified the zip of user 1. Then, sending the same PATCH request a second time does not achieve the intended effect of the client, since we end up with an incorrect zip. Hence the method is not idempotent in the sense of the RFC.
If instead the client uses a PUT request to correct the email, sending to the server all properties of user 1 along with the email, his intended effect will be achieved even if the request has to be re-sent later and user 1 has been modified in the meanwhile --- since the second PUT request will overwrite all changes since the first request.
In my humble opinion, idempotence means:
- PUT:
I send a compete resource definition, so - the resulting resource state is exactly as defined by PUT params. Each and every time I update the resource with the same PUT params - the resulting state is exactly the same.
- PATCH:
I sent only part of the resource definition, so it might happen other users are updating this resource's OTHER parameters in a meantime. Consequently - consecutive patches with the same parameters and their values might result with different resource state. For instance:
Presume an object defined as follows:
CAR: - color: black, - type: sedan, - seats: 5
I patch it with:
{color: 'red'}
The resulting object is:
CAR: - color: red, - type: sedan, - seats: 5
Then, some other users patches this car with:
{type: 'hatchback'}
so, the resulting object is:
CAR: - color: red, - type: hatchback, - seats: 5
Now, if I patch this object again with:
{color: 'red'}
the resulting object is:
CAR: - color: red, - type: hatchback, - seats: 5
What is DIFFERENT to what I've got previously!
This is why PATCH is not idempotent while PUT is idempotent.
PUT vs PATCH Great explanation here: https://medium.com/backticks-tildes/restful-api-design-put-vs-patch-4a061aa3ed0b
참고URL : https://stackoverflow.com/questions/28459418/rest-api-put-vs-patch-with-real-life-examples
'code' 카테고리의 다른 글
인증 대 권한 부여 (0) | 2020.10.03 |
---|---|
같은 여러 개를 가질 수 있습니까? (0) | 2020.10.03 |
grep -R에서 디렉토리를 제외하려면 어떻게해야합니까? (0) | 2020.10.03 |
android : layout_weight는 무엇을 의미합니까? (0) | 2020.10.02 |
바이트 배열을 문자열로 변환하는 방법 (0) | 2020.10.02 |