当前位置:  开发笔记 > 编程语言 > 正文

用于处理REST Web服务中的批处理操作的模式?

如何解决《用于处理RESTWeb服务中的批处理操作的模式?》经验,为你挑选了5个好方法。

REST样式Web服务中的资源批处理操作存在哪些经过验证的设计模式?

我试图在性能和稳定性方面在理想与现实之间取得平衡.我们现在有一个API,所有操作都从列表资源(即:GET /用户)或单个实例(PUT/user/1,DELETE/user/22等)中检索.

在某些情况下,您希望更新整组对象的单个字段.向前后发送每个对象的整个表示来更新一个字段似乎非常浪费.

在RPC样式API中,您可以使用以下方法:

/mail.do?method=markAsRead&messageIds=1,2,3,4... etc. 

什么是REST等效的?或者可以偶尔妥协.它是否会破坏设计以在一些特定的操作中添加它真正提高性能等?现在所有情况下的客户端都是Web浏览器(客户端的javascript应用程序).



1> Alex..:

批处理的简单RESTful模式是使用集合资源.例如,要一次删除多条消息.

DELETE /mail?&id=0&id=1&id=2

批量更新部分资源或资源属性会有点复杂.也就是说,更新每个markedAsRead属性.基本上,不是将属性视为每个资源的一部分,而是将其视为将资源放入其中的存储桶.已经发布了一个例子.我调整了一下.

POST /mail?markAsRead=true
POSTDATA: ids=[0,1,2]

基本上,您正在更新标记为已读的邮件列表.

您也可以使用它将多个项目分配到同一类别.

POST /mail?category=junk
POSTDATA: ids=[0,1,2]

显然,进行iTunes风格的批量部分更新(例如,艺术家+ 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创造力丰富.


没有必要.POST是一种工厂模式方法,它比PUT/DELETE/GET更不明确和明显.唯一的期望是服务器将决定POST的结果.POST正是它一直以来的样子,我提交表单数据并且服务器做了一些事情(希望有所期待),并给出了一些关于结果的指示.我们不需要使用POST创建资源,我们经常选择.我可以使用PUT轻松创建资源,我只需要将资源URL定义为发送者(通常不是理想的).
分号是为此目的而保留的.

2> Christian Nu..:

完全没有 - 我认为REST等价物(或者至少是一个解决方案)几乎就是 - 设计的专用接口适应客户端所需的操作.

我想起了Crane和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
        }
    }
}

那应该让你去.祝好运!



3> fezfox..:

虽然我认为@Alex走的是正确的道路,但从概念上讲,我认为它应该与建议的相反.

该URL实际上是"我们所定位的资源",因此:

    [GET] mail/1

意味着从id为1的邮件中获取记录

    [PATCH] mail/1 data: mail[markAsRead]=true

表示修补id为1的邮件记录.查询字符串是一个"过滤器",过滤从URL返回的数据.

    [GET] mail?markAsRead=true

所以这里我们要求所有已标记为已读的邮件.所以对于这条路径的[PATCH]会说"修补已经标记为真的记录"......这不是我们想要实现的目标.

所以遵循这种思路的批处理方法应该是:

    [PATCH] mail/?id=1,2,3  data: mail[markAsRead]=true

当然,我并不是说这是真正的REST(它不允许批量记录操作),而是遵循已经存在并由REST使用的逻辑.



4> justin.hughe..:

你的语言," 看起来非常浪费......",对我而言,这表明了一种过早优化的尝试.除非可以证明发送对象的整个表示是一个主要的性能损失(我们说用户不能接受> 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
            }
          ]

人们声明PATCH应该通过提供一系列更改来实现,包括:action(CRUD),路径(URL)和值更改.这可能被视为标准实现,但如果您查看整个REST API,则它是非直观的一次性.此外,上面的实现是GitHub如何实现PATCH.

总而言之,可以通过批处理操作遵循RESTful原则,并且仍然具有可接受的性能.



5> Aides..:

谷歌驱动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--

推荐阅读
sx-March23
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有