RESTful API的設計原則

說在前面,這篇文章是無意中發現的,因爲感覺寫的很好,所以翻譯了一下。由於英文水平有限,難免有出錯的地方,請看官理解一下。翻譯和校正文章花了我大約2周的業餘時間,如有人願意轉載請註明出處,謝謝^_^

 

Principles of good RESTful API Design

好RESTful API的設計原則

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.

 

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.

做出一個好的API設計很難。API表達的是你的數據和你的數據使用者之間的契約。打破這個契約將會招致很多憤怒的郵件,和一大堆傷心的用戶-因爲他們手機上的App不工作了。而文檔化只能達到一半的效果,並且也很難找到一個願意寫文檔的程序員。

你所能做的最重要一件事來提高服務的價值就是創建一個API。因爲隨着其他服務的成長,有這樣一個API會使你的服務或者核心應用將有機會變成一個平臺。環顧一下現有的這些大公司:Facebook,Twitter,Google, Github,Amazon,Netflix等。如果當時他們沒有通過API來開放數據的話,也不可能成長到如今的規模。事實上,整個行業存在的唯一目的就是消費所謂平臺上的數據。

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

 

本文提到的這些原則,如果你的API能嚴格按照這些原則來設計,使用者就可以知道它接下來要做什麼,並且能減少大量不必要的疑惑或者是憤怒的郵件。我已經把所有內容都整理到不同的主題裏了,你無需按順序去閱讀它。

Definitions

定義

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

  • 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.

這裏有一些非常重要的術語,我將在本文裏面一直用到它們:

  • 資源:一個對象的單獨實例,如一隻動物
  • 集合:一羣同種對象,如動物
  • HTTP:跨網絡的通信協議
  • 客戶端:可以創建HTTP請求的客戶端應用程序
  • 第三方開發者:這個開發者不屬於你的項目但是有想使用你的數據
  • 服務器:一個HTTP服務器或者應用程序,客戶端可以跨網絡訪問它
  • 端點:這個API在服務器上的URL用於表達一個資源或者一個集合
  • 冪等:無邊際效應,多次操作得到相同的結果
  • URL段:在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.

規劃好你的API的外觀要先於開發它實際的功能。首先你要知道數據該如何設計和核心服務/應用程序會如何工作。如果你純粹新開發一個API,這樣會比較容易一些。但如果你是往已有的項目中增加API,你可能需要提供更多的抽象。

有時候一個集合可以表達一個數據庫表,而一個資源可以表達成裏面的一行記錄,但是這並不是常態。事實上,你的API應該儘可能通過抽象來分離數據與業務邏輯。這點非常重要,只有這樣做你纔不會打擊到那些擁有複雜業務的第三方開發者,否則他們是不會使用你的API的。

當然你的服務可能很多部分是不應該通過API暴露出去的。比較常見的例子就是很多API是不允許第三方來創建用戶的。

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.

顯然你瞭解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請求,並且也可以被緩存的。

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.

無論你正在構建什麼,無論你在入手前做了多少計劃,你核心的應用總會發生變化,數據關係也會變化,資源上的屬性也會被增加或刪除。只要你的項目還活着,並且有大量的用戶在用,這種情況總是會發生。

請謹記一點,API是服務器與客戶端之間的一個公共契約。如果你對服務器上的API做了一個更改,並且這些更改無法向後兼容,那麼你就打破了這個契約,客戶端又會要求你重新支持它。爲了避免這樣的事情,你既要確保應用程序逐步的演變,又要讓客戶端滿意。那麼你必須在引入新版本API的同時保持舊版本API仍然可用。

注:如果你只是簡單的增加一個新的特性到API上,如資源上的一個新屬性或者增加一個新的端點,你不需要增加API的版本。因爲這些並不會造成向後兼容性的問題,你只需要修改文檔即可。

隨着時間的推移,你可能聲明不再支持某些舊版本的API。申明不支持一個特性並不意味着關閉或者破壞它。而是告訴客戶端舊版本的API將在某個特定的時間被刪除,並且建議他們使用新版本的API。

 一個好的RESTful API會在URL中包含版本信息。另一種比較常見的方案是在請求頭裏面保持版本信息。但是跟很多不同的第三方開發者一起工作後,我可以很明確的告訴你,在請求頭裏麪包含版本信息遠沒有放在URL裏面來的容易。

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分析就是持續跟蹤那些正爲人使用的API的版本和端點信息。而這可能就跟每次請求都往數據庫增加一個整數那樣簡單。有很多的原因顯示API跟蹤分析是一個好主意,例如,對那些使用最廣泛的API來說效率是最重要的。

第三方開發者通常會關注API的構建目的,其中最重要的一個目的是你決定什麼時候不再支持某個版本。你需要明確的告知開發者他們正在使用那些即將被移除的API特性。這是一個很好的方式在你準備刪除舊的API之前去提醒他們進行升級。

當然第三方開發者的通知流程可以以某種條件被自動觸發,例如每當一個過時的特性上發生10000次請求時就發郵件通知開發者。

API Root URL

API根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.

無論你信不信,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來發布的。

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.

一個端點就是指向特定資源或資源集合的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一個動物園裏的僱員來達到相同的效果。

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!

當客戶端創建了一個請求來獲取一個對象列表時,很重要一點就是你要返回給他們一個符合查詢條件的所有對象的列表。這個列表可能會很大。但你不能隨意給返回數據的數量做限制。因爲這些無謂的限制會導致第三方開發者不知道發生了什麼。如果他們請求一個確切的集合並且要遍歷結果,然而他們發現只拿到了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來實現。獨立的端點會讓客戶端更好過一些,因爲他們的需求往往超出你的預期。本文中提到這種冗餘差異可能對第三方開發者並不可見。

無論怎麼說,當你準備過濾或排序數據時,你必須明確的將那些客戶端可以過濾或排序的列放到白名單中,因爲我們不想將任何的數據庫錯誤發送給客戶端。

 

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:

對於一個RESTful API來說很重要的一點就是要使用HTTP的狀態碼,因爲它們是HTTP的標準。很多的網絡設備都可以識別這些狀態碼,例如負載均衡器可能會通過配置來避免發送請求到一臺web服務器,如果這臺服務器已經發送了很多的50x錯誤回來。這裏有大量的HTTP狀態碼可以選擇,但是下面的列表只給出了一些重要的代碼作爲一個參考:

  • 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.

    1xx範圍的狀態碼是保留給底層HTTP功能使用的,並且估計在你的職業生涯裏面也用不着手動發送這樣一個狀態碼出來。

    2xx範圍的狀態碼是保留給成功消息使用的,你儘可能的確保服務器總髮送這些狀態碼給用戶。

    3xx範圍的狀態碼是保留給重定向用的。大多數的API不會太常使用這類狀態碼,但是在新的超媒體樣式的API中會使用更多一些。

    4xx範圍的狀態碼是保留給客戶端錯誤用的。例如,客戶端提供了一些錯誤的數據或請求了不存在的內容。這些請求應該是冪等的,不會改變任何服務器的狀態。

    5xx範圍的狀態碼是保留給服務器端錯誤用的。這些錯誤常常是從底層的函數拋出來的,並且開發人員也通常沒法處理。發送這類狀態碼的目的是確保客戶端能得到一些響應。收到5xx響應後,客戶端沒辦法知道服務器端的狀態,所以這類狀態碼是要儘可能的避免。

    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.

    當使用不同的HTTP動詞向服務器請求時,客戶端需要在返回結果裏面拿到一系列的信息。下面的列表是非常經典的RESTful API定義:

    • GET /collection: 返回一系列資源對象
    • GET /collection/resource: 返回單獨的資源對象
    • POST /collection: 返回新創建的資源對象
    • PUT /collection/resource: 返回完整的資源對象
    • PATCH /collection/resource: 返回完整的資源對象
    • DELETE /collection/resource: 返回一個空文檔

    請注意當一個客戶端創建一個資源時,她們常常不知道新建資源的ID(也許還有其他的屬性,如創建和修改的時間戳等)。這些屬性將在隨後的請求中返回,並且作爲剛纔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.

    服務器在大多數情況下是想確切的知道誰創建了什麼請求。當然,有些API是提供給公共用戶(匿名用戶)的,但是大部分時間裏也是代表某人的利益。

    OAuth2.0提供了一個非常好的方法去做這件事。在每一個請求裏,你可以明確知道哪個客戶端創建了請求,哪個用戶提交了請求,並且提供了一種標準的訪問過期機制或允許用戶從客戶端註銷,所有這些都不需要第三方的客戶端知道用戶的登陸認證信息。

    還有OAuth1.0和xAuth同樣適用這樣的場景。無論你選擇哪個方法,請確保它爲多種不同語言/平臺上的庫提供了一些通用的並且設計良好文檔,因爲你的用戶可能會使用這些語言和平臺來編寫客戶端。

    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.

    目前,大多數“精彩”的API都爲RESTful接口提供JSON數據。諸如Facebook,Twitter,Github等等你所知的。XML曾經也火過一把(通常在一個大企業級環境下)。這要感謝SOAP,不過它已經掛了,並且我們也沒看到太多的API把HTML作爲結果返回給客戶端(除非你在構建一個爬蟲程序)。

     只要你返回給他們有效的數據格式,開發者就可以使用流行的語言和框架進行解析。如果你正在構建一個通用的響應對象,通過使用一個不同的序列化器,你也可以很容易的提供之前所提到的那些數據格式(不包括SOAP)。而你所要做的就是把使用方式放在響應數據的接收頭裏面。

    有些API的創建者會推薦把.json, .xml, .html等文件的擴展名放在URL裏面來指示返回內容類型,但我個人並不習慣這麼做。我依然喜歡通過接收頭來指示返回內容類型(這也是HTTP標準的一部分),並且我覺得這麼做也比較適當一些。

    Hypermedia APIs

    超媒體API

    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.

    超媒體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和內容。

    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.

    老實說,即使你不能百分之百的遵循指南中的條款,你的API也不是那麼糟糕。但是,如果你不爲API準備文檔的話,沒有人會知道怎麼使用它,那它真的會成爲一個糟糕的API。

    • 讓你的文檔對那些未經認證的開發者也可用
    • 不要使用文檔自動化生成器,即便你用了,你也要保證自己審閱過並讓它具有更好的版式。
    • 不要截斷示例中請求與響應的內容,要展示完整的東西。並在文檔中使用高亮語法。
    • 文檔化每一個端點所預期的響應代碼和可能的錯誤消息,和在什麼情況下會產生這些的錯誤消息

    如果你有富餘的時間,那就創建一個控制檯來讓開發者可以立即體驗一下API的功能。創建一個控制檯並沒有想象中那麼難,並且開發者們(內部或者第三方)也會因此而擁戴你。

    另外確保你的文檔能夠被打印。CSS是個強大的工具可以幫助到你。而且在打印的時候也不用太擔心邊側欄的問題。即便沒有人會打印到紙上,你也會驚奇的發現很多開發者願意轉化成PDF格式進行離線閱讀。

    Errata: Raw HTTP Packet

    勘誤:原始的HTTP封包

    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.

    因爲我們所做的都是基於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
    }
    

    將想法付諸於實踐,藉此來影響他人是一個人存在的真正價值

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章