RESTful 서비스에서 부분 업데이트에 대한 모범 사례
고객 관리 시스템을 위해 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
고마워요 프랭크
기본적으로 두 가지 옵션이 있습니다.
사용
PATCH
(하지만 정확하게 발생하는 것을 지정하는 고유 한 미디어 유형을 정의해야 함)사용
POST
하위 자원 및 Location 헤더가 주요 자원을 가리키는과 303 페이지의 기타를 반환합니다. 303의 의도는 클라이언트에게 "POST를 수행했으며 그 결과 다른 리소스가 업데이트 된 것입니다. 어떤 리소스의 위치 헤더를 참조하십시오." POST / 303은 일부 주요 자원의 상태를 구축하기 위해 자원에 반복적으로 추가하기위한 것이며 부분 업데이트에 완벽하게 적합합니다.
부분 업데이트에는 POST를 사용해야합니다.
고객 123의 필드를 업데이트하려면 / customer / 123에 POST를 만드십시오.
상태 만 업데이트하려면 / customer / 123 / status로 PUT 할 수도 있습니다.
일반적으로 GET 요청에는 부작용이 없어야하며 PUT은 전체 리소스를 작성 / 바꾸기위한 것입니다.
이것은 http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods에서 볼 수 있듯이 HTTP에서 직접 수행됩니다 .
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 에서보다 자세한 답변을 찾을 수 있습니다 .
비슷한 문제가 발생합니다. 하위 리소스의 PUT은 단일 필드 만 업데이트하려는 경우 작동하는 것 같습니다. 그러나 때로는 여러 가지 사항을 업데이트하려고합니다. 일부 항목을 변경하는 옵션이있는 리소스를 나타내는 웹 양식을 생각하십시오. 사용자가 양식을 제출하면 여러 PUT이 생성되지 않아야합니다.
내가 생각할 수있는 두 가지 솔루션이 있습니다.
전체 리소스로 PUT을 수행하십시오. 서버 측에서 전체 자원이있는 PUT이 변경되지 않은 모든 값을 무시한다는 의미를 정의하십시오.
부분 리소스로 PUT을 수행하십시오. 서버 측에서이 의미를 병합으로 정의하십시오.
2는 단지 1의 대역폭 최적화입니다. 자원이 일부 필드가 필수 필드 인 경우 (프로토 버퍼 생각) 1이 유일한 옵션입니다.
이 두 가지 접근 방식의 문제점은 필드를 지우는 방법입니다. 필드를 지우는 특수 널 값을 정의해야합니다 (특히 널 값은 프로토 버퍼에 대해 정의되지 않으므로 프로토 버퍼에 대해).
코멘트?
확장 된 질문에 추가 할 사항. 더 복잡한 비즈니스 활동을 완벽하게 설계 할 수 있다고 생각합니다. 그러나 당신은 사고 방식 / 절차 스타일을 제시하고 자원과 동사에 대해 더 많이 생각해야합니다.
메일 발송
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: { ...} ....}
상태를 수정하기 위해 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
}
You are creating a conceptual sub-resource for your customer resource. It is also consistent with Roy Fielding's definition of a resource: "...A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time..." In this case the conceptual mapping is active-customer to customer with status=ACTIVE.
Read operation:
GET /customer/123/active
GET /customer/123/inactive
If you make those calls one right after the other one of them must return status 404, the successful output may not include the status as it is implicit. Of course you can still use GET /customer/123?status=ACTIVE|INACTIVE to query the customer resource directly.
The DELETE operation is interesting as the semantics can be confusing. But you have the option of not publishing that operation for this conceptual resource, or use it in accordance with your business logic.
DELETE /customer/123/active
That one can take your customer to a DELETED/DISABLED status or to the opposite status (ACTIVE/INACTIVE).
Use PUT for updating incomplete/partial resource.
You can accept jObject as parameter and parse its value to update the resource.
Below is the function which you can use as a reference :
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);
}
Check out http://www.odata.org/
It defines the MERGE method, so in your case it would be something like this:
MERGE /customer/123
<customer>
<status>DISABLED</status>
</customer>
Only the status
property is updated and the other values are preserved.
Regarding your Update.
The concept of CRUD I believe has caused some confusion regarding API design. CRUD is a general low level concept for basic operations to perform on data, and HTTP verbs are just request methods (created 21 years ago) that may or may not map to a CRUD operation. In fact, try to find the presence of the CRUD acronym in the HTTP 1.0/1.1 specification.
A very well explained guide that applies a pragmatic convention can be found in the Google cloud platform API documentation. It describes the concepts behind the creation of a resource based API, one that emphasizes a big amount of resources over operations, and includes the use cases that you are describing. Although is a just a convention design for their product, I think it makes a lot of sense.
The base concept here (and one that produces a lot of confusion) is the mapping between "methods" and HTTP verbs. One thing is to define what "operations" (methods) your API will do over which types of resources (for example, get a list of customers, or send an email), and another are the HTTP verbs. There must be a definition of both, the methods and the verbs that you plan to use and a mapping between them.
It also says that, when an operation does not map exactly with a standard method (List
, Get
, Create
, Update
, Delete
in this case), one may use "Custom methods", like BatchGet
, which retrieves several objects based on several object id input, or SendEmail
.
It doesn't matter. In terms of REST, you can't do a GET, because it's not cacheable, but it doesn't matter if you use POST or PATCH or PUT or whatever, and it doesn't matter what the URL looks like. If you're doing REST, what matters is that when you get a representation of your resource from the server, that representation is able give the client state transition options.
If your GET response had state transitions, the client just needs to know how to read them, and the server can change them if needed. Here an update is done using POST, but if it was changed to PATCH, or if the URL changes, the client still knows how to make an update:
{
"customer" :
{
},
"operations":
[
"update" :
{
"method": "POST",
"href": "https://server/customer/123/"
}]
}
You could go as far as to list required/optional parameters for the client to give back to you. It depends on the application.
As far as business operations, that might be a different resource linked to from the customer resource. If you want to send an email to the customer, maybe that service is it's own resource that you can POST to, so you might include the following operation in the customer resource:
"email":
{
"method": "POST",
"href": "http://server/emailservice/send?customer=1234"
}
Some good videos, and example of the presenter's REST architecture are these. Stormpath only uses GET/POST/DELETE, which is fine since REST has nothing to do with what operations you use or how URLs should look (except GETs should be cacheable):
https://www.youtube.com/watch?v=pspy1H6A3FM,
https://www.youtube.com/watch?v=5WXYw4J4QOU,
http://docs.stormpath.com/rest/quickstart/
RFC 7396: JSON Merge Patch (published four years after the question was posted) describes the best practices for a PATCH in terms of the format and processing rules.
In a nutshell, you submit an HTTP PATCH to a target resource with the application/merge-patch+json MIME media type and a body representing only the parts that you want to be changed/added/removed and then follow the below processing rules.
Rules:
If the provided merge patch contains members that do not appear within the target, those members are added.
If the target does contain the member, the value is replaced.
Null values in the merge patch are given special meaning to indicate the removal of existing values in the target.
Example test cases that illustrate the rules above (as seen in the appendix of that 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}}}
참고URL : https://stackoverflow.com/questions/2443324/best-practice-for-partial-updates-in-a-restful-service
'IT story' 카테고리의 다른 글
특정 경도와 위도로 Google지도에 연결하려면 어떻게해야하나요? (0) | 2020.05.09 |
---|---|
문자열을 배열에 넣는 방법, 줄 바꿈? (0) | 2020.05.09 |
node.js로 보안 REST API를 구현하는 방법 (0) | 2020.05.09 |
Android의 FFmpeg (0) | 2020.05.09 |
AngularJS 컨트롤러의 수명주기는 무엇입니까? (0) | 2020.05.09 |