RESTful 서비스에서 부분 업데이트에 대한 모범 사례


208

고객 관리 시스템을 위해 RESTful 서비스를 작성 중이며 레코드를 부분적으로 업데이트하는 모범 사례를 찾으려고 노력하고 있습니다. 예를 들어, 발신자가 GET 요청으로 전체 레코드를 읽을 수 있기를 원합니다. 그러나 업데이트하려면 상태를 ENABLED에서 DISABLED로 변경하는 것과 같이 레코드의 특정 작업 만 허용됩니다. (이보다 더 복잡한 시나리오가 있습니다)

보안상의 이유로 호출자가 업데이트 된 필드만으로 전체 레코드를 제출하지 않기를 원합니다 (과도한 느낌).

URI를 구성하는 권장 방법이 있습니까? REST 책을 읽을 때 RPC 스타일 호출은 눈살을 찌푸리는 것처럼 보입니다.

다음 통화에서 ID가 123 인 고객의 전체 고객 레코드를 반환하는 경우

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

상태를 어떻게 업데이트해야합니까?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

업데이트 : 질문을 보완합니다. '비즈니스 로직 호출'을 REST API에 어떻게 통합합니까? 합의 된 방법이 있습니까? 모든 방법이 본질적으로 CRUD 인 것은 아닙니다. ' sendEmailToCustomer (123) ', ' mergeCustomers (123, 456) ', ' countCustomers () ' 와 같은 일부는 더 복잡합니다.

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

3
"비즈니스 로직 호출"에 대한 귀하의 질문에 대한 답변은 POST다음과 같습니다 : Roy Fielding 자신의 roy.gbiv.com/untangled/2009/it-is-okay-to-use-post 게시물 : 기본 아이디어 : 운영 용도에 이상적인 방법 ( GET또는 등 PUT)이 아닙니다 POST.
rojoca

이것은 내가 결국 한 일입니다. GET, PUT, DELETE를 사용하여 알려진 자원을 검색하고 업데이트하기위한 REST 호출을 작성하십시오. 새로운 리소스 추가를위한 POST 및 비즈니스 로직 호출을위한 설명 URL이있는 POST.
magiconair

무엇을 결정하든 해당 작업이 GET 응답에 속하지 않으면 RESTful 서비스가없는 것입니다. 나는 그것을 여기에서 보지 않는다
MStodd

답변:


69

기본적으로 두 가지 옵션이 있습니다.

  1. 사용 PATCH(하지만 정확하게 발생하는 것을 지정하는 고유 한 미디어 유형을 정의해야 함)

  2. 사용 POST하위 자원 및 Location 헤더가 주요 자원을 가리키는과 303 페이지의 기타를 반환합니다. 303의 목적은 클라이언트에게 "POST를 수행했으며 그 결과 다른 리소스가 업데이트 된 것입니다. 어떤 리소스의 위치 헤더를 참조하십시오." POST / 303은 일부 주요 자원의 상태를 구축하기 위해 자원에 반복적으로 추가하기위한 것이며 부분 업데이트에 완벽하게 적합합니다.


OK, POST / 303은 나에게 의미가 있습니다. 패치 및 병합 유효한 HTTP 동사 목록에서 찾을 수 없으므로 더 많은 테스트가 필요합니다. 시스템이 고객 123에게 이메일을 보내도록하려면 어떻게 URI를 구성합니까? 객체의 상태를 전혀 바꾸지 않는 순수한 RPC 메소드 호출과 같은 것. 이 작업을 수행하는 RESTful 방법은 무엇입니까?
magiconair

이메일 URI 질문을 이해하지 못합니다. POST를 통해 이메일을 보내거나 mailto : customer.123@service.org를 찾고있는 게이트웨이를 구현 하시겠습니까?
Jan Algermissen

15
REST와 HTTP는 CRUD와 HTTP 메소드를 동일시하는 일부 사람들을 제외하고 CRUD와 관련이 없습니다. REST는 표현을 전송하여 자원 상태를 조작하는 것입니다. 달성하고자하는 것이 무엇이든 적절한 의미를 가진 표현을 자원으로 전송하여 수행합니다. '순수한 메소드 호출'또는 '비즈니스 로직'이라는 용어는 'HTTP는 전송을위한 것'을 너무 쉽게 암시하므로주의하십시오. 이메일, POST를 게이트웨이 리소스로 보내려면 계정으로 병합해야하는 경우 새 계정을 만들고 다른 두 개 등의 POST 표현을 만드십시오.
Jan Algermissen

9
Google의 작동 방식도 참조하십시오. googlecode.blogspot.com/2010/03/…
Marius

4
williamdurand.fr/2014/02/14/please-do-patch-not-patch-like-an-idiot PATCH [{ "op": "test", "path": "/ a / b / c", "value" : "foo"}, { "op": "제거", "path": "/ a / b / c"}, { "op": "add", "path": "/ a / b / c" , "value": [ "foo", "bar"]}, { "op": "replace", "path": "/ a / b / c", "value": 42}, { "op": "move", "from": "/ a / b / c", "path": "/ a / b / d"}, { "op": "copy", "from": "/ a / b / d ","path ":"/ a / b / e "}]
intotecho

48

부분 업데이트에는 POST를 사용해야합니다.

고객 123의 필드를 업데이트하려면 / customer / 123에 POST를 만드십시오.

상태 만 업데이트하려면 / customer / 123 / status로 PUT 할 수도 있습니다.

일반적으로 GET 요청에는 부작용이 없어야하며 PUT은 전체 리소스를 작성 / 바꾸기위한 것입니다.

이것은 http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods에서 볼 수 있듯이 HTTP에서 직접 수행됩니다 .


1
@John Saunders POST는 반드시 URI에서 액세스 할 수있는 새로운 리소스를 만들 필요는 없습니다. tools.ietf.org/html/rfc2616#section-9.5
wsorenson

10
@wsorensen : 새 URL /customer/123을 만들 필요는 없지만 POST 는 논리적으로 고객 123 아래에있는 명백한 것을 만들어야 한다고 생각했습니다 . 주문일까요? /customer/123/statusPOST가 /customers암시 적으로 생성 status한다고 가정하고 합법적 인 REST라고 가정하면 PUT이 더 의미 가있는 것 같습니다 .
John Saunders

1
@ John Saunders : 실제로 말하면, 주어진 URI에있는 리소스의 필드를 업데이트하려면 POST가 PUT보다 더 의미가 있으며 업데이트가 없기 때문에 REST 서비스에서 자주 사용된다고 생각합니다. POST to / customers는 새로운 고객을 만들 수 있으며, / customer / 123 / status에 대한 PUT은 사양의 단어와 더 잘 일치 할 수 있지만 모범 사례와 관련하여 POST to / post하지 않는 이유는 없다고 생각합니다. customer / 123은 필드를 업데이트합니다. 간결하고 의미가 있으며 사양의 어떤 것도 엄격하게 다루지 않습니다.
wsorenson

8
POST 요청이 dem 등원이 아니어야합니까? 확실하게 항목을 업데이트하는 것은 dem 등원이므로 대신 PUT이어야합니까?
Martin Andersson

1
@MartinAndersson-요청이 비등 성일 필요POST 는 없습니다 . 언급 한 바와 같이 전체 리소스를 교체해야합니다. PUT
Halle Knast

10

json-patch 문서를 사용하여 부분 업데이트에 PATCH를 사용해야합니다 ( http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08 또는 http://www.mnot.net/ 참조). blog / 2012 / 09 / 05 / patch ) 또는 XML 패치 프레임 워크 ( http://tools.ietf.org/html/rfc5261 참조 ) 내 의견으로는, json-patch는 귀하의 비즈니스 데이터 종류에 가장 적합합니다.

JSON / XML 패치 문서가 포함 된 PATCH는 부분 업데이트를위한 매우 중요한 의미를 갖습니다. 원본 문서의 수정 된 복사본으로 POST를 사용하기 시작하면 부분 업데이트의 경우 누락 된 값 (또는 null 값)이 "이 속성을 무시합니다"또는 "이 속성을 empty value "-결국 해킹 된 솔루션으로 인해 결국에는 자신 만의 패치 형식이 만들어 질 것입니다.

http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html 에서보다 자세한 답변을 찾을 수 있습니다 .


한편 json-patchxml-patch 의 RFC는 최종 확정되었습니다.
botchniaque

8

비슷한 문제가 발생합니다. 하위 리소스의 PUT은 단일 필드 만 업데이트하려는 경우 작동하는 것 같습니다. 그러나 때로는 여러 가지 사항을 업데이트하려고합니다. 일부 항목을 변경하는 옵션이있는 리소스를 나타내는 웹 양식을 생각하십시오. 사용자가 양식을 제출하면 여러 PUT이 생성되지 않아야합니다.

내가 생각할 수있는 두 가지 솔루션이 있습니다.

  1. 전체 리소스로 PUT을 수행하십시오. 서버 측에서 전체 자원이있는 PUT이 변경되지 않은 모든 값을 무시한다는 의미를 정의하십시오.

  2. 부분 리소스로 PUT을 수행하십시오. 서버 측에서이 의미를 병합으로 정의하십시오.

2는 1의 대역폭 최적화입니다. 자원이 일부 필드가 필수 필드 인 경우 (프로토 버퍼 생각) 1이 유일한 옵션입니다.

이 두 가지 접근 방식의 문제점은 필드를 지우는 방법입니다. 필드를 지우는 특수 널 값을 정의해야합니다 (특히 널 값은 프로토 버퍼에 대해 정의되지 않으므로 프로토 버퍼에 대해).

코멘트?


2
별도의 질문으로 게시하는 경우 더 유용합니다.
intotecho

6

상태를 수정하기 위해 RESTful 접근법은 리소스의 상태를 설명하는 논리적 하위 리소스를 사용하는 것입니다. 이 IMO는 상태가 줄어든 경우 매우 유용하고 깨끗합니다. 고객 리소스에 대한 기존 작업을 강요하지 않고도 API를보다 표현력있게 만듭니다.

예:

POST /customer/active  <-- Providing entity in the body a new customer
{
  ...  // attributes here except status
}

POST 서비스는 새로 작성된 고객을 id로 반환해야합니다.

{
    id:123,
    ...  // the other fields here
}

생성 된 리소스에 대한 GET은 리소스 위치를 사용합니다.

GET /customer/123/active

GET / customer / 123 / inactive는 404를 반환해야합니다.

PUT 조작의 경우 Json 엔티티를 제공하지 않고 상태 만 업데이트합니다.

PUT /customer/123/inactive  <-- Deactivating an existing customer

엔티티를 제공하면 고객의 컨텐츠를 업데이트하고 동시에 상태를 업데이트 할 수 있습니다.

PUT /customer/123/inactive
{
    ...  // entity fields here except id and status
}

고객 자원에 대한 개념적 하위 자원을 작성 중입니다. 또한 Roy Fielding의 자원에 대한 정의와 일치합니다. "... 자원은 특정 시점에서 맵핑에 해당하는 엔티티가 아닌 엔티티 세트에 대한 개념적 맵핑입니다." 개념적 매핑은 status = ACTIVE 인 고객에게 능동적 인 고객입니다.

읽기 조작 :

GET /customer/123/active 
GET /customer/123/inactive

다른 호출 중 하나가 상태 404를 반환해야하는 즉시 해당 호출을 수행하면 성공적인 출력에는 암시 적 상태가 포함되지 않을 수 있습니다. 물론 GET / customer / 123? status = ACTIVE | INACTIVE를 사용하여 고객 리소스를 직접 쿼리 할 수 ​​있습니다.

의미 체계가 혼동 될 수 있으므로 DELETE 작업이 흥미 롭습니다. 그러나이 개념 자원에 대해 해당 조작을 공개하지 않거나 비즈니스 논리에 따라이를 사용할 수 있습니다.

DELETE /customer/123/active

이를 통해 고객을 삭제 / 비활성화 상태 또는 반대 상태 (ACTIVE / INACTIVE)로 전환 할 수 있습니다.


하위 리소스에 어떻게 접근합니까?
MStodd

답을 더 명확하게하기 위해 답을 리팩터링했습니다
raspacorp

5

확장 된 질문에 추가 할 사항. 더 복잡한 비즈니스 활동을 완벽하게 설계 할 수 있다고 생각합니다. 그러나 당신은 사고 방식 / 절차 스타일을 제시하고 자원과 동사에 대해 더 많이 생각해야합니다.

메일 발송


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

이 자원 + POST의 구현은 메일을 발송합니다. 필요한 경우 / customer / 123 / outbox와 같은 것을 제공 한 다음 / customer / mails / {mailId}에 대한 리소스 링크를 제공 할 수 있습니다.

고객 수

검색 리소스처럼 처리 할 수 ​​있습니다 (페이징이 포함 된 검색 메타 데이터 및 고객 수를 제공하는 num-found 정보 포함).


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}


POST 하위 리소스에서 필드를 논리적으로 그룹화하는 방법이 마음에 듭니다.
gertas

3

불완전한 / 부분 리소스를 업데이트하려면 PUT을 사용하십시오.

jObject를 매개 변수로 승인하고 해당 값을 구문 분석하여 자원을 업데이트 할 수 있습니다.

다음은 참조로 사용할 수있는 기능입니다.

public IHttpActionResult Put(int id, JObject partialObject)
{
    Dictionary<string, string> dictionaryObject = new Dictionary<string, string>();

    foreach (JProperty property in json.Properties())
    {
        dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
    }

    int id = Convert.ToInt32(dictionaryObject["id"]);
    DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);            
    Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);

    //Call function to update resource
    update(id, startTime, isGroup);

    return Ok(appointmentModelList);
}

2

업데이트 관련.

CRUD의 개념은 API 디자인과 관련하여 약간의 혼란을 초래했습니다. CRUD는 기본 작업에서 데이터에 대해 수행하는 일반적인 저수준 개념이며 HTTP 동사는 CRUD 작업에 매핑되거나 매핑되지 않을 수 있는 요청 방법 ( 21 년 전 생성) 일뿐 입니다. 실제로 HTTP 1.0 / 1.1 사양에서 CRUD 약어가 있는지 찾아보십시오.

실용적 규칙을 적용하는 잘 설명 된 가이드는 Google 클라우드 플랫폼 API 설명서 에서 찾을 수 있습니다 . 여기에는 작업에 비해 많은 양의 리소스를 강조하는 리소스 기반 API 생성의 개념과 설명하는 사용 사례가 포함되어 있습니다. 그들의 제품에 대한 컨벤션 디자인 일 뿐이지 만 많은 의미가 있다고 생각합니다.

여기서 기본 개념 (그리고 많은 혼동을 일으키는 개념)은 "방법"과 HTTP 동사 사이의 매핑입니다. 하나는 API가 어떤 유형의 리소스 (예 : 고객 목록을 얻거나 이메일을 보내는 것)에 대해 수행 할 "작업"(방법)을 정의하고 다른 하나는 HTTP 동사입니다. 사용하려는 메소드와 동사, 그리고 이들 사이맵핑에 대한 정의가 있어야 합니다 .

작업이 표준 방법으로 정확하게 매핑되지 않을 때 그것은 또한 (그 말한다 List, Get, Create, Update, Delete이 경우), 하나 같이, "사용자 정의 방법"을 사용할 수 있습니다 BatchGet여러 개체 ID 입력을 기반으로 여러 개체를 검색하거나하는 SendEmail.


2

RFC 7396 : JSON 병합 패치 (질문이 게시 된 후 4 년 후에 게시 됨)는 PATCH의 모범 사례를 형식 및 처리 규칙과 관련하여 설명합니다.

간단히 말해서, application / merge-patch + json MIME 미디어 유형과 변경 / 추가 / 제거하려는 부분 만 나타내는 본문 을 사용하여 대상 리소스에 HTTP PATCH를 제출 한 다음 아래 처리 규칙을 따릅니다.

규칙 :

  • 제공된 병합 패치에 대상 내에 나타나지 않는 구성원이 포함 된 경우 해당 구성원이 추가됩니다.

  • 대상에 멤버가 포함되어 있으면 값이 바뀝니다.

  • 병합 패치의 널 (NULL) 값은 대상에서 기존 값이 제거되었음을 나타내는 특별한 의미를 갖습니다.

RFC 의 부록 에서 볼 수있는 위의 규칙을 설명하는 테스트 사례 예 :

 ORIGINAL         PATCH           RESULT
--------------------------------------------
{"a":"b"}       {"a":"c"}       {"a":"c"}

{"a":"b"}       {"b":"c"}       {"a":"b",
                                 "b":"c"}
{"a":"b"}       {"a":null}      {}

{"a":"b",       {"a":null}      {"b":"c"}
"b":"c"}

{"a":["b"]}     {"a":"c"}       {"a":"c"}

{"a":"c"}       {"a":["b"]}     {"a":["b"]}

{"a": {         {"a": {         {"a": {
  "b": "c"}       "b": "d",       "b": "d"
}                 "c": null}      }
                }               }

{"a": [         {"a": [1]}      {"a": [1]}
  {"b":"c"}
 ]
}

["a","b"]       ["c","d"]       ["c","d"]

{"a":"b"}       ["c"]           ["c"]

{"a":"foo"}     null            null

{"a":"foo"}     "bar"           "bar"

{"e":null}      {"a":1}         {"e":null,
                                 "a":1}

[1,2]           {"a":"b",       {"a":"b"}
                 "c":null}

{}              {"a":            {"a":
                 {"bb":           {"bb":
                  {"ccc":          {}}}
                   null}}}

1

http://www.odata.org/를 확인 하십시오

MERGE 메소드를 정의하므로 귀하의 경우 다음과 같습니다.

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

status속성 만 업데이트되고 다른 값은 유지됩니다.


MERGE유효한 HTTP 동사는?
John Saunders

3
PATCH를 살펴보십시오. 곧 표준 HTTP이며 동일한 기능을 수행합니다.
Jan Algermissen

@John Saunders 네, 확장 방법입니다.
Max Toro

FYI MERGE가 OData v4에서 제거되었습니다. MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. 참조 docs.oasis-open.org/odata/new-in-odata/v4.0/cn01/...
tanguy_k

0

중요하지 않습니다. REST의 관점에서 GET을 수행 할 수는 없습니다. 캐시 할 수는 없지만 POST, PATCH 또는 PUT 등을 사용하더라도 URL의 모양은 중요하지 않습니다. REST를 수행하는 경우 중요한 것은 서버에서 자원을 표시 할 때 해당 표시에 클라이언트 상태 전이 옵션을 제공 할 수 있다는 것입니다.

GET 응답에 상태 전이가있는 경우 클라이언트는이를 읽는 방법을 알고 있으면되고, 필요한 경우 서버가이를 변경할 수 있습니다. 여기서는 POST를 사용하여 업데이트를 수행하지만 PATCH로 변경되었거나 URL이 변경되면 클라이언트는 여전히 업데이트 방법을 알고 있습니다.

{
  "customer" :
  {
  },
  "operations":
  [
    "update" : 
    {
      "method": "POST",
      "href": "https://server/customer/123/"
    }]
}

클라이언트가 사용자에게 돌려 줄 필수 / 선택적 매개 변수를 나열 할 수 있습니다. 응용 프로그램에 따라 다릅니다.

비즈니스 운영의 경우 고객 리소스와 연결된 다른 리소스 일 수 있습니다. 고객에게 이메일을 보내려는 경우 해당 서비스는 POST 할 수있는 자체 리소스이므로 고객 리소스에 다음 작업을 포함시킬 수 있습니다.

"email":
{
  "method": "POST",
  "href": "http://server/emailservice/send?customer=1234"
}

좋은 비디오와 발표자의 REST 아키텍처의 예는 다음과 같습니다. Stormpath는 GET / POST / DELETE 만 사용합니다. REST는 사용자가 사용하는 작업이나 URL의 모양과 아무 관련이 없기 때문에 좋습니다 (GET이 캐시 가능해야 함 제외).

https://www.youtube.com/watch?v=pspy1H6A3FM ,
https://www.youtube.com/watch?v=5WXYw4J4QOU ,
http://docs.stormpath.com/rest/quickstart/

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.