好的RESTful API的设计原则

转载自一位大佬html

英文原版

Principles of good RESTful API Design

Good API design is hard! An API represents a contract between you and those who Consume your data. Breaking this contract will result in many angry emails, and a slew of sad users with mobile apps which no longer work. Documentation is half the battle, and it is very difficult to find programmer who also likes to write.程序员

Building an API is one of the most important things you can do to increase the value of your service. By having an API, your service / core application has the potential to become a platform from which other services grow. Look at the current huge tech companies: Facebook, Twitter, Google, GitHub, Amazon, Netflix… None of them would be nearly as big as they are today if they hadn’t opened up their data via API. In fact, an entire industry exists with the sole purpose of consuming data provided by said platforms.web

The easier your API is to consume, the more people that will consume it.数据库

The principles of this document, if followed closely when designing your API, will ensure that Consumers of your API will be able to understand what is going on, and should drastically reduce the number of confused and/or angry emails you receive. I’ve organized everything into topics, which don’t necessarily need to be read in order.json

Definitions

Here’s a few of the important terms I will use throughout the course of this document:api

  • Resource: A single instance of an object. For example, an animal.
  • Collection: A collection of homogeneous objects. For example, animals.
  • HTTP: A protocol for communicating over a network.
  • Consumer: A client computer application capable of making HTTP requests.
  • Third Party Developer: A developer not a part of your project but who wishes to consume your data.
  • Server: An HTTP server/application accessible from a Consumer over a network.
  • Endpoint: An API URL on a Server which represents either a Resource or an entire Collection.
  • Idempotent: Side-effect free, can happen multiple times without penalty.
  • URL Segment: A slash-separated piece of information in the URL.

Data Design and Abstraction

Planning how your API will look begins earlier than you’d think; first you need to decide how your data will be designed and how your core service / application will work. If you’re doing API First Development this should be easy. If you’re attaching an API to an existing project, you may need to provide more abstraction.浏览器

Occasionally, a Collection can represent a database table, and a Resource can represent a row within that table. However, this is not the usual case. In fact, your API should abstract away as much of your data and business logic as possible. It is very important that you don’t overwhelm Third-Party Developers with any complex application data, if you do they won’t want to use your API.缓存

There are also many parts of your service which you SHOULD NOT expose via API at all. A common example is that many APIs will not allow third parties to create users.服务器

Verbs

Surely you know about GET and POST requests. These are the two most commonly requests used when your browser visits different webpages. The term POST is so popular that it has even invaded common language, where people who know nothing about how the Internet works do know they can “post” something on a friends Facebook wall.网络

There are four and a half very important HTTP verbs that you need to know about. I say “and a half”, because the PATCH verb is very similar to the PUT verb, and two two are often combined by many an API developer. Here are the verbs, and next to them are their associated database call (I’m assuming most people reading this know more about writing to a database than designing an API).

  • GET (SELECT): Retrieve a specific Resource from the Server, or a listing of Resources.
  • POST (CREATE): Create a new Resource on the Server.
  • PUT (UPDATE): Update a Resource on the Server, providing the entire Resource.
  • PATCH (UPDATE): Update a Resource on the Server, providing only changed attributes.
  • DELETE (DELETE): Remove a Resource from the Server.

Here are two lesser known HTTP verbs:

  • HEAD – Retrieve meta data about a Resource, such as a hash of the data or when it was last updated.
  • OPTIONS – Retrieve information about what the Consumer is allowed to do with the Resource.

A good RESTful API will make use of the four and a half HTTP verbs for allowing third parties to interact with its data, and will never include actions / verbs as URL segments.

Typically, GET requests can be cached (and often are!) Browsers, for example, will cache GET requests (depending on cache headers), and will go as far as prompt the user if they attempt to POST for a second time. A HEAD request is basically a GET without the response body, and can be cached as well.

Versioning

No matter what you are building, no matter how much planning you do beforehand, your core application is going to change, your data relationships will change, attributes will invariably be added and removed from your Resources. This is just how software development works, and is especially true if your project is alive and used by many people (which is likely the case if you’re building an API).

Remember than an API is a published contract between a Server and a Consumer. If you make changes to the Servers API and these changes break backwards compatibility, you will break things for your Consumer and they will resent you for it. Do it enough, and they will leave. To ensure your application evolves AND you keep your Consumers happy, you need to occasionally introduce new versions of the API while still allowing old versions to be accessible.

As a side note, if you are simply ADDING new features to your API, such as new attributes on a Resource (which are not required and the Resource will function without), or if you are ADDING new Endpoints, you do not need to increment your API version number since these changes do not break backwards compatibility. You will want to update your API Documentation (your Contract), of course.

Over time you can deprecate old versions of the API. To deprecate a feature doesn’t mean to shut if off or diminish the quality of it, but to tell Consumers of your API that the older version will be removed on a specific date and that they should upgrade to a newer version.

A good RESTful API will keep track of the version in the URL. The other most common solution is to put a version number in a request header, but after working with many different Third Party Developers, I can tell you that adding headers is no where near as easy as adding a URL Segment.

Analytics

Keep track of the version/endpoints of your API being used by Consumers. This can be as simple as incrementing an integer in a database each time a request is made. There are many reasons that keeping track of API Analytics is a good idea, for example, the most commonly used API calls should be made efficient.

For the purposes of building an API which Third Party Developers will love, the most important thing is that when you do deprecate a version of your API, you can actually contact developers using deprecated API features. This is the perfect way to remind them to upgrade before you kill the old API version.

The process of Third Party Developer notification can be automated, e.g. mail the developer every time 10,000 requests to a deprecated feature are made.

API Root URL

The root location of your API is important, believe it or not. When a developer (read as code archaeologist) inherits an old project using your API and needs to build new features, they may not know about your service at all. Perhaps all they know is a list of URLs which the Consumer calls out to. It’s important that the root entry point into your API is as simple as possible, as a long complex URL will appear daunting and can turn developers away.

Here are two common URL Roots:

  • https://example.org/api/v1/*
  • https://api.example.com/v1/*

If your application is huge, or you anticipate it becoming huge, putting the API on its own subdomain (e.g. api.) is a good choice. This can allow for some more flexible scalability down the road.

  • https://example.org/api/v1/*
  • https://api.example.com/v1/*

If you anticipate your API will never grow to be that large, or you want a much simpler application setup (e.g. you want to host the website AND API from the same framework), placing your API beneath a URL segment at the root of the domain (e.g. /api/) works as well.

It’s a good idea to have content at the root of your API. Hitting the root of GitHub’s API returns a listing of endpoints, for example. Personally, I’m a fan of having the root URL give information which a lost developer would find useful, e.g., how to get to the developer documentation for the API.

Also, notice the HTTPS prefix. As a good RESTful API, you must host your API behind HTTPS.

Endpoints

An Endpoint is a URL wi个thin your API which points to a specific Resource or a Collection of Resources.

If you were building a fictional API to represent several different Zoo’s, each containing many Animals (with an animal belonging to exactly one Zoo), employees (who can work at multiple zoos) and keeping track of the species of each animal, you might have the following endpoints:

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/animal_types
  • https://api.example.com/v1/employees

When referring to what each endpoint can do, you’ll want to list valid HTTP Verb and Endpoint combinations. For example, here’s a semi-comprehensive list of actions one can perform with our fictional API. Notice that I’ve preceded each endpoint with the HTTP Verb, as this is the same notation used within an HTTP Request header.

  • GET /zoos: List all Zoos (ID and Name, not too much detail)
  • POST /zoos: Create a new Zoo
  • GET /zoos/ZID: Retrieve an entire Zoo object
  • PUT /zoos/ZID: Update a Zoo (entire object)
  • PATCH /zoos/ZID: Update a Zoo (partial object)
  • DELETE /zoos/ZID: Delete a Zoo
  • GET /zoos/ZID/animals: Retrieve a listing of Animals (ID and Name).
  • GET /animals: List all Animals (ID and Name).
  • POST /animals: Create a new Animal
  • GET /animals/AID: Retrieve an Animal object
  • PUT /animals/AID: Update an Animal (entire object)
  • PATCH /animals/AID: Update an Animal (partial object)
  • GET /animal_types: Retrieve a listing (ID and Name) of all Animal Types
  • GET /animal_types/ATID: Retrieve an entire Animal Type object
  • GET /employees: Retrieve an entire list of Employees
  • GET /employees/EID: Retreive a specific Employee
  • GET /zoos/ZID/employees: Retrieve a listing of Employees (ID and Name) who work at this Zoo
  • POST /employees: Create a new Employee
  • POST /zoos/ZID/employees: Hire an Employee at a specific Zoo
  • DELETE /zoos/ZID/employees/EID: Fire an Employee from a specific Zoo

In the above list, ZID means Zoo ID, AID means Animal ID, EID means Employee ID, and ATID means Animal Type ID. Having a key in your documentation for whatever convention you choose is a good idea.

I’ve left out the common API URL prefix in the above examples for brevity. While this can be fine during communications, in your actual API documentation, you should always display the full URL to each endpoint (e.g. GET http://api.example.com/v1/animal_type/ATID).

Notice how the relationships between data is displayed, specifically the many to many relationships between employees and zoos. By adding an additional URL segment, one can perform more specific interactions. Of course there is no HTTP verb for “FIRE”-ing an employee, but by performing a DELETE on an Employee located within a Zoo, we’re able to achieve the same effect.

Filtering

When a Consumer makes a request for a listing of objects, it is important that you give them a list of every single object matching the requested criteria. This list could be massive. But, it is important that you don’t perform any arbitrary limitations of the data. It is these arbitrary limits which make it hard for a third party developer to know what is going on. If they request a certain Collection, and iterate over the results, and they never see more than 100 items, it is now their job to figure out where this limit is coming from. Is their ORM buggy and limiting items to 100? Is the network chopping up large packets?

Minimize the arbitrary limits imposed on Third Party Developers.

It is important, however, that you do offer the ability for a Consumer to specify some sort of filtering/limitation of the results. The most important reason for this is that the network activity is minimal and the Consumer gets their results back as soon as possible. The second most important reason for this is the Consumer may be lazy, and if the Server can do filtering and pagination for them, all the better. The not-so-important reason (from the Consumers perspective), yet a great benefit for the Server, is that the request will be less resource heavy.

Filtering is mostly useful for performing GETs on Collections of resources. Since these are GET requests, filtering information should be passed via the URL. Here are some examples of the types of filtering you could conceivably add to your API:

  • ?limit=10: Reduce the number of results returned to the Consumer (for Pagination) 
  • ?offset=10: Send sets of information to the Consumer (for Pagination)
  • ?animal_type_id=1: Filter records which match the following condition (WHERE animal_type_id = 1) 
  • ?sortby=name&order=asc: Sort the results based on the specified attribute (ORDER BY name ASC) 

Some of these filterings can be redundant with endpoint URLS. For example I previously mentioned GET /zoo/ZID/animals. This would be the same thing as GET /animals?zoo_id=ZID. Dedicated endpoints being made available to the Consumer will make their lives easier, this is especially true with requests you anticipate they will make a lot. In the documentation, mention this redundancy so that Third Party Developers aren’t left wondering if differences exist.

Also, this goes without saying, but whenever you perform filtering or sorting of data, make sure you white-list the columns for which the Consumer can filter and sort by. We don’t want any database errors being sent to Consumers!

Status Codes

It is very important that as a RESTful API, you make use of the proper HTTP Status Codes; they are a standard after all! Various network equipment is able to read these status codes, e.g. load balancers can be configured to avoid sending requests to a web server sending out lots of 50x errors. There are a plethora of HTTP Status Codes to choose from, however this list should be a good starting point:

  • 200 OK – [GET]
    • The Consumer requested data from the Server, and the Server found it for them (Idempotent)
  • 201 CREATED – [POST/PUT/PATCH]
    • The Consumer gave the Server data, and the Server created a resource
  • 204 NO CONTENT – [DELETE]
    • The Consumer asked the Server to delete a Resource, and the Server deleted it
  • 400 INVALID REQUEST – [POST/PUT/PATCH]
    • The Consumer gave bad data to the Server, and the Server did nothing with it (Idempotent)
  • 404 NOT FOUND – [*]
    • The Consumer referenced an inexistant Resource or Collection, and the Server did nothing (Idempotent)
  • 500 INTERNAL SERVER ERROR – [*]
    • The Server encountered an error, and the Consumer has no knowledge if the request was successful

Status Code Ranges

The 1xx range is reserved for low-level HTTP stuff, and you’ll very likely go your entire career without manually sending one of these status codes.

The 2xx range is reserved for successful messages where all goes as planned. Do your best to ensure your Server sends as many of these to the Consumer as possible.

The 3xx range is reserved for traffic redirection. Most APIs do not use these requests much (not nearly as often as the SEO folks use them ;), however, the newer Hypermedia style APIs will make more use of these.

The 4xx range is reserved for responding to errors made by the Consumer, e.g. they’re providing bad data or asking for things which don’t exist. These requests should be be idempotent, and not change the state of the server.

The 5xx range is reserved as a response when the Server makes a mistake. Often times, these errors are thrown by low-level functions even outside of the developers hands, to ensure a Consumer gets some sort of response. The Consumer can’t possibly know the state of the server when a 5xx response is received, and so these should be avoidable.

Expected Return Documents

When performing actions using the different HTTP verbs to Server endpoints, a Consumer needs to get some sort of information in return. This list is pretty typical of RESTful APIs:

  • GET /collection: Return a listing (array) of Resource objects
  • GET /collection/resource: Return an individual Resource object
  • POST /collection: Return the newly created Resource object
  • PUT /collection/resource: Return the complete Resource object
  • PATCH /collection/resource: Return the complete Resource object
  • DELETE /collection/resource: Return an empty document

Note that when a Consumer creates a Resource, they usually do not know the ID of the Resource being created (nor other attributes such as created and modified timestamps, if applicable). These additional attributes are returned with subsequent request, and of course as a response to the initial POST.

Authentication

Most of the time a Server will want to know exactly who is making which Requests. Sure, some APIs provide endpoints to be consumed by the general (anonymous) public, but most of the time work is being perform on behalf of someone.

OAuth 2.0 provides a great way of doing this. With each Request, you can be sure you know which Consumer is making requests, which User they are making requests on behalf of, and provides a (mostly) standardized way of expiring access or allowing Users to revoke access from a Consumer, all without the need for a third-party consumer to know the Users login credentials.

There are also OAuth 1.0 and xAuth, which fill the same space. Whichever method you choose, make sure it is something common and well documented with many different libraries written for the languages/platforms which your Consumers will likely be using.

I can honestly tell you that OAuth 1.0a, while it is the most secure of the options, is a huge pain in the ass to implement. I was surprised by the number of Third Party Developers who had to implement their own library since one didn’t exist for their language already. I’ve spent enough hours debugging cryptic “invalid signature” errors to recommend you choose an alternative.

Content Type

Currently, the most “exciting” of APIs provide JSON data from RESTful interfaces. This includes Facebook, Twitter, GitHub, you name it. XML appears to have lost the war a while ago (except in large corporate environments). SOAP, thankfully, is all but dead, and we really don’t see much APIs providing HTML to be consumed (unless, that is, you’re building a scraper!)

Developers using popular languages and frameworks can very likely parse any valid data format you return to them. You can even provide data in any of the aforementioned data formats (not including SOAP) quite easily, if you’re building a common response object and using a different serializer. What does matter though, is that you make use of the Accept header when responding with data.

Some API creators recommend adding a .json, .xml, or .html file extension to the URL (after the endpoint) for specifying the content type to be returned, although I’m personally not a fan of this. I really like the Accept header (which is built into the HTTP spec) and feel that is the appropriate thing to use.

Hypermedia APIs

Hypermedia APIs are very likely the future of RESTful API design. They’re actually a pretty amazing concept, going “back to the roots” of how HTTP and HTML was intended to work.

When working with non-Hypermedia RESTful APIs, the URL Endpoints are part of the contract between the Server and the Consumer. These Endpoints MUST be known by the Consumer ahead of time, and changing them means the Consumer is no longer able to communicate with the Server as intended. This, as you can assume, is quite a limitation.

Now, API Consumers are of course not the only user agent making HTTP requests on the Internet. Far from it. Humans, with their web browsers, are the most common user agent making HTTP requests. Humans, however, are NOT locked into this predefined Endpoint URL contract that RESTful APIs are. What makes humans so special? Well, they’re able to read content, click links for headings which look interesting, and in general explore a website and interpret content to get to where they want to go. If a URL changes, a human is not affected (unless, that is, they bookmarked a page, in which case they go to the homepage and find a new route to their beloved data).

The Hypermedia API concept works the same way a human would. Requesting the Root of the API returns a listing of URLs which point perhaps to each collection of information, and describing each collection in a way which the Consumer can understand. Providing IDs for each resource isn’t important (or necessarily required), as long as a URL is provided.

With the Consumer of a Hypermedia API crawling links and gathering information, URLs are always up-to-date within responses, and do not need to be known beforehand as part of a contract. If a URL is ever cached, and a subsequent request returns a 404, the Consumer can simply go back to the root and discover the content again.

When retrieving a list of Resources within a Collection, an attribute containing a complete URL for the individual Resources are returned. When performing a POST/PATCH/PUT, the response can be a 3xx redirect to the complete Resource.

JSON doesn’t quite give us the semantics we need for specifying which attributes are URLs, nor how URLs relate to the current document. HTML, as you can probably guess, does provide this information. We may very well see our APIs coming full circle and returning back to consuming HTML. Considering how far we’ve come with CSS, one day we may even see  it be common practice for APIs and Websites to use the exact same URLs and content.

Documentation

Honestly, if you don’t conform 100% to the criteria in this guide, your API will not necessarily be horrible. However, if you don’t properly document your API, nobody is going to know how to use it, and it WILL be a horrible API.

Make your Documentation available to unauthenticated developers.

Do not use automatic documentation generators, or if you do, at least make sure you’re doctoring it up and making it presentable.

Do not truncate example request and response bodies; show the whole thing. Use a syntax highlighter in your documentation.

Document expected response codes and possible error messages for each endpoint, and what could have gone wrong to cause those error messages.

If you’ve got the spare time, build a developer API console so that developers can immediately experiment with your API. It’s not as hard as you might think and developers (both internal and third party) will love you for it!

Make sure your documentation can be printed; CSS is a powerful thing; don’t be afraid to hide that sidebar when the docs are printed. Even if nobody ever prints a physical copy, you’d be surprised at how many developers like to print to PDF for offline reading.

Errata: Raw HTTP Packet

Since everything we do is over HTTP, I’m going to show you a dissection of an HTTP packet. I’m often surprised at how many people don’t know what these things look like! When the Consumer sends a Request to the Server, they provide a set of Key/Value pairs, called a Header, along with two newline characters, and finally the request body. This is all sent in the same packet.

The server then responds in the say Key/Value pair format, with two newlines and then the response body. HTTP is very much a request/response protocol; there is no “Push” support (the Server sending data to the Consumer unprovoked), unless you use a different protocol such as Websockets.

When designing your API, you should be able to work with tools which allow you to look at raw HTTP packets. Consider using Wireshark, for example. Also, make sure you are using a framework / web server which allows you to read and change as many of these fields as possible.

中文翻译

好RESTful API的设计原则

作出一个好的API设计很难。API表达的是你的数据和你的数据使用者之间的契约。打破这个契约将会招致不少愤怒的邮件,和一大堆伤心的用户-由于他们手机上的App不工做了。而文档化只能达到一半的效果,而且也很难找到一个愿意写文档的程序员。

你所能作的最重要一件事来提升服务的价值就是建立一个API。由于随着其余服务的成长,有这样一个API会使你的服务或者核心应用将有机会变成一个平台。环顾一下现有的这些大公司:Facebook,Twitter,Google, Github,Amazon,Netflix等。若是当时他们没有经过API来开放数据的话,也不可能成长到现在的规模。事实上,整个行业存在的惟一目的就是消费所谓平台上的数据。

你的API越容易使用,那么就会有越多的人去用它

本文提到的这些原则,若是你的API能严格按照这些原则来设计,使用者就能够知道它接下来要作什么,而且能减小大量没必要要的疑惑或者是愤怒的邮件。我已经把全部内容都整理到不一样的主题里了,你无需按顺序去阅读它。

定义

这里有一些很是重要的术语,我将在本文里面一直用到它们:

  • 资源:一个对象的单独实例,如一只动物
  • 集合:一群同种对象,如动物
  • HTTP:跨网络的通讯协议
  • 客户端:能够建立HTTP请求的客户端应用程序
  • 第三方开发者:这个开发者不属于你的项目可是有想使用你的数据
  • 服务器:一个HTTP服务器或者应用程序,客户端能够跨网络访问它
  • 端点:这个API在服务器上的URL用于表达一个资源或者一个集合
  • 幂等:无边际效应,屡次操做获得相同的结果
  • URL段:在URL里面已斜杠分隔的内容

数据设计与抽象

规划好你的API的外观要先于开发它实际的功能。首先你要知道数据该如何设计和核心服务/应用程序会如何工做。若是你纯粹新开发一个API,这样会比较容易一些。但若是你是往已有的项目中增长API,你可能须要提供更多的抽象。

有时候一个集合能够表达一个数据库表,而一个资源能够表达成里面的一行记录,可是这并非常态。事实上,你的API应该尽量经过抽象来分离数据与业务逻辑。这点很是重要,只有这样作你才不会打击到那些拥有复杂业务的第三方开发者,不然他们是不会使用你的API的。

固然你的服务可能不少部分是不该该经过API暴露出去的。比较常见的例子就是不少API是不容许第三方来建立用户的。

状态

显然你了解GET和POST请求。当你用浏览器去访问不一样页面的时候,这两个是最多见的请求。POST术语如此流行以致于开始侵扰通俗用语。即便是那些不知道互联网如何工做的人们也能“post”一些东西到朋友的Facebook墙上。

这里至少有四个半很是重要的HTTP动词须要你知道。我之因此说“半个”的意思是PATCH这个动词很是相似于PUT,而且它们俩也经常被开发者绑定到同一个API上。

  • GET (选择):从服务器上获取一个具体的资源或者一个资源列表。
  • POST (建立): 在服务器上建立一个新的资源。
  • PUT (更新):以总体的方式更新服务器上的一个资源。
  • PATCH (更新):只更新服务器上一个资源的一个属性。
  • DELETE (删除):删除服务器上的一个资源。

还有两个不经常使用的HTTP动词:

  • HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。
  • OPTIONS:获取客户端能对资源作什么操做的信息。

一个好的RESTful API只容许第三方调用者使用这四个半HTTP动词进行数据交互,而且在URL段里面不出现任何其余的动词。

通常来讲,GET请求能够被浏览器缓存(一般也是这样的)。例如,缓存请求头用于第二次用户的POST请求。HEAD请求是基于一个无响应体的GET请求,而且也能够被缓存的。

版本化

不管你正在构建什么,不管你在入手前作了多少计划,你核心的应用总会发生变化,数据关系也会变化,资源上的属性也会被增长或删除。只要你的项目还活着,而且有大量的用户在用,这种状况老是会发生。

请谨记一点,API是服务器与客户端之间的一个公共契约。若是你对服务器上的API作了一个更改,而且这些更改没法向后兼容,那么你就打破了这个契约,客户端又会要求你从新支持它。为了不这样的事情,你既要确保应用程序逐步的演变,又要让客户端满意。那么你必须在引入新版本API的同时保持旧版本API仍然可用。

注:若是你只是简单的增长一个新的特性到API上,如资源上的一个新属性或者增长一个新的端点,你不须要增长API的版本。由于这些并不会形成向后兼容性的问题,你只须要修改文档便可。

随着时间的推移,你可能声明再也不支持某些旧版本的API。申明不支持一个特性并不意味着关闭或者破坏它。而是告诉客户端旧版本的API将在某个特定的时间被删除,而且建议他们使用新版本的API。

一个好的RESTful API会在URL中包含版本信息。另外一种比较常见的方案是在请求头里面保持版本信息。可是跟不少不一样的第三方开发者一块儿工做后,我能够很明确的告诉你,在请求头里面包含版本信息远没有放在URL里面来的容易。

分析

所谓API分析就是持续跟踪那些正为人使用的API的版本和端点信息。而这可能就跟每次请求都往数据库增长一个整数那样简单。有不少的缘由显示API跟踪分析是一个好主意,例如,对那些使用最普遍的API来讲效率是最重要的。

第三方开发者一般会关注API的构建目的,其中最重要的一个目的是你决定何时再也不支持某个版本。你须要明确的告知开发者他们正在使用那些即将被移除的API特性。这是一个很好的方式在你准备删除旧的API以前去提醒他们进行升级。

固然第三方开发者的通知流程能够以某种条件被自动触发,例如每当一个过期的特性上发生10000次请求时就发邮件通知开发者。

API根URL

不管你信不信,API的根地址很重要。当一个开发者接手了一个旧项目(如进行代码考古时)。而这个项目正在使用你的API,同时开发者还想构建一个新的特性,但他们彻底不知道你的服务。幸运的是他们知道客户端对外调用的那些URL列表。让你的API根入口点保持尽量的简单是很重要的,由于开发者极可能一看到那些冗长而又复杂的URL就转身而走。

这里有两个常见的URL根例子:

  • https://example.org/api/v1/*
  • https://api.example.com/v1/*

若是你的应用很庞大或者你预期它将会变的很庞大,那么将API放到子域下一般是一个好选择。这种作法能够保持某些规模化上的灵活性。

若是你以为你的API不会变的很庞大,或是你只是想让应用安装更简单些(如你想用相同的框架来支持站点和API),将你的API放到根域名下也是能够的。

让API根拥有一些内容一般也是个好主意。Github的API根就是一个典型的例子。从我的角度来讲我是一个经过根URL发布信息的粉丝,这对不少人来讲是有用的,例如如何获取API相关的开发文档。

一样也请注意HTTPS前缀,一个好的RESTful API老是基于HTTPS来发布的。

端点

一个端点就是指向特定资源或资源集合的URL。

若是你正在构建一个虚构的API来展示几个不一样的动物园,每个动物园又包含不少动物,员工和每一个动物的物种,你可能会有以下的端点信息:

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/animal_types
  • https://api.example.com/v1/employees

针对每个端点来讲,你可能想列出全部可行的HTTP动词和端点的组合。以下所示,请注意我把HTTP动词都放在了虚构的API以前,正如将一样的注解放在每个HTTP请求头里同样。(下面的URL就不翻译了,我以为没啥必要翻^_^)

  • GET /zoos: List all Zoos (ID and Name, not too much detail)
  • POST /zoos: Create a new Zoo
  • GET /zoos/ZID: Retrieve an entire Zoo object
  • PUT /zoos/ZID: Update a Zoo (entire object)
  • PATCH /zoos/ZID: Update a Zoo (partial object)
  • DELETE /zoos/ZID: Delete a Zoo
  • GET /zoos/ZID/animals: Retrieve a listing of Animals (ID and Name).
  • GET /animals: List all Animals (ID and Name).
  • POST /animals: Create a new Animal
  • GET /animals/AID: Retrieve an Animal object
  • PUT /animals/AID: Update an Animal (entire object)
  • PATCH /animals/AID: Update an Animal (partial object)
  • GET /animal_types: Retrieve a listing (ID and Name) of all Animal Types
  • GET /animal_types/ATID: Retrieve an entire Animal Type object
  • GET /employees: Retrieve an entire list of Employees
  • GET /employees/EID: Retreive a specific Employee
  • GET /zoos/ZID/employees: Retrieve a listing of Employees (ID and Name) who work at this Zoo
  • POST /employees: Create a new Employee
  • POST /zoos/ZID/employees: Hire an Employee at a specific Zoo
  • DELETE /zoos/ZID/employees/EID: Fire an Employee from a specific Zoo

在上面的列表里,ZID表示动物园的ID, AID表示动物的ID,EID表示雇员的ID,还有ATID表示物种的ID。让文档里全部的东西都有一个关键字是一个好主意。

为了简洁起见,我已经省略了全部API共有的URL前缀。做为沟通方式这没什么问题,可是若是你真要写到API文档中,那就必须包含完整的路径(如,GET http://api.example.com/v1/animal_type/ATID)。

请注意如何展现数据之间的关系,特别是雇员与动物园之间的多对多关系。经过添加一个额外的URL段就能够实现更多的交互能力。固然没有一个HTTP动词能表示正在解雇一我的,可是你可使用DELETE一个动物园里的雇员来达到相同的效果。

过滤器

当客户端建立了一个请求来获取一个对象列表时,很重要一点就是你要返回给他们一个符合查询条件的全部对象的列表。这个列表可能会很大。但你不能随意给返回数据的数量作限制。由于这些无谓的限制会致使第三方开发者不知道发生了什么。若是他们请求一个确切的集合而且要遍历结果,然而他们发现只拿到了100条数据。接下来他们就不得不去查找这个限制条件的出处。究竟是ORM的bug致使的,仍是由于网络截断了大数据包?

尽量减小那些会影响到第三方开发者的无谓限制

这点很重要,但你可让客户端本身对结果作一些具体的过滤或限制。这么作最重要的一个缘由是能够最小化网络传输,并让客户端尽量快的获得查询结果。其次是客户端可能比较懒,若是这时服务器能对结果作一些过滤或分页,对你们都是好事。另一个不那么重要的缘由是(从客户端角度来讲),对服务器来讲响应请求的负载越少越好。

过滤器是最有效的方式去处理那些获取资源集合的请求。因此只要出现GET的请求,就应该经过URL来过滤信息。如下有一些过滤器的例子,多是你想要填加到API中的:

  • ?limit=10: 减小返回给客户端的结果数量(用于分页)
  • ?offset=10: 发送一堆信息给客户端(用于分页)
  • ?animal_type_id=1: 使用条件匹配来过滤记录
  • ?sortby=name&order=asc:  对结果按特定属性进行排序

有些过滤器可能会与端点URL的效果重复。例如我以前提到的GET /zoo/ZID/animals。它也一样能够经过GET /animals?zoo_id=ZID来实现。独立的端点会让客户端更好过一些,由于他们的需求每每超出你的预期。本文中提到这种冗余差别可能对第三方开发者并不可见。

不管怎么说,当你准备过滤或排序数据时,你必须明确的将那些客户端能够过滤或排序的列放到白名单中,由于咱们不想将任何的数据库错误发送给客户端。

状态码

对于一个RESTful API来讲很重要的一点就是要使用HTTP的状态码,由于它们是HTTP的标准。不少的网络设备均可以识别这些状态码,例如负载均衡器可能会经过配置来避免发送请求到一台web服务器,若是这台服务器已经发送了不少的50x错误回来。这里有大量的HTTP状态码能够选择,可是下面的列表只给出了一些重要的代码做为一个参考:

  • 200 OK – [GET]
    • 客户端向服务器请求数据,服务器成功找到它们
  • 201 CREATED – [POST/PUT/PATCH]
    • 客户端向服务器提供数据,服务器根据要求建立了一个资源
  • 204 NO CONTENT – [DELETE]
    • 客户端要求服务器删除一个资源,服务器删除成功
  • 400 INVALID REQUEST – [POST/PUT/PATCH]
    • 客户端向服务器提供了不正确的数据,服务器什么也没作
  • 404 NOT FOUND – [*]
    • 客户端引用了一个不存在的资源或集合,服务器什么也没作
  • 500 INTERNAL SERVER ERROR – [*]
    • 服务器发生内部错误,客户端没法得知结果,即使请求已经处理成功

状态码范围

1xx范围的状态码是保留给底层HTTP功能使用的,而且估计在你的职业生涯里面也用不着手动发送这样一个状态码出来。

2xx范围的状态码是保留给成功消息使用的,你尽量的确保服务器总发送这些状态码给用户。

3xx范围的状态码是保留给重定向用的。大多数的API不会太常使用这类状态码,可是在新的超媒体样式的API中会使用更多一些。

4xx范围的状态码是保留给客户端错误用的。例如,客户端提供了一些错误的数据或请求了不存在的内容。这些请求应该是幂等的,不会改变任何服务器的状态。

5xx范围的状态码是保留给服务器端错误用的。这些错误经常是从底层的函数抛出来的,而且开发人员也一般无法处理。发送这类状态码的目的是确保客户端能获得一些响应。收到5xx响应后,客户端没办法知道服务器端的状态,因此这类状态码是要尽量的避免。

预期的返回文档

当使用不一样的HTTP动词向服务器请求时,客户端须要在返回结果里面拿到一系列的信息。下面的列表是很是经典的RESTful API定义:

  • GET /collection: 返回一系列资源对象
  • GET /collection/resource: 返回单独的资源对象
  • POST /collection: 返回新建立的资源对象
  • PUT /collection/resource: 返回完整的资源对象
  • PATCH /collection/resource: 返回完整的资源对象
  • DELETE /collection/resource: 返回一个空文档

请注意当一个客户端建立一个资源时,她们经常不知道新建资源的ID(也许还有其余的属性,如建立和修改的时间戳等)。这些属性将在随后的请求中返回,而且做为刚才POST请求的一个响应结果。

认证

服务器在大多数状况下是想确切的知道谁建立了什么请求。固然,有些API是提供给公共用户(匿名用户)的,可是大部分时间里也是表明某人的利益。

OAuth2.0提供了一个很是好的方法去作这件事。在每个请求里,你能够明确知道哪一个客户端建立了请求,哪一个用户提交了请求,而且提供了一种标准的访问过时机制或容许用户从客户端注销,全部这些都不须要第三方的客户端知道用户的登录认证信息。

还有OAuth1.0和xAuth一样适用这样的场景。不管你选择哪一个方法,请确保它为多种不一样语言/平台上的库提供了一些通用的而且设计良好文档,由于你的用户可能会使用这些语言和平台来编写客户端。

内容类型

目前,大多数“精彩”的API都为RESTful接口提供JSON数据。诸如Facebook,Twitter,Github等等你所知的。XML曾经也火过一把(一般在一个大企业级环境下)。这要感谢SOAP,不过它已经挂了,而且咱们也没看到太多的API把HTML做为结果返回给客户端(除非你在构建一个爬虫程序)。

只要你返回给他们有效的数据格式,开发者就可使用流行的语言和框架进行解析。若是你正在构建一个通用的响应对象,经过使用一个不一样的序列化器,你也能够很容易的提供以前所提到的那些数据格式(不包括SOAP)。而你所要作的就是把使用方式放在响应数据的接收头里面。

有些API的建立者会推荐把.json, .xml, .html等文件的扩展名放在URL里面来指示返回内容类型,但我我的并不习惯这么作。我依然喜欢经过接收头来指示返回内容类型(这也是HTTP标准的一部分),而且我以为这么作也比较适当一些。

超媒体API

超媒体API极可能就是RESTful API设计的未来。超媒体是一个很是棒的概念,它回归到了HTTP和HTML如何运做的“本质”。

在非超媒体RESTful API的情景中,URL端点是服务器与客户端契约的一部分。这些端点必须让客户端事先知道,而且修改它们也意味着客户端可能再也没法与服务器通讯了。你能够先假定这是一个限制。

时至今日,英特网上的API客户端已经不只仅只有那些建立HTTP请求的用户代理了。大多数HTTP请求是由人们经过浏览器产生的。人们不会被哪些预先定义好的RESTful API端点URL所束缚。是什么让人们变的如此不同凡响?由于人们能够阅读内容,能够点击他们感兴趣的连接,并浏览一下网站,而后跳到他们关注的内容那里。即便一个URL改变了,人们也不会受到影响(除非他们事先给某个页面作了书签,这时他们回到主页并发现原来有一条新的路径能够去往以前的页面)。

超媒体API概念的运做跟人们的行为相似。经过请求API的根来得到一个URL的列表,这个列表里面的每个URL都指向一个集合,而且提供了客户端能够理解的信息来描述每个集合。是否为每个资源提供ID并不重要(或者不是必须的),只要提供URL便可。

一个超媒体API一旦具备了客户端,那么它就能够爬行连接并收集信息,而URL老是在响应中被更新,而且不须要如契约的一部分那样事先被知晓。若是一个URL曾经被缓存过,而且在随后的请求中返回404错误,那么客户端能够很简单的回退到根URL并从新发现内容。

在获取集合中的一个资源列表时会返回一个属性,这个属性包含了各个资源的完整URL。当实施一个POST/PATCH/PUT请求后,响应能够被一个3xx的状态码重定向到完整的资源上。

JSON不只告诉了咱们须要定义哪些属性做为URL,也告诉了咱们如何将URL与当前文档关联的语义。正如你猜的那样,HTML就提供了这样的信息。咱们可能很乐意看到咱们的API走完了完整的周期,并回到了处理HTML上来。想一下咱们与CSS一块儿前行了多远,有一天咱们可能再次看到它变成了一个通用实践让API和网站能够去使用相同的URL和内容。

文档

老实说,即便你不能百分之百的遵循指南中的条款,你的API也不是那么糟糕。可是,若是你不为API准备文档的话,没有人会知道怎么使用它,那它真的会成为一个糟糕的API。

  • 让你的文档对那些未经认证的开发者也可用
  • 不要使用文档自动化生成器,即使你用了,你也要保证本身审阅过并让它具备更好的版式。
  • 不要截断示例中请求与响应的内容,要展现完整的东西。并在文档中使用高亮语法。
  • 文档化每个端点所预期的响应代码和可能的错误消息,和在什么状况下会产生这些的错误消息

若是你有富余的时间,那就建立一个控制台来让开发者能够当即体验一下API的功能。建立一个控制台并无想象中那么难,而且开发者们(内部或者第三方)也会所以而拥戴你。

另外确保你的文档可以被打印。CSS是个强大的工具能够帮助到你。并且在打印的时候也不用太担忧边侧栏的问题。即使没有人会打印到纸上,你也会惊奇的发现不少开发者愿意转化成PDF格式进行离线阅读。

勘误:原始的HTTP封包

由于咱们所作的都是基于HTTP协议,因此我将展现给你一个解析了的HTTP封包。我常常很惊讶的发现有多少人不知道这些东西。当客户端发送一个请求道服务器时,他们会提供一个键值对集,先是一个头,紧跟着是两个回车换行符,而后才是请求体。全部这些都是在一个封包里被发送。

服务器响应也是一样的键值对集,带两个回车换行符,而后是响应体。HTTP就是一个请求/响应协议;它不支持“推送”模式(服务器直接发送数据给客户端),除非你采用其余协议,如Websockets。

当你设计API时,你应该可以使用工具去查看原始的HTTP封包。Wireshark是个不错的选择。同时,你也该采用一个框架/web服务器,使你可以在必要时修改某些字段的值。

Example HTTP Request

POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24
 
{
  "name": "Gir",
  "animal_type": 12
}

Example HTTP Response

HTTP/1.1 200 OK
Date: Wed, 18 Dec 2013 06:08:22 GMT
Content-Type: application/json
Access-Control-Max-Age: 1728000
Cache-Control: no-cache
 
{
  "id": 12,
  "created": 1386363036,
  "modified": 1386363036,
  "name": "Gir",
  "animal_type": 12
}


a