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

在REST风格的Web服务中对资源进行批量操作时存在哪些经过验证的设计模式?

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

有些情况下,您想要更新整个对象的单个字段。 来回发送每个对象的整个表示来更新一个字段似乎非常浪费。

在RPC风格的API中,你可以有一个方法:

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

这里的REST等价物是什么? 或者可以立即妥协。 它是否破坏了设计,增加了一些能够提高性能的特定操作? 现在所有情况下的客户端都是一个Web浏览器(客户端的JavaScript应用程序)。


一个简单的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风格批量部分更新显然要复杂得多(例如,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和Pascarello的书中提到的模式Ajax in Action(顺便提一下,这是一本很好的书 - 强烈推荐),在这本书中他们演示了如何实现一个CommandQueue类对象,它的任务是将请求排入批处理,然后定期将它们发布到服务器。

如果我没有记错的话,该对象本质上只是持有一个“命令”数组 - 例如,为了扩展您的示例,每一个记录都包含一个“markAsRead”命令,一个“messageId”以及一个对回调/处理函数的引用函数 - 然后根据某个时间表或某些用户操作,命令对象将被序列化并发布到服务器,客户端将处理后续处理。

我并没有碰巧得到这些细节,但这听起来像是一种这样的命令队列,可以解决你的问题。 它会大幅降低整体的烦琐程度,并且会以一种更为灵活的方式抽象出服务器端的界面。


更新 :啊哈! 我从网上找到了一本关于代码示例的书(尽管我仍然建议你拿起实际的书!)。 看看这里,从第5.5.3节开始:

这很容易编码,但可能会导致很多非常小的流量到服务器,这是效率低下并可能造成混淆。 如果我们想要控制流量,我们可以捕获这些更新并将它们排队在本地 ,然后在闲暇时分批发送给服务器。 清单5.13显示了一个用JavaScript实现的简单更新队列。 [...]

队列维护两个数组。 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是沿着正确的道路,但从概念上讲,我认为它应该与所建议的相反。

该网址实际上是“我们所针对的资源”,因此:

    [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 <the records we are targeting> data: mail[markAsRead]=true

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

链接地址: http://www.djcxy.com/p/48819.html

上一篇: Patterns for handling batch operations in REST web services?

下一篇: How to convert string to xml file in java