简体   繁体   English

具有多个唯一 ID 的 Rest API 设计

[英]Rest API design with multiple unique ids

Currently, we are developing an API for our system and there are some resources that may have different kinds of identifiers.目前,我们正在为我们的系统开发 API,有些资源可能具有不同类型的标识符。 For example, there is a resource called orders , which may have an unique order number and also have an unique id.例如,有一个名为orders的资源,它可能有一个唯一的订单号,也有一个唯一的 id。 At the moment, we only have URLs for the id, which are these URLs:目前,我们只有 id 的 URL,它们是这些 URL:

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

But now we need also the possibility to use order numbers, which normally would result into:但现在我们还需要使用订单号的可能性,这通常会导致:

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

Obviously that won't work, since id and orderNumber are both numbers.显然这行不通,因为 id 和 orderNumber 都是数字。

I know that there are some similar questions, but they don't help me out, because the answers don't really fit or their approaches are not really restful or comprehensible (for us and for possible developers using the API).我知道有一些类似的问题,但它们并没有帮助我解决问题,因为答案并不合适,或者他们的方法不是很安静或难以理解(对于我们和可能使用 API 的开发人员)。 Additionally, the questions and answers are partially older than 7 years.此外,问题和答案部分超过 7 年。

To name a few:仅举几例:

1. Using a query param 1. 使用查询参数

One suggests to use a query param, eg有人建议使用查询参数,例如

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

I think, there are a lot of problems.我认为,有很多问题。 First, this is a filter on the orders collections, so that the result should be a list as well.首先,这是对订单集合的过滤器,因此结果也应该是一个列表。 However, there is only one order for the unique order number which is a little bit confusing.但是,唯一的订单号只有一个订单,这有点令人困惑。 Secondly, we use such a filter to search/filter for a subset of orders.其次,我们使用这样的过滤器来搜索/过滤订单子集。 Additionally, a query params is some kind of a second-class parameter, but should be first-class in this case.此外,查询参数是某种二等参数,但在这种情况下应该是一等参数。 This is even a problem, if I the object does not exist.这甚至是一个问题,如果我的对象不存在。 Normally a get would return a 404 (not found), but a GET /api/orders/?orderNumber=1234 would be an empty array, if the order 1234 does not exist.通常,get 将返回 404(未找到),但如果订单 1234 不存在,则GET /api/orders/?orderNumber=1234将是一个空数组。

2. Using a prefix 2. 使用前缀

Some public APIs use some kind of a discriminator to distinguish between different types, eg like:一些公共 API 使用某种鉴别器来区分不同的类型,例如:

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

This works for their approach, because id_1234 and ordernumber_367652 are their real unique identifiers that are also returned by other resources.这适用于他们的方法,因为id_1234ordernumber_367652是他们真正的唯一标识符,也由其他资源返回。 However, that would result in a response object like this:然而,这会导致一个像这样的响应对象:

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

This is not very clean, because the type (id or order number) is modelled twice.这不是很干净,因为类型(id 或订单号)被建模了两次。 And apart from the problem of changing all identifiers and response objects, this would be confusing, if you eg want to search for all order numbers greater than 67363 (thus, there is also a string/number clash).除了更改所有标识符和响应对象的问题之外,如果您例如想要搜索所有大于 67363 的订单号(因此,也存在字符串/数字冲突),这将是令人困惑的。 If the response does not add the type as a prefix, a user have to add this for some request, which would also be very confusing (sometime you have to add this and sometimes not...)如果响应没有添加类型作为前缀,用户必须为某些请求添加这个,这也会很混乱(有时你必须添加这个,有时不......)

3. Using a verb 3. 使用动词

This is what eg Twitter does: their URL ends with show.json , so you can use it like:这就是例如 Twitter 所做的:他们的 URL 以show.json ,所以你可以像这样使用它:

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

I think, this is the most awful solution, since it is not restful.我认为,这是最糟糕的解决方案,因为它并不安宁。 Furthermore, it has some of the problems that I mentioned in the query param approach.此外,它还存在我在查询参数方法中提到的一些问题。

4. Using a subresource 4. 使用子资源

Some people suggest to model this like a subresource, eg:有些人建议将其建模为子资源,例如:

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

I like the readability of this approach, but I think the meaning of /api/orders/ordernumber/367652 would be "get (just) the order number 367652" and not the order.我喜欢这种方法的可读性,但我认为/api/orders/ordernumber/367652是“获取(仅)订单号 367652”而不是订单。 Finally, this breaks some best practices like using plurals and only real resources.最后,这打破了一些最佳实践,例如使用复数和仅使用真实资源。

So finally, my questions are: Did we missed something?所以最后,我的问题是:我们错过了什么吗? And are there are other approaches, because I think that this is not an unusual problem?还有其他方法吗,因为我认为这不是一个不寻常的问题?

to me, the most RESTful way of solving your problem is using the approach number 2 with a slight modification.对我来说,解决问题的最 RESTful 方法是使用方法 2 稍加修改。

From a theoretical point of view, you just have valid identification code to identify your order.从理论上讲,您只需拥有有效的识别码来识别您的订单。 At this point of the design process, it isn't important whether your identification code is an id or an order number.在设计过程的这一点上,您的识别码是 ID 还是订单号并不重要。 It's something that uniquely identify your order and that's enough.这是唯一标识您的订单的东西,这就足够了。

The fact that you have an ambiguity between ids and numbers format is an issue belonging to the implementation phase, not the design phase.您在 id 和数字格式之间存在歧义的事实是属于实施阶段的问题,而不是设计阶段。

So for now, what we have is:所以现在,我们所拥有的是:

GET /api/orders/{some_identification_code}

and this is very RESTful.这是非常 RESTful 的。

Of course you still have the problem of solving your ambiguity, so we can proceed with the implementation phase.当然,您仍然有解决歧义的问题,因此我们可以继续实施阶段。 Unfortunately your order identification_code set is made of two distinct entities that share the format.不幸的是,您的订单identification_code集由两个共享格式的不同实体组成。 It's trivial it can't work.这是微不足道的,它无法工作。 But now the problem is in the definition of these entity formats.但现在问题出在这些实体格式的定义上。

My suggestion is very simple: ids will be integers, while numbers will be codes such as N1234567.我的建议很简单:id 将是整数,而数字将是代码,例如 N1234567。 This approach will make your resource representation acceptable:这种方法将使您的资源表示可以接受:

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

Additionally, it is common in many scenarios such as courier shipments.此外,它在许多场景中很常见,例如快递运输。

Here is an alternate option that I came up with that I found slightly more palatable.这是我想出的另一种选择,我觉得它更可口。

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

The reason being it keeps the pathing consistent with REST standards, and then in the service if they did pass idType=orderNumber (idType of id is the default) you can pick up on that.原因是它使路径与 REST 标准保持一致,然后在服务中,如果他们确实通过了 idType=orderNumber(id 的 idType 是默认值),您可以选择它。

I'm struggling with the same issue and haven't found a perfect solution.我正在为同样的问题而苦苦挣扎,但还没有找到完美的解决方案。 I ended up using this format:我最终使用了这种格式:

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

Not perfect, but it is readable.不完美,但它是可读的。

I'm also struggling with this!我也在纠结这个! In my case, i only really need to be able to GET using the secondary ID, which makes this a little easier.就我而言,我只需要能够使用辅助 ID 进行 GET,这使得这更容易一些。

I am leaning towards using an optional prefix to the ID:我倾向于使用可选的 ID 前缀:

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

or this could be a chance to use an obscure feature of the URI specification, path parameters , which let you attach parameters to particular path elements:或者这可能是使用 URI 规范的一个晦涩的特性path parameters 的机会,它允许您将参数附加到特定的路径元素:

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

The URL using an unqualified ID is the canonical one.使用非限定 ID 的 URL 是规范的 URL。 There are two options for the behaviour of non-canonical URLs: either return the entity, or redirect to the canonical URL.非规范 URL 的行为有两个选项:返回实体或重定向到规范 URL。 The latter is more theoretically pure, but it may be inconvenient for users.后者在理论上更纯粹,但对用户来说可能不方便。 Or it may be more useful for users, who knows!或者它可能对用户更有用,谁知道呢!

Another way to approach this is to model an order number as its own thing:解决此问题的另一种方法是将订单号建模为自己的东西:

GET /api/ordernumbers/{orderNumber}

This could return a small object with just the ID, which users could then use to retrieve the entity.这可以返回一个只有 ID 的小对象,然后用户可以使用它来检索实体。 Or even just redirect to the order.或者甚至只是重定向到订单。

If you also want a general search resource, then that can also be used here:如果您还需要通用搜索资源,也可以在此处使用:

GET /api/orders?number={orderNumber}

In my case, i don't want such a resource (yet), and i could be uncomfortable adding what appears to be a general search resource that only supports one field.就我而言,我(尚)不想要这样的资源,并且添加看似仅支持一个字段的通用搜索资源可能会让我感到不舒服。

So basically, you want to treat all ids and order numbers as unique identifiers for the order records.所以基本上,您希望将所有 ID 和订单号视为订单记录的唯一标识符。 The thing about unique identifiers is, of course, they have to be unique!当然,关于唯一标识符的事情是,它们必须是唯一的! But your ids and order numbers are all numeric;但是您的 ID 和订单号都是数字; do their ranges overlap?他们的范围重叠吗? If, say, "1234" could be either an id or an order number, then obviously /api/orders/1234 is not going to reference a unique order.例如,如果“1234”可以是 id 或订单号,那么显然 /api/orders/1234 不会引用唯一的订单。

If the ranges are unique, then you just need discriminator logic in the handler code for /api/orders/{id}, that can tell an id from an order number.如果范围唯一的,那么您只需要在 /api/orders/{id} 的处理程序代码中使用鉴别器逻辑,它可以从订单号中分辨出 id。 This could actually work, say if your order numbers have more digits than your ids ever will.这实际上是可行的,比如说,如果您的订单号的位数比您的 ID 多。 But I expect you would have done this already if you could.但我希望如果可以的话,你早就这样做了。

If the ranges might overlap, then you must at least force the references to them to have unique ranges.如果范围可能重叠,那么您至少必须强制对它们的引用具有唯一的范围。 The simplest way would be to add a prefix when referring to an order number, eg the prefix "N".最简单的方法是在引用订单号时添加前缀,例如前缀“N”。 So that if the order with id 1234 has order number 367652, it could be retrieved with either of these calls:因此,如果 id 为 1234 的订单的订单号为 367652,则可以通过以下任一调用进行检索:

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

But then, either the database must change to include the "N" prefix in all order numbers (you say this is not possible) or else the handler code would have to strip off the "N" prefix before converting to int.但是,数据库必须更改以在所有订单号中包含“N”前缀(您说这是不可能的),否则处理程序代码必须在转换为 int 之前去除“N”前缀。 In that case, the "N" prefix should only be used in the API calls - user facing data-entry forms should not expose it !在这种情况下,“N”前缀应该用在 API 调用中——面向用户的数据输入表单不应该暴露它 You can't have a "lookup by any identifier" field where users can enter either id or order number (this would have a non-uniqueness problem anyway.) Instead, you must have separate "lookup by id" and "lookup by order number" options.你不能有一个“通过任何标识符查找”字段,用户可以在其中输入 id 或订单号(无论如何这都会有非唯一性问题。)相反,你必须有单独的“按 id 查找”和“按订单查找”号码”选项。 Then, you should be able to have the order number input handler automatically add the "N" prefix before submitting to the API.然后,您应该能够让订单号输入处理程序在提交到 API 之前自动添加“N”前缀。

Fundamentally, this is a problem with the database design - if this (using values from both fields as "unique identifiers") was a requirement, then the database fields should have been designed with this in mind (ie with non-overlapping ranges) - if you can't change the order number format, then the id format should have been different.从根本上说,这是数据库设计的一个问题——如果这是一个要求(使用来自两个字段的值作为“唯一标识符”),那么数据库字段的设计应该考虑到这一点(即具有非重叠范围)——如果您无法更改订单号格式,则 id 格式应该有所不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM