태그 보관물: rest

rest

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
            }
          ]

사람들은 조치 (CRUD), 경로 (URL) 및 값 변경을 포함하는 변경 배열을 제공하여 PATCH를 구현해야한다고 말합니다. 이는 표준 구현으로 간주 될 수 있지만 REST API 전체를 살펴보면 직관적이지 않은 일회성입니다. 또한 위의 구현은 GitHub가 PATCH를 구현 한 방법 입니다.

요약하면 배치 작업으로 RESTful 원칙을 준수하면서도 여전히 수용 가능한 성능을 유지할 수 있습니다.


답변

Google 드라이브 API에는이 문제를 해결하기위한 정말 흥미로운 시스템이 있습니다 ( 여기 참조 ).

그들이하는 일은 기본적으로 하나의 Content-Type: multipart/mixed요청 으로 다른 요청을 그룹화하는 것 입니다. 각 개별 요청은 정의 된 구분 기호로 구분됩니다. 일괄 요청의 헤더 및 쿼리 매개 변수는 개별 요청 Authorization: Bearer some_token에서 재정의되지 않는 한 개별 요청 (예 :)으로 상속됩니다 .


: ( 문서 에서 가져온 )

의뢰:

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--

응답:

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--

답변

레인지 파서를 작성하는 예제와 같은 작업을 원합니다.

“messageIds = 1-3,7-9,11,12-15″를 읽을 수있는 파서를 만드는 것은 귀찮지 않습니다. 모든 메시지를 포괄하는 담요 작업의 효율성을 확실히 높이고 확장 성이 뛰어납니다.


답변

좋은 포스트. 며칠 동안 해결책을 찾고있었습니다. 다음과 같이 쉼표로 구분 된 묶음 ID로 쿼리 문자열을 전달하는 솔루션을 생각해 냈습니다.

DELETE /my/uri/to/delete?id=1,2,3,4,5

… 그런 다음 WHERE IN내 SQL 의 절로 전달합니다 . 그것은 잘 작동하지만 다른 사람들 이이 접근법에 대해 어떻게 생각하는지 궁금합니다.