RESTful服务最佳实践——(六)

资源命名

除了适当地使用HTTP动词,在创建一个可以理解的、易于使用的Web服务API时,资源命名可以说是最具有争议的和最重要的概念。当资源被很好的命名,这个API是非常直观并且易于使用的。如果命名的不好,同样的API会感觉很笨拙并且难以使用和理解。下面是一些当你需要为你的新API创建资源URL时的小技巧。

从本质上讲,一个RESTFul API最终是简单URI的集合,HTTP调用这些URI、JSON或/和用XML表示的资源,其中许多包含相关的链接。RESTful的可寻址能力主要由URI覆盖。每个资源都有自己的地址或URI–服务器可以提供的每个有趣信息,都是作为资源来公开。统一接口的原则部分地由URI和HTTP动词的组合来解决,并符合使用标准和约定。

在决定哪些资源是在你的系统内,你需要用名词而不是用动词或操作来命名它们。换句话说,一个RESTful URI应该关联到一个作为事物的资源,而不是关联到一个动作。名词具有一些作为动词没有的属性,是另一个显著因素。

一些资源的例子:

  • 系统的使用者
  • 学生登记的课程
  • 一个用户的帖子时间轴
  • 跟在别的用户后面的用户
  • 一篇关于骑马的文章

服务套件中的每个资源至少有一个URI来标识。最佳的是,这个URI是有意义的并且充分描述了这个资源。URI应该遵循可预测的、具有层次结构来提高可理解性和可用性的:可预测的,含义是资源与名称是一致的;分层的,含义是数据具有关系上的结构。这不是REST规则或规范,但它增强了这个API。

RESTful API是写给用户的。URI的名称和结构应该传达意义给用户。通常很难知道数据边界应该是什么,但是对数据的理解上,你最有可能是有目的的去找这个名称,以及作为有意义的数据返回到客户端的表征。API设计是为了你的客户端,而不是你的数据。

假设我们描述一个订单系统包括客户、订单,行项目,产品等。考虑在这个服务中涉及的描述资源的URL:

资源URL例子

为了在系统中插入(创建)一个新的用户,我们可以使用:

POST http://www.example.com/customers

为了使用ID33245读取一个用户的信息:

GET http://www.example.com/customers/33245

相同的URI可以用于PUT和DELETE,分别更新和删除数据。

这里提出了产品的URI:

POST http://www.example.com/products

用于创建新的产品。

GET|PUT|DELETE http://www.example.com/products/66432

用于分别读取、更新、删除产品66432。

现在,有趣的地方是……如何为用户创建一个新的订单?

一种方式可以是:

POST http://www.example.com/orders

这种方式可以用来创建菜单,但它抛开了用户的背景。

因为我们想为用户创建一个菜单(记住这个关系),这个URI可能没有达到其应有的直观效果。下面这个URL可能会更清晰:

POST http://www.example.com/customers/33245/orders

现在我们知道我们是正在为ID33245的用户创建一个订单。
那以下请求返回的是什么呢?

GET http://www.example.com/customers/33245/orders

大概是一个ID为33245的用户所创建或拥有的订单列表。注意:我们可能选择不支持删除或者更新这个URL,因为它的操作对象是一个集合。

那么,继续层级这个概念,下面这个请求的URI是什么样呢?

POST http://www.example.com/customers/33245/orders/8769/lineitems

这个可能是(为了ID为33245的用户)增加一个编号为8769的订单条目。没错!对那个URI使用GET方式,返回那个订单的所有条目。然而,如果这些条目与用户信息无关,或者和用户背景之外的信息有关,我们将会提供POST www.example.com/orders/8769/lineitems这个URI。

先不考虑这些条目,由于所给的资源可能有多个URI,我们可能也要提供一个GET http://www.example.com/orders/8769 的URI,来支持在不知道用户ID的情况下,根据订单ID检索订单。

更深一层:

GET http://www.example.com/customers/33245/orders/8769/lineitems/1

可能只返回同个订单中的第一个条目。

现在你可以明白层级概念是怎样的了。他们不是明确的规则,只是确定这些强化的结构对你服务的用户是有意义的。在所有软件开发的工艺中,命名是成功的关键。

看一些广泛使用API来掌握这个窍门,利用你队友直观来提炼你的API资源URI。一些API的例子:

资源命名的反面例子

虽然我们已经讨论过一些适当的命名资源例子,但有时看到一些反面例子也是很有益的。下面是一些非常不RESTful的资源URI,看起来是很混乱的。那些是错误行为的例子!

首先,往往服务端会使用一个单一的URI来指定服务接口,使用查询串参数且/或用HTTP动词来指定这个请求操作。例如,更新ID12345的用户,这个带有JSON体的请求可能会是:

GET http://api.example.com/services?op=update_customer&id=12345&format=json

现在,你可以像上面这样做。尽管“services”的这个URL节点是一个名词,但这个URL不像URI层次一样是自描述的,不像URI层次那样对所有请求都是一样的。加上,虽然我们想要执行更新,但它使用GET当做HTTP动词。所以客户端使用这样的API,与直觉是相反的、是痛苦的(甚至是危险的)。

以下是另外一个更新用户的操作的例子:

GET http://api.example.com/update_customer/12345

以及他的邪恶分身:

GET http://api.example.com/customers/12345/update

当你在浏览其他开发者的服务套件时,会经常看到这种用法。但值得注意的是,这些开发者试图去创建RESTful的资源名称,而且已经获得了一些进步。但是你比这更进一步–能够识别出URL中的动词短语。注意,在这个URL中我们不需要用这个“update”动词短语,因为我们可以依赖HTTP 动词去告知这个操作。阐明这个观点,正如下面这个资源URL是多余的:

PUT http://api.example.com/customers/12345/update

这个请求同时存在PUT和“update”,我们会混淆我们的服务对象!“update”是指这个资源吗?所以,我们花费了一些时间去打马。我相信你能理解我的意思……

复数

让我们来讨论一下复数和“单数”的争议…没有听过这个争议点?它确实是存在的,事实上它归结为这个问题……

在你的层级中你的URI节点是否需要命名为单数或者复数名词?例如,在你检索用户资源的URI命名是否需要像下面这样:

GET http://www.example.com/customer/33245

或者:

GET http://www.example.com/customers/33245

两种方式都有好的理由来支撑,但是一般习惯做法是节点名总是使用复数命名,以使得你的API URI在所有的HTTP方法中保持一致。原因是基于这样的理念:客户在服务套件中是一个集合,而这个用户ID(例如33245)是指集合中其中的一个客户。

利用这个规则,一个多节点URI例子会是这样(强调):

GET http://www.example.com/customers/33245/orders/8769/lineitems/1

“customers”、“orders”以及“lineitems”这些URI节点都是他们的复数形式。

这意味着,你真正只需要两个基本的URL作为根资源。一个用于集合内资源的创建,第二个用来根据标识符获取、更新和删除资源。例如创建的情况,以customers为例,由以下的URL进行操作:

POST http://www.example.com/customers

以及读取、更新和删除的情况,用下面的URL操作:

GET|PUT|DELETE http://www.example.com/customers/{id}

正如前面提到的,给定的资源可能有多个URI,但作为一个最小完整增删改查功能,利用两个简单的URI来处理是恰当的。

或许你会问:是否在有些情况下复数没有意义?嗯,事实上是这样的。当没有集合概念的时候(复数没有意义)。换句话说,当资源只有一个的情况下,使用单数资源名称是可以接受的–它是一个单一的资源。例如,如果有一个单一的总体配置资源,你可以使用一个单数名词来表示,如:

GET|PUT|DELETE http://www.example.com/configuration

注意缺少配置ID的POST动词用法。以及每个用户只有一个配置,那么这个URL可以是:

GET|PUT|DELETE http://www.example.com/customers/12345/configuration

另外,注意没有配置ID的非POST动词用法。尽管如此,我相信在这两种情况下POST使用可能会是有效的。


原文如下


Resource Naming

In addition to utilizing the HTTP verbs appropriately, resource naming is arguably the most debated and most important concept to grasp when creating an understandable, easily leveraged Web service API. When resources are named well, an API is intuitive and easy to use. Done poorly, that same API can feel klutzy and be difficult to use and understand. Below are a few tips to get you going when creating the resource URIs for your new API.

Essentially, a RESTFul API ends up being simply a collection of URIs, HTTP calls to those URIs and some JSON and/or XML representations of resources, many of which will contain relational links. The RESTful principal of addressability is covered by the URIs. Each resource has its own address or URI—every interesting piece of information the server can provide is exposed as a resource. The constraint of uniform interface is partially addressed by the combination of URIs and HTTP verbs, and using them in line with the standards and conventions.

In deciding what resources are within your system, name them as nouns as opposed to verbs or actions. In other words, a RESTful URI should refer to a resource that is a thing instead of referring to an action. Nouns have properties as verbs do not, just another distinguishing factor.

Some example resources are:

  • Users of the system.
  • Courses in which a student is enrolled.
  • A user’s timeline of posts.
  • The users that follow another user.
  • An article about horseback riding.

Each resource in a service suite will have at least one URI identifying it. And it’s best when that URI makes sense and adequately describes the resource. URIs should follow a predictable, hierarchical structure to enhance understandability and, therefore, usability: predictable in the sense that they’re consistent, hierarchical in the sense that data has structure—relationships. This is not a REST rule or constraint, but it enhances the API.

RESTful APIs are written for consumers. The name and structure of URIs should convey meaning to those consumers. It’s often difficult to know what the data boundaries should be, but with understanding of your data, you most-likely are equipped to take a stab and what makes sense to return as a representation to your clients. Design for your clients, not for your data.

Let’s say we’re describing an order system with customers, orders, line items, products, etc. Consider the URIs involved in describing the resources in this service suite

Resource URI Examples

To insert (create) a new customer in the system, we might use:

POST http://www.example.com/customers

To read a customer with Customer ID# 33245:

GET http://www.example.com/customers/33245

The same URI would be used for PUT and DELETE, to update and delete, respectively.

Here are proposed URIs for products:

POST http://www.example.com/products

for creating a new product.

GET|PUT|DELETE http://www.example.com/products/66432

for reading, updating, deleting product 66432, respectively.

Now, here is where it gets fun… What about creating a new order for a customer?
One option might be:

POST http://www.example.com/orders

And that could work to create an order, but it’s arguably outside the context of a customer.

Because we want to create an order for a customer (note the relationship), this URI perhaps is not as intuitive as it could be. It could be argued that the following URI would offer better clarity:

POST http://www.example.com/customers/33245/orders

Now we know we’re creating an order for customer ID# 33245.

Now what would the following return?

GET http://www.example.com/customers/33245/orders

Probably a list of orders that customer #33245 has created or owns. Note: we may choose to not support DELETE or PUT for that url since it’s operating on a collection.

Now, to continue the hierarchical concept, what about the following URI?

POST http://www.example.com/customers/33245/orders/8769/lineitems

That might add a line item to order #8769 (which is for customer #33245). Right! GET for that URI might return all the line items for that order. However, if line items don’t make sense only in customer context or also make sense outside the context of a customer, we would offer a POST www.example.com/orders/8769/lineitems URI.

Along those lines, because there may be multiple URIs for a given resource, we might also offer a GET http://www.example.com/orders/8769 URI that supports retrieving an order by number without having to know the customer number.

To go one layer deeper in the hierarchy:

GET http://www.example.com/customers/33245/orders/8769/lineitems/1

Might return only the first line item in that same order.

By now you can see how the hierarchy concept works. There aren’t any hard and fast rules, only make sure the imposed structure makes sense to consumers of your services. As with everything in the craft of Software Development, naming is critical to success.

Look at some widely used APIs to get the hang of this and leverage the intuition of your teammates to refine your API resource URIs. Some example APIs are:

Resource Naming Anti-Patterns

While we’ve discussed some examples of appropriate resource names, sometimes it’s informative to see some anti-patterns. Below are some examples of poor RESTful resource URIs seen in the “wild.” These are examples of what not to do!

First up, often services use a single URI to specify the service interface, using query-string parameters to specify the requested operation and/or HTTP verb. For example to update customer with ID 12345, the request for a JSON body might be:

GET http://api.example.com/servicesop=update_customer&id=12345&format=json

By now, you’re above doing this. Even though the ‘services’ URL node is a noun, this URL is not selfdescriptive as the URI hierarchy is the same for all requests. Plus, it uses GET as the HTTP verb even though we’re performing an update. This is counter-intuitive and is painful (even dangerous) to use as a client.

Here’s another example following the same operation of updating a customer:

GET http://api.example.com/update_customer/12345

And its evil twin:

GET http://api.example.com/customers/12345/update

You’ll see this one a lot as you visit other developer’s service suites. Note that the developer is attempting to create RESTful resource names and has made some progress. But you’re better than this able to identify the verb phrase in the URL. Notice that we don’t need to use the ‘update’ verb phrase in the URL because we can rely on the HTTP verb to inform that operation. Just to clarify, the following resource URL is redundant:

PUT http://api.example.com/customers/12345/update

With both PUT and ‘update’ in the request, we’re offering to confuse our service consumers! Is ‘update’ the resource? So, we’ve spent some time beating the horse at this point. I’m certain you understand…

Pluralization

Let’s talk about the debate between the pluralizers and the “singularizers”… Haven’t heard of that debate? It does exist. Essentially, it boils down to this question…

Should URI nodes in your hierarchy be named using singular or plural nouns? For example, should your URI for retrieving a representation of a customer resource look like this:

GET http://www.example.com/customer/33245

or:

GET http://www.example.com/customers/33245

There are good arguments on both sides, but the commonly-accepted practice is to always use plurals in node names to keep your API URIs consistent across all HTTP methods. The reasoning is based on the concept that customers are a collection within the service suite and the ID (e.g. 33245) refers to one of those customers in the collection.

Using this rule, an example multi-node URI using pluralization would look like (emphasis added):

GET http://www.example.com/customers/33245/orders/8769/lineitems/1

with ‘customers’, ‘orders’, and ‘lineitems’ URI nodes all being their plural forms.

This implies that you only really need two base URLs for each root resource. One for creation of the resource within a collection and the second for reading, updating and deleting the resource by its identifier. For example the creation case, using customers as the example, is handled by the following URL:

POST http://www.example.com/customers

And the read, update and delete cases are handled by the following:

GET|PUT|DELETE http://www.example.com/customers/{id}

As mentioned earlier, there may be multiple URIs for a given resource, but as a minimum full CRUD capabilities are aptly handled with two simple URIs.

You ask if there is a case where pluralization doesn’t make sense. Well, yes, in fact there is. When there isn’t a collection concept in play. In other words, it’s acceptable to use a singularized resource name when there can only be one of the resource—it’s a singleton resource. For example, if there was a single, overarching configuration resource, you might use a singularized noun to represent that:

GET|PUT|DELETE http://www.example.com/configuration

Note the lack of a configuration ID and usage of POST verb. And say that there was only one configuration per customer, then the URL might be:

GET|PUT|DELETE http://www.example.com/customers/12345/configuration

Again, no ID for the configuration and no POST verb usage. Although, I’m sure that in both of these cases POST usage might be argued to be valid. Well… OK.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值