【问题标题】:具有多个唯一 ID 的 Rest API 设计
【发布时间】:2017-04-27 10:49:32
【问题描述】:

目前,我们正在为我们的系统开发一个 API,并且有些资源可能具有不同类型的标识符。 例如,有一个名为orders 的资源,它可能有一个唯一的订单号,也有一个唯一的id。目前,我们只有 id 的 URL,即这些 URL:

GET /api/orders/{id}
PUT /api/orders/{id}
DELETE /api/orders/{id}

但现在我们还需要使用订单号的可能性,这通常会导致:

GET /api/orders/{orderNumber}
PUT /api/orders/{orderNumber}
DELETE /api/orders/{orderNumber}

这显然行不通,因为 id 和 orderNumber 都是数字。

我知道有一些类似的问题,但它们并没有帮助我,因为答案并不真正适合,或者他们的方法不是很平静或难以理解(对于我们和可能使用 API 的开发人员)。此外,问题和答案部分超过 7 年。

仅举几例:

1.使用查询参数

有人建议使用查询参数,例如

GET /api/orders/?orderNumber={orderNumber}

我认为,有很多问题。首先,这是订单集合的过滤器,因此结果也应该是一个列表。但是,唯一的订单号只有一个订单,这有点令人困惑。其次,我们使用这样的过滤器来搜索/过滤订单子集。此外,查询参数是某种二等参数,但在这种情况下应该是一等的。这甚至是一个问题,如果我的对象不存在。通常,get 会返回 404(未找到),但如果订单 1234 不存在,GET /api/orders/?orderNumber=1234 将是一个空数组。

2。使用前缀

一些公共 API 使用某种鉴别器来区分不同的类型,例如喜欢:

GET /api/orders/id_1234
GET /api/orders/ordernumber_367652

这适用于他们的方法,因为id_1234ordernumber_367652 是它们真正的唯一标识符,其他资源也会返回这些标识符。但是,这会导致这样的响应对象:

{
  "id": "id_1234",
  "ordernumber": "ordernumber_367652"
  //...
}

这不是很干净,因为类型(id 或订单号)被建模了两次。除了更改所有标识符和响应对象的问题之外,如果您例如想要搜索所有大于 67363 的订单号(因此,也存在字符串/数字冲突)。如果响应没有添加类型作为前缀,用户必须为某些请求添加这个,这也会很混乱(有时你必须添加这个,有时不......)

3.使用动词

这就是例如Twitter 可以:他们的 URL 以 show.json 结尾,所以你可以像这样使用它:

GET /api/orders/show.json?id=1234 
GET /api/orders/show.json?number=367652

我认为,这是最糟糕的解决方案,因为它并不平静。此外,它还有我在查询参数方法中提到的一些问题。

4.使用子资源

有些人建议将此建模为子资源,例如:

GET /api/orders/1234 
GET /api/orders/id/1234   //optional
GET /api/orders/ordernumber/367652

我喜欢这种方法的可读性,但我认为/api/orders/ordernumber/367652 的含义是“获取(仅)订单号 367652”而不是订单。最后,这打破了一些最佳实践,例如使用复数形式和仅使用真实资源。

所以最后,我的问题是:我们错过了什么吗?还有其他方法吗,因为我认为这不是一个不寻常的问题?

【问题讨论】:

    标签: json rest http api-design


    【解决方案1】:

    因此,基本上,您希望将所有 ID 和订单号视为订单记录的唯一标识符。当然,唯一标识符的问题是它们必须是唯一的!但是您的 ID 和订单号都是数字;他们的范围重叠吗?例如,如果“1234”可以是 id 或订单号,那么显然 /api/orders/1234 不会引用唯一订单。

    如果范围唯一的,那么您只需要 /api/orders/{id} 的处理程序代码中的鉴别器逻辑,它可以从订单号中分辨出一个 id。这实际上可以工作,例如,如果您的订单号的位数比您的 ID 多。但如果可以的话,我希望你已经这样做了。

    如果范围可能重叠,那么您至少必须强制对它们的引用具有唯一范围。最简单的方法是在引用订单号时添加前缀,例如前缀“N”。因此,如果 id 为 1234 的订单的订单号为 367652,则可以通过以下任一调用来检索它:

    /api/orders/1234
    /api/orders/N367652
    

    但是,要么数据库必须更改以在所有订单号中包含“N”前缀(您说这是不可能的),否则处理程序代码必须在转换为 int 之前去掉“N”前缀。在这种情况下,“N”前缀应该在 API 调用中使用 - 面向用户的数据输入表单不应公开它!您不能有一个“通过任何标识符查找”字段,用户可以在其中输入 id 或订单号(这无论如何都会有非唯一性问题。)相反,您必须有单独的“查找方式” id”和“按订单号查找”选项。然后,您应该能够让订单号输入处理程序在提交到 API 之前自动添加“N”前缀。

    从根本上说,这是数据库设计的问题 - 如果需要(使用两个字段中的值作为“唯一标识符”),那么在设计数据库字段时应该考虑到这一点(即不重叠范围) - 如果您无法更改订单号格式,则 id 格式应该不同。

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    【解决方案2】:

    我也在为此苦苦挣扎!在我的情况下,我只需要能够使用辅助 ID 进行 GET,这使得这更容易一些。

    我倾向于对 ID 使用可选前缀:

    GET /api/orders/{id}
    GET /api/orders/id:{id}
    GET /api/orders/number:{orderNumber}
    

    或者这可能是使用 URI 规范的一个不起眼的功能 path parameters 的机会,它允许您将参数附加到特定的路径元素:

    GET /api/orders/{id}
    GET /api/orders/{id};id_type=id
    GET /api/orders/{orderNumber};id_type=number
    

    使用非限定 ID 的 URL 是规范的。非规范 URL 的行为有两种选择:要么返回实体,要么重定向到规范 URL。后者理论上更纯粹,但可能会给用户带来不便。或者它可能对用户更有用,谁知道呢!

    解决此问题的另一种方法是将订单号建模为自己的东西:

    GET /api/ordernumbers/{orderNumber}
    

    这可以返回一个只有 ID 的小对象,然后用户可以使用它来检索实体。甚至只是重定向到订单。

    如果您还想要一个通用搜索资源,那么也可以在这里使用:

    GET /api/orders?number={orderNumber}
    

    在我的情况下,我不想要这样的资源(还),我可能会觉得添加似乎只支持一个字段的通用搜索资源。

    【讨论】:

      【解决方案3】:

      我正在努力解决同样的问题,但还没有找到完美的解决方案。我最终使用了这种格式:

      GET /api/orders/{orderid}
      GET /api/orders/bynumber/{orderNumber}
      

      不完美,但可读。

      【讨论】:

      • 为了保持一致,我什至会使用 byid 按 id 检索订单。
      【解决方案4】:

      这是我想出的另一种选择,我觉得它更可口。

      GET /api/orders/1234
      GET /api/orders/1234?idType=id //optional
      GET /api/orders/367652?idType=ordernumber
      

      原因是它使路径与 REST 标准保持一致,然后在服务中,如果它们确实通过了 idType=orderNumber(idType 是默认值),您可以继续使用。

      【讨论】:

        【解决方案5】:

        对我来说,解决问题的最 RESTful 方式是使用方法 2 并稍作修改。

        从理论上讲,您只需拥有有效的识别码来识别您的订单。在设计过程的这一点上,您的识别码是 id 还是订单号并不重要。它可以唯一标识您的订单,这就足够了。

        id 和数字格式之间存在歧义这一事实属于实施阶段,而不是设计阶段。

        所以现在,我们拥有的是:

        GET /api/orders/{some_identification_code}

        这是非常 RESTful 的。

        当然,您仍然有解决歧义的问题,因此我们可以继续实施阶段。不幸的是,您的订单identification_code set 由两个共享格式的不同实体组成。这是微不足道的,它不能工作。但现在问题在于这些实体格式的定义。

        我的建议很简单:id 是整数,而数字是 N1234567 之类的代码。这种方法将使您的资源表示可以接受:

        {
          "id": "1234",
          "ordernumber": "N367652"
          //...
        }
        

        另外,在快递发货等很多场景中也很常见。

        【讨论】:

        • 谢谢!我也觉得这是最安逸的。从理论上的 POV 来看,你是对的。但是,我认为设计阶段还应该考虑参数可以/必须看起来如何以及 api 用户必须如何处理它们。他/她是否必须加一个“N”?!另一个问题是:“订单号”没有在我们的系统中创建(ERP 系统创建它们) - 所以我们无法更改它并且在我们的系统中添加一些文本(这里:“N”)会与企业范围内的唯一性冲突数字。我们目前考虑将参数名称添加到{some_identification_code},如下所示:stackoverflow.com/a/9743414/4245733
        • 如果你不习惯称其为“实施阶段”,可以称其为“二次设计阶段”。我只是指出纯粹的 API 设计和 ID 问题是分开的问题。为了理解,我做了一个强烈的区分。但是,您无法生成订单号这一事实并不意味着您无法在 API 级别进行重新映射。一件事是业务模型,另一件事是 API 模型。
        猜你喜欢
        • 2023-03-25
        • 2011-01-04
        • 2022-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        相关资源
        最近更新 更多