我喜欢一些一些帮助处理一个奇怪的边缘情况与分页API我正在建设。

与许多api一样,这个api也会分页较大的结果。如果你查询/foos,你会得到100个结果(即foo #1-100),和一个链接到/foos?Page =2,返回foo #101-200。

不幸的是,如果在API使用者进行下一次查询之前从数据集中删除了foo #10, /foos?Page =2将偏移100并返回foos #102-201。

这对于试图获取所有foo的API使用者来说是一个问题——他们不会收到foo #101。

处理这种情况的最佳实践是什么?我们希望使它尽可能的轻量级(即避免为API请求处理会话)。来自其他api的示例将非常感谢!


当前回答

我对此进行了长时间的思考,最终得出了下面我将描述的解决方案。这在复杂性上是一个相当大的进步,但如果你确实迈出了这一步,你最终会得到你真正想要的,这是未来请求的确定性结果。

你所举的项目被删除的例子只是冰山一角。如果您正在通过颜色=蓝色进行过滤,但有人在请求之间更改了项目的颜色,该怎么办?以分页方式可靠地获取所有项目是不可能的…除非…我们实现修订历史。

我已经实现了它,实际上它比我想象的要简单。以下是我所做的:

I created a single table changelogs with an auto-increment ID column My entities have an id field, but this is not the primary key The entities have a changeId field which is both the primary key as well as a foreign key to changelogs. Whenever a user creates, updates or deletes a record, the system inserts a new record in changelogs, grabs the id and assigns it to a new version of the entity, which it then inserts in the DB My queries select the maximum changeId (grouped by id) and self-join that to get the most recent versions of all records. Filters are applied to the most recent records A state field keeps track of whether an item is deleted The max changeId is returned to the client and added as a query parameter in subsequent requests Because only new changes are created, every single changeId represents a unique snapshot of the underlying data at the moment the change was created. This means that you can cache the results of requests that have the parameter changeId in them forever. The results will never expire because they will never change. This also opens up exciting feature such as rollback / revert, synching client cache etc. Any features that benefit from change history.

其他回答

我不完全确定您的数据是如何处理的,因此这可能有效,也可能无效,但是您是否考虑过使用时间戳字段进行分页?

当你查询/foos时,你会得到100个结果。你的API应该返回如下内容(假设是JSON,但如果它需要XML,也可以遵循相同的原则):

{
    "data" : [
        {  data item 1 with all relevant fields    },
        {  data item 2   },
        ...
        {  data item 100 }
    ],
    "paging":  {
        "previous":  "http://api.example.com/foo?since=TIMESTAMP1" 
        "next":  "http://api.example.com/foo?since=TIMESTAMP2"
    }

}

只是一个注释,只使用一个时间戳依赖于结果中的隐式“限制”。您可能希望添加显式限制,或者也使用until属性。

时间戳可以使用列表中的最后一个数据项动态确定。这似乎或多或少是Facebook在其Graph API中的分页方式(向下滚动到底部,以我上面给出的格式查看分页链接)。

一个问题可能是,如果您添加了一个数据项,但根据您的描述,听起来它们将被添加到最后(如果没有,请告诉我,我将看看是否可以改进这一点)。

如果你有分页,你也可以按键对数据排序。为什么不让API客户端在URL中包含之前返回的集合的最后一个元素的键,并在SQL查询中添加一个WHERE子句(或者其他等价的东西,如果您不使用SQL),以便它只返回那些键大于此值的元素呢?

你有几个问题。

首先,你有你引用的例子。

如果插入行,也会遇到类似的问题,但在这种情况下,用户获得重复的数据(可以说比丢失数据更容易管理,但仍然是一个问题)。

如果您没有对原始数据集进行快照,那么这就是现实。

你可以让用户创建一个显式快照:

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

结果:

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

然后你可以一整天都在上面分页,因为它现在是静态的。这可以是相当轻的重量,因为您可以只捕获实际的文档键,而不是整个行。

如果用例只是你的用户想要(并且需要)所有的数据,那么你可以简单地给他们:

GET /query/12345?all=true

把全套装备都寄过来。

再补充一下Kamilk的回答:https://www.stackoverflow.com/a/13905589

这在很大程度上取决于你处理的数据集有多大。小型数据集确实可以有效地进行偏移分页,但大型实时数据集确实需要游标分页。 找到了一篇精彩的文章,关于Slack如何随着数据集的增加而进化其api的分页,解释了每个阶段的积极和消极因素:https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12

可能很难找到最佳实践,因为大多数带有api的系统都不适应这种情况,因为这是一个极端的优势,或者它们通常不会删除记录(Facebook, Twitter)。Facebook实际上表示,由于分页后进行了过滤,每个“页面”可能没有请求的结果数量。 https://developers.facebook.com/blog/post/478/

如果你真的需要适应这种边缘情况,你需要“记住”你停止的地方。jandjorgensen的建议是正确的,但我将使用保证唯一的字段,如主键。您可能需要使用多个字段。

按照Facebook的流程,您可以(也应该)缓存已经请求的页面,如果它们请求已经请求过的页面,则只返回已删除的行。