REST 웹 서비스에서 일괄 작업을 처리하기위한 패턴?
REST 스타일 웹 서비스 내의 리소스에 대한 배치 작업에 대해 어떤 입증 된 디자인 패턴이 있습니까?
성능과 안정성 측면에서 이상과 현실 사이의 균형을 유지하려고합니다. 현재 모든 작업이 목록 리소스 (예 : GET / user) 또는 단일 인스턴스 (PUT / user / 1, DELETE / user / 22 등)에서 검색하는 API가 있습니다.
전체 개체 집합의 단일 필드를 업데이트하려는 경우가 있습니다. 하나의 필드를 업데이트하기 위해 각 객체에 대한 전체 표현을주고받는 것이 매우 낭비적인 것처럼 보입니다.
RPC 스타일 API에서는 다음과 같은 방법을 사용할 수 있습니다.
/mail.do?method=markAsRead&messageIds=1,2,3,4... etc.
여기서 REST와 동등한 것은 무엇입니까? 아니면 타협해도 괜찮습니다. 실제로 성능 등을 향상시키는 몇 가지 특정 작업을 추가하도록 디자인을 망치게됩니까? 모든 경우에있어서의 클라이언트는 현재 웹 브라우저 (클라이언트 측의 자바 스크립트 애플리케이션)이다.
배치에 대한 간단한 RESTful 패턴은 콜렉션 자원을 사용하는 것입니다. 예를 들어 한 번에 여러 메시지를 삭제합니다.
DELETE /mail?&id=0&id=1&id=2
부분 리소스 또는 리소스 속성을 일괄 업데이트하는 것이 조금 더 복잡합니다. 즉, 각 표시된 AsRead 속성을 업데이트하십시오. 기본적으로 속성을 각 리소스의 일부로 취급하는 대신 리소스를 넣을 버킷으로 취급합니다. 하나의 예가 이미 게시되었습니다. 조금 조정했습니다.
POST /mail?markAsRead=true
POSTDATA: ids=[0,1,2]
기본적으로 읽은 것으로 표시된 메일 목록을 업데이트하고 있습니다.
이것을 사용하여 여러 항목을 동일한 카테고리에 할당 할 수도 있습니다.
POST /mail?category=junk
POSTDATA: ids=[0,1,2]
iTunes 스타일 배치 부분 업데이트 (예 : artist + albumTitle이지만 trackTitle이 아님)를 수행하는 것은 훨씬 더 복잡합니다. 버킷 유추가 시작됩니다.
POST /mail?markAsRead=true&category=junk
POSTDATA: ids=[0,1,2]
장기적으로 단일 부분 자원 또는 자원 속성을 업데이트하는 것이 훨씬 쉽습니다. 하위 리소스를 사용하십시오.
POST /mail/0/markAsRead
POSTDATA: true
또는 매개 변수화 된 자원을 사용할 수 있습니다. REST 패턴에서는 일반적이지 않지만 URI 및 HTTP 스펙에서는 허용됩니다. 세미콜론은 자원 내에서 수평으로 관련된 매개 변수를 나눕니다.
여러 속성과 여러 자원을 업데이트하십시오.
POST /mail/0;1;2/markAsRead;category
POSTDATA: markAsRead=true,category=junk
하나의 속성만으로 여러 리소스를 업데이트하십시오.
POST /mail/0;1;2/markAsRead
POSTDATA: true
하나의 자원만으로 여러 속성을 업데이트하십시오.
POST /mail/0/markAsRead;category
POSTDATA: markAsRead=true,category=junk
RESTful 한 창의력이 풍부합니다.
전혀 그렇지 않다. 나는 REST에 상응하는 것이 (또는 적어도 하나의 솔루션이) 거의 정확하다고 생각한다.
나는 Crane and Pascarello의 책 Ajax in Action (훌륭한 책, 강력히 권장되는 책) 에서 언급 한 패턴을 상기시켜 줍니다.이 명령은 CommandQueue 종류의 객체를 요청으로 일괄 처리하고 그런 다음 정기적으로 서버에 게시하십시오.
내가 정확하게 기억한다면 객체는 본질적으로 "명령"의 배열을 가졌다. 예를 들어, 각각의 "markAsRead"명령을 포함하는 레코드, "messageId"및 콜백 / 핸들러에 대한 참조 기능-일정에 따라 또는 일부 사용자 작업에 따라 명령 개체가 직렬화되어 서버에 게시되고 클라이언트는 후속 사후 처리를 처리합니다.
자세한 내용은 알 수 없지만 이런 종류의 명령 대기열이 문제를 처리하는 한 가지 방법 인 것 같습니다. 전체적인 대화가 줄고 서버 측 인터페이스를 추상화하여 더 유연하게 사용할 수 있습니다.
업데이트 : 아하! 나는 그 책에서 온라인으로 코드 조각으로 완성 된 코드를 발견했습니다 (실제로 책을 집어 올리는 것이 좋습니다!). 섹션 5.5.3으로 시작하는 여기를 살펴보십시오 .
이것은 코딩하기 쉽지만 서버에 대한 아주 작은 비트의 트래픽을 초래할 수 있으며, 이는 비효율적이고 혼란을 줄 수 있습니다. 트래픽을 제어하려면 이러한 업데이트를 캡처 하여 로컬로 큐에 넣은 다음 여가 시간에 일괄 적으로 서버로 보낼 수 있습니다. JavaScript로 구현 된 간단한 업데이트 큐가 목록 5.13에 나와 있습니다. [...]
대기열은 두 개의 배열을 유지합니다.
queued
새로운 업데이트가 추가되는 숫자 인덱스 배열입니다.sent
서버로 전송되었지만 응답을 기다리는 업데이트가 포함 된 연관 배열입니다.
다음은 두 가지 관련 기능입니다. 하나는 명령을 대기열에 추가하고 ( addCommand
) 직렬화 한 다음 서버로 전송하는 역할을합니다 fireRequest
.
CommandQueue.prototype.addCommand = function(command)
{
if (this.isCommand(command))
{
this.queue.append(command,true);
}
}
CommandQueue.prototype.fireRequest = function()
{
if (this.queued.length == 0)
{
return;
}
var data="data=";
for (var i = 0; i < this.queued.length; i++)
{
var cmd = this.queued[i];
if (this.isCommand(cmd))
{
data += cmd.toRequestString();
this.sent[cmd.id] = cmd;
// ... and then send the contents of data in a POST request
}
}
}
당신이 갈 수 있도록해야합니다. 행운을 빕니다!
@Alex가 올바른 길을 가고 있다고 생각하지만 개념적으로 제안 된 것과 반대이어야한다고 생각합니다.
URL은 실제로 "대상으로하는 리소스"입니다.
[GET] mail/1
ID가 1 인 메일에서 레코드를 가져오고
[PATCH] mail/1 data: mail[markAsRead]=true
메일 레코드에 ID 1을 패치하는 것을 의미합니다. querystring은 "필터"이며 URL에서 반환 된 데이터를 필터링합니다.
[GET] mail?markAsRead=true
그래서 우리는 이미 읽은 것으로 표시된 모든 메일을 요청하고 있습니다. 따라서이 경로에 대한 [PATCH]는 " 이미 true로 표시된 레코드를 패치합니다"라고 말할 것입니다 . 이것은 우리가 달성하려는 것이 아닙니다.
따라서이 생각에 따른 배치 방법은 다음과 같아야합니다.
[PATCH] mail/?id=1,2,3 <the records we are targeting> data: mail[markAsRead]=true
물론 이것은 이것이 진정한 REST (배치 레코드 조작을 허용하지 않음)라고 말하지 않고 이미 존재하고 REST에서 사용중인 논리를 따릅니다.
귀하의 언어 인 " 매우 낭비스러운 것 같습니다 ..."는 나에게 조기 최적화 시도를 나타냅니다. 객체의 전체 표현을 전송하는 것이 주요 성능 적중이라는 것을 알 수 없다면 (우리는> 150ms로 사용자에게 용납 할 수 없다고 말하고 있습니다) 새로운 비표준 API 동작을 만들려는 시도는 없습니다. API가 단순할수록 사용하기 쉬워집니다.
삭제의 경우 서버가 삭제가 발생하기 전에 객체의 상태에 대해 알 필요가 없으므로 다음을 전송하십시오.
DELETE /emails
POSTDATA: [{id:1},{id:2}]
다음 생각은 응용 프로그램이 개체의 대량 업데이트와 관련하여 성능 문제가 발생하는 경우 각 개체를 여러 개체로 나눌 때 고려해야 할 사항입니다. 그런 식으로 JSON 페이로드는 크기의 일부입니다.
예를 들어 두 개의 개별 전자 메일의 "읽기"및 "보관 된"상태를 업데이트하기 위해 응답을 보낼 때 다음을 보내야합니다.
PUT /emails
POSTDATA: [
{
id:1,
to:"someone@bratwurst.com",
from:"someguy@frommyville.com",
subject:"Try this recipe!",
text:"1LB Pork Sausage, 1 Onion, 1T Black Pepper, 1t Salt, 1t Mustard Powder",
read:true,
archived:true,
importance:2,
labels:["Someone","Mustard"]
},
{
id:2,
to:"someone@bratwurst.com",
from:"someguy@frommyville.com",
subject:"Try this recipe (With Fix)",
text:"1LB Pork Sausage, 1 Onion, 1T Black Pepper, 1t Salt, 1T Mustard Powder, 1t Garlic Powder",
read:true,
archived:false,
importance:1,
labels:["Someone","Mustard"]
}
]
전자 메일의 가변 구성 요소 (읽기, 보관, 중요도, 레이블)를 다른 개체 (대상, 텍스트, 텍스트)가 업데이트되지 않으므로 별도의 개체로 분리합니다.
PUT /email-statuses
POSTDATA: [
{id:15,read:true,archived:true,importance:2,labels:["Someone","Mustard"]},
{id:27,read:true,archived:false,importance:1,labels:["Someone","Mustard"]}
]
또 다른 접근 방식은 PATCH를 사용하는 것입니다. 업데이트하려는 속성과 다른 모든 속성을 무시해야 함을 명시 적으로 나타냅니다.
PATCH /emails
POSTDATA: [
{
id:1,
read:true,
archived:true
},
{
id:2,
read:true,
archived:false
}
]
People state that PATCH should be implemented by providing an array of changes containing: action (CRUD), path (URL), and value change. This may be considered a standard implementation but if you look at the entirety of a REST API it is a non-intuitive one-off. Also, the above implementation is how GitHub has implemented PATCH.
To sum it up, it is possible to adhere to RESTful principles with batch actions and still have acceptable performance.
The google drive API has a really interesting system to solve this problem (see here).
What they do is basically grouping different requests in one Content-Type: multipart/mixed
request, with each individual complete request separated by some defined delimiter. Headers and query parameter of the batch request are inherited to the individual requests (i.e. Authorization: Bearer some_token
) unless they are overridden in the individual request.
Example: (taken from their docs)
Request:
POST https://www.googleapis.com/batch
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.20.0 (gzip)
Content-Type: multipart/mixed; boundary=END_OF_PART
Content-Length: 963
--END_OF_PART
Content-Length: 337
Content-Type: application/http
content-id: 1
content-transfer-encoding: binary
POST https://www.googleapis.com/drive/v3/files/fileId/permissions?fields=id
Authorization: Bearer authorization_token
Content-Length: 70
Content-Type: application/json; charset=UTF-8
{
"emailAddress":"example@appsrocks.com",
"role":"writer",
"type":"user"
}
--END_OF_PART
Content-Length: 353
Content-Type: application/http
content-id: 2
content-transfer-encoding: binary
POST https://www.googleapis.com/drive/v3/files/fileId/permissions?fields=id&sendNotificationEmail=false
Authorization: Bearer authorization_token
Content-Length: 58
Content-Type: application/json; charset=UTF-8
{
"domain":"appsrocks.com",
"role":"reader",
"type":"domain"
}
--END_OF_PART--
Response:
HTTP/1.1 200 OK
Alt-Svc: quic=":443"; p="1"; ma=604800
Server: GSE
Alternate-Protocol: 443:quic,p=1
X-Frame-Options: SAMEORIGIN
Content-Encoding: gzip
X-XSS-Protection: 1; mode=block
Content-Type: multipart/mixed; boundary=batch_6VIxXCQbJoQ_AATxy_GgFUk
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
Date: Fri, 13 Nov 2015 19:28:59 GMT
Cache-Control: private, max-age=0
Vary: X-Origin
Vary: Origin
Expires: Fri, 13 Nov 2015 19:28:59 GMT
--batch_6VIxXCQbJoQ_AATxy_GgFUk
Content-Type: application/http
Content-ID: response-1
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Fri, 13 Nov 2015 19:28:59 GMT
Expires: Fri, 13 Nov 2015 19:28:59 GMT
Cache-Control: private, max-age=0
Content-Length: 35
{
"id": "12218244892818058021i"
}
--batch_6VIxXCQbJoQ_AATxy_GgFUk
Content-Type: application/http
Content-ID: response-2
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Fri, 13 Nov 2015 19:28:59 GMT
Expires: Fri, 13 Nov 2015 19:28:59 GMT
Cache-Control: private, max-age=0
Content-Length: 35
{
"id": "04109509152946699072k"
}
--batch_6VIxXCQbJoQ_AATxy_GgFUk--
I would be tempted in an operation like the one in your example to write a range parser.
It's not a lot of bother to make a parser that can read "messageIds=1-3,7-9,11,12-15". It would certainly increase efficiency for blanket operations covering all messages and is more scalable.
Great post. I've been searching for a solution for a few days. I came up with a solution of using passing a query string with a bunch IDs separated by commas, like:
DELETE /my/uri/to/delete?id=1,2,3,4,5
...then passing that to a WHERE IN
clause in my SQL. It works great, but wonder what others think of this approach.
From my point of view I think Facebook has the best implementation.
A single HTTP request is made with a batch parameter and one for a token.
In batch a json is sent. which contains a collection of "requests". Each request has a method property (get / post / put / delete / etc ...), and a relative_url property (uri of the endpoint), additionally the post and put methods allow a "body" property where the fields to be updated are sent .
more info at: Facebook batch API
'IT story' 카테고리의 다른 글
톰캣 VS 부두 (0) | 2020.05.29 |
---|---|
Android 및 XMPP : 현재 사용 가능한 솔루션 (0) | 2020.05.29 |
표준 브라우저 가상 머신이 아닌 왜 JavaScript입니까? (0) | 2020.05.29 |
오류 : R에서… 기능을 찾을 수 없습니다 (0) | 2020.05.29 |
mysql 확장은 더 이상 사용되지 않으며 향후 제거 될 예정입니다. 대신 mysqli 또는 PDO를 사용하십시오. [duplicate] (0) | 2020.05.29 |