RESTful URI 的设计

今天开始来一起学习 RESTful API 的设计。

我认为在构建一个 API 时要先进行设计这是很重要的。这应该是创建新 API 的第一步,而不应该拿了需求直接就开始写码。这也是初级和高级程序员的区别,很多公司觉得这种初级程序员给了需求立刻开工,这能力杠杠的,其实恰恰相反,没有好的设计,日后的维护和扩展将变得相当困难。万丈高楼平地起,磨刀不误砍材工。通过设计 API,你将获得一些真正的好处。还有一个最大的问题是发布了 API 后就很难再去修复。因为你将拥有依赖于 API 的客户端了。这就好像泼出去的水一样,是收不回来的了。

因此,如果你在设计 API 的方式上犯了错误,那么你将不得不忍受,除非你当然可以说服你的客户允许你修改。尝试通过添加临时端点来等方法来解决单个用例。但是我真的希望你在构建 API 之前先了解它的需求,以及日后可能的需求,而不仅仅是了解你在其他公司或者网上所看到的样板,然后就照猫画虎。

就好像 Quora 有一个很有意思的提问,那就是程序员既然可以使用 Stack Overflow 这个网站来复制粘贴,那为什么薪水还这么高呢?

有一个斯坦福博士的回答是:因为好的程序员知道这个代码要贴在哪里。你想一下,并非所有的 API 都只是数据访问,你可能还需要 API 做一些其他的事情。

请记住,在构建 API 时,这就是该 API 生效的起点。随着它的成熟和构建,你希望 API 不必以较大的方式进行更改,因为它可以随着时间的推移而不断发展。

扯淡结束,让我们开始看一下真正的问题。

当我们考虑 REST API 时,我希望你考虑一下它的不同部分,然后再决定在每个部分中应该放置什么。需要注意的一点是,你设计的是 REST API 的每个部分,而不仅仅是 URL。当然,起点是 URI,即某个 Web 服务器上的路径。但是我们也有动词。这表明你打算在该 URI 上执行什么操作,然后正如我们在使用 HTTP 所讨论的那样,该 REST API 的一部分是你是否要使用标头,客户端是否需要使用标头,使用什么标头,然后是请求正文(如果有)。

RESTful URI 的设计

当我们查看发送回的响应时。其中有状态码(发送回什么样的响应,以表示成功或失败,或者为什么失败)。你可以将头发送回客户端,然后返回正文也就是请求要求的东东。

RESTful URI 的设计

让我们从 URI 开始讨论。在 REST 中,URI 只是资源的路径。因此,当你拥有服务器时,就会有一个 API,它就是服务器名称后的任何路径,以指示如何获取该服务器上的某个对象。比如 api.diaozhatian.com/people,在设计 API 时我们不会考虑的 URI 的一部分通常是查询字符串。

因此,可以将其添加到 URI 末尾。当从逻辑上考虑查询字符串时,查询字符串应该始终是可选的。它们通常用于格式化,排序和搜索之类的事情。因此,在设计 API 时,我希望你考虑名词。这点很重要,一定要记住。名词是大大的好,动词是大大的不好。这里有一些例子。在我构建 API 的早期,我很容易将 API 视为只是一些远程过程调用的端点。因此,下面这些例子,看上去都是有道理的。

RESTful URI 的设计

但是在 REST API 中,我们实际更喜欢名词或者说更应该使用名词。比如下面这样。

RESTful URI 的设计

因此,在我们的案例中,这些客户、收据、产品、员工等等,它们可以被看做是存放用户可能想要或不想要的资源的容器,这些将保存开发人员想要访问的资源。在大多数情况下,除非你只授予某人访问单个项目的权限,否则它们始终将是多元的。不同之处在于你可以将客户视为完整的客户列表。从表面上看,我们可以将其视为这些名词的集合。

销售的客户、会计系统中存在的发票、通过网络进行的付款、可以出售的产品,这些就是我认为的规范资源。这些是你希望通过 API 公开的名词,但它可能比这更复杂,因为很容易将资源视为只是某些数据存储所拥有的实体。我希望你将资源视为位于上下文之内的资源。因此,个人可能是一种资源。

但是你也可以想象资源是发票,而发票则更为复杂。你可能知道发票本身,然后是发票中包含的所有项目。因此,我们正在考虑的这个复杂对象仍被视为单个资源。当然你不一定需要公开每个个性化的 API。

想像在一个图书保管库中,关于一本书的标题信息,作者,多少页等本身就是一种资源,即使你可以单独查看该书的内容。我们正在谈论的是你通过 API 公开的复杂对象,有时甚至是单个对象。在我们的案例中,我们实际上要处理我们将要构建的 API 中的三个对象。

RESTful URI 的设计

我们来一起使用一个网上的 API,该 API 是用于公开一些有关联合国教科文组织遗产的数据,就是用于教学和练习使用的。

我们的其中一个对象是有关信息的站点,该站点具有描述、ID 和名称。在我们的案例中,还需要知道网站及其位置,位置就是所在的国家/地区,所在的经度和纬度,这是一种离散资源。如果没有位置,你将无法真正拥有一个网站。地点在某种程度上被认为是该对象的子对象,但在 API 中,根据上下文,这也是一种对象。右边的种类和区域也可以被视为资源。

当我们谈论 URI 中的标识符时,我们谈论的是能够访问资源集合中的各个项目。因此,这些必须是唯一的标识符,因为在 URI 中,我们希望每个单独的 URI 点都可以定位到特定的资源上。你并不总是要进行搜索,而是通常会有一些 URI 来唯一标识系统中某处的一种资源。这些不必是主键,因此也不必是 GUID(如果这是你存储它的方式),整数或任何其他形式。看一下我们的例子:

RESTful URI 的设计

网站将有一条通向网页里各个资源的路径,我们可以使用数字表示主键,使用它表示我们正在寻找的路径,或者我们可以像代理键一样使用某些东西可以来识别单个站点,但不一定要公开我们对数据存储的实现。

与 uk-101 相同,也许某些标识符可以为你提供足够的信息,以便在其后面的系统中找到它。因此,stone-henge 的 URI 本身应该是唯一的,但你为唯一标识符设计的用于唯一标识它的方法取决于你。通常你在 URI 中离主键或其他种类的键越远,你的 REST API 的设计就越好。

同样,如果我们回顾 GitHub 的方式,它会在整个系统中使用唯一的标识符来表示个人、个人存储库等。它永远不会只是为你提供该对象的编号或唯一的 uuid 编号。这使它更容易知道哪些标识符是唯一的,并且使开发人员更容易仅从 URI 看到他们实际在看什么。因为最终 URI 尽管只是用于某些资源,所以在某些时候开发人员正在寻找并使用这些 URI。你不希望它们一定会在可读性方面出错,但是,如果你能兼得两者,那么就可以拥有可读性以及功能上的独特优势。 在设计 URI 时,你还想考虑如何使用查询字符串。这些通常用于非资源属性。

RESTful URI 的设计

这可能是你希望使用查询字符串进行排序,分页,指定格式等等。你实际上不需要这些,但这样允许开发人员对操作方式进行更多的控制。你可以通过获取页​​面结果与排序结果等方式来获取此信息,以便你可以让他们执行,这个执行或者调用不一定要指定如何构成资源,而是要指定如何返回资源。

URI 的设计实战

现在,让我们看看如何在设计中使用 URI。让我们打开 Postman,我们将继续使用 arest.me 网站。同样,这是一个互联网上的站点,你当然可以像我一样使用 postman 来进行交互,因为我们正在处理联合国教科文组织遗产站点,我决定将 API 的第一部分设计为 sites,你可以把它定义得更长一些。

现在,使用以 API 开头的 URI 的想法很普遍。你可以使服务器以 api.arest.me 开头,也可以将其包含在 URI 中。我认为这两种情况各有好坏,所以也就无所谓了。我经常将其作为 URI 的一部分,因为有时会在单个服务器下公开网站和 API。但是,如果你将 API 作为单独的机器构建,那么对我来说,将其用作服务器地址的一部分是完全可以的。

我们将使用第一个动词-GET。我们后面会讨论更多地动词。但是这里的想法是我想获得一个站点列表。还记得当我们执行 arest.me 时,我们只得到了一些 HTML。但是在这种情况下,如果我们转到该 API,我们将实际上只返回一些表示它的 JSON。

{
        "id": 1068,
        "name": "<I>Sacri Monti</I> of Piedmont and Lombardy",
        "yearInscribed": 2003,
        "url": "https://whc.unesco.org/en/list/1068",
        "imageUrl": "https://www.zadmei.com/wp-content/uploads/2022/09/1662390162-5c9ce507725e76b.jpg",
        "descriptionMarkup": "<p>The nine <em>Sacri Monti</em> (Sacred Mountains) of northern Italy are groups of chapels and other architectural features created in the late 16th and 17th centuries and dedicated to different aspects of the Christian faith. In addition to their symbolic spiritual meaning, they are of great beauty by virtue of the skill with which they have been integrated into the surrounding natural landscape of hills, forests and lakes. They also house much important artistic material in the form of wall paintings and statuary.</p>",
        "states": "Italy",
        "location": {
            "name": "Regions of Lombardy and Piedmont",
            "longitude": 9.169555556,
            "latitude": 9.169555556
        },
        "categoryName": "Cultural",
        "regionName": "Europe and North America"
    },
    {
        "id": 1583,
        "name": "<I>TEST-- eden</I> of Piedmont and Lombardy",
        "yearInscribed": 2003,
        "url": "https://whc.unesco.org/en/list/1068",
        "imageUrl": "https://www.zadmei.com/wp-content/uploads/2022/09/1662390162-5c9ce507725e76b.jpg",
        "descriptionMarkup": "<p>The nine <em>Sacri Monti</em> (Sacred Mountains) of northern Italy are groups of chapels and other architectural features created in the late 16th and 17th centuries and dedicated to different aspects of the Christian faith. In addition to their symbolic spiritual meaning, they are of great beauty by virtue of the skill with which they have been integrated into the surrounding natural landscape of hills, forests and lakes. They also house much important artistic material in the form of wall paintings and statuary.</p>",
        "states": "USA",
        "location": {
            "name": "eden dva",
            "longitude": 9.169555556,
            "latitude": 9.169555556
        },
        "categoryName": "Cultural",
        "regionName": "Europe and North America"
    },

当然这只是一部分。服务器实现将 JSON 作为默认值发送,尽管由于 Postman 的工作方式,而且似乎在大多数情况下都需要 JSON,实际上它会抛出一个临时标头,说我们将接受任何类型的数据。因此,accept 标头是要发送的东西,因为它通常必须由 Postman 为你发送,作为 GET 的一部分发送。如果你调用这个 API 的话,你会发现,这个 API 返回大量的信息。这并不是我们真正想要的。如果我们在此处查看 id,则可以在 URI 的路径中迈出另一步,仅使用这些 id 中的一个继续获取单个元素即可。比如我上面例子中的一个 id 1208。

http://arest.me/api/sites/1208

RESTful URI 的设计

这是我们保证此 URI 对于此单独的联合国教科文组织遗产而言是唯一的。无论我们执行同一请求多少次,我们都将始终获得同一站点。如果我们继续在这里输入另一个数字,例如 1207,那将使我们指向另一个数字。

RESTful URI 的设计

因此,你应该考虑 URI 的这一部分对于一种资源而言确实是唯一的,这就是统一资源标识符背后的全部思想。单词标识符是该首字母缩写词的重要组成部分,因为它标识的是网络上某个位置的资源,该资源表示某个位置的单个资源,并且该资源将位于你的 API 中,指向存在的某种资源。Get 得到的永远是这个资源,而且是不会变的。