以前團隊主要的工做就是作一套 REST API。我接手這個工做時發現那些API寫的比較業餘,沒有考慮幾個基礎的HTTP/1.1 RFC(2616,7232,5988等等)的實現,因而我花了些時間重寫,而後寫下了那篇文章。node
站在今天的角度看,那時我作的系統也有很多問題,不少 API 以外的問題沒有考慮:git
API 的使用文檔。當時個人作法是把文檔寫在公司使用的協做系統 confluence 裏,但這樣作的最大的問題是:代碼和文檔分離,很差維護。程序員
API 的監控。整個 API 系統沒有一個成體系的監控機制,各類 metrics 的收集嚴重依賴 API 的實現者處理,拿時髦的話說就是無法 orchestrate。es6
API 的測試。作過大量 API 工做的人都知道,爲 API 寫測試用例是很是痛苦的事情,你不但要對 API 使用的代碼作 unit test,還須要對 API 自己作 smoke test(最基本的 functional test),保證全部 API 是可用的,符合預期的。因爲須要撰寫的測試用例的數量巨大,通常咱們寫寫 unit test 就了事。github
理想狀況下,一個 API 撰寫完成,應該可以自動生成文檔和測試用例,而 API 系統也應該提供一整套統計的 API 用於生成 metrics。缺省狀況下,API 系統自己就應該收集不少 metrics,好比每一個 API 的 response time,status code 等等,使用 collectd / statd 收集信息,並能夠進一步發送到 datadog / new relic 這樣的 APM 系統。mongodb
在 adRise,咱們有一套運行了數年的 API 系統,不符合 RFC,(幾乎)沒有文檔,(幾乎)沒有測試,(幾乎)沒有監控,最要命的是,它的開發效率和運行效率都不高。所以,過去的一兩個月,我主導開發了一個全新的 API 系統。數據庫
在打造一個新的系統以前,咱們須要確立一些目標。這是我在設計 API 時寫下的一些目標:express
A well defined pipeline to process requestsjson
REST API done right (methods, status code and headers)後端
Validation made easy
Security beared in mind
Policy based request throttling
Easy to add new APIs
Easy to document and test
Introspection
其中,introspection 包含兩層意思:
API 系統自動收集 metrics,自我監控
不管是撰寫者,仍是調用者,都很很方便的獲取想要獲取的信息
有了以上目標,接下來的就是進行技術選型。技術選型是沒法脫離團隊單獨完成的,若是讓我我的選擇一個基礎語言和框架,我大概會選擇基於 Erlang/OTP,使用 Elixir 開發的 Phoenix,或者,乾脆使用 Plug(Phoenix 的基石)。由於 Plug / Phoenix 經過組合來構建 pipeline 的方式很符合個人思惟,Elixir 對 macro 的支持和 Erlang 語言核心的 pattern matching 讓諸如路由這樣的子系統高效簡潔美觀,而 Erlang / OTP 的高併發下的健壯性又是一個 API 系統苦苦追求的。
然而,我須要考慮團隊的現實。在 adRise,咱們使用 node.js 做爲後端的主要技術棧(還有一些 PHP / Python / scala),所以 API 系統最好是基於 node.js 來完成。node.js 下有不少適合於寫 API 的框架,好比說:express,restify,hapi,loopback,sails.js 等。在綜合考察了這些框架以後,我選擇了 restify,緣由有三:
接口和結構很是相似 express(團隊對此很是有經驗),但比 express 更專一於 REST API
一系列 middleware 和 route actions 能夠組成一個靈活高效的 pipeline
簡單,可擴展性強,容易和其餘庫結合,很適合做爲一個新的框架的起點
源代碼很好理解,一天內就能讀完(好吧這是個湊數的緣由)
事實證實,這是個還算不錯的選擇。
定下了基礎框架,接下來就是選擇核心的組件。首先就是 validator。不少人作系統並不重視 validator,或者沒有一個統一的視角去看待 validator,這樣很差。任何一個系統的運行環境都是個骯髒的世界,處處是魑魅魍魎,污穢不堪;而咱們但願系統自己是純淨的,是極樂淨土,那怎麼辦?
簡單,打造一堵嘆息的牆壁,擋住五小強
簡單,淨化輸入輸出。對於一個 API,什麼樣的 header,body 和 querystring 是被容許的?什麼樣的 response body 是合格的?這個須要定義清楚。因此咱們須要一個合適的 validator。若是說挑框架似四郎選秀女,環肥燕瘦讓你眼花繚亂,選 validator 就像姜維點將,看來看去只有王平廖化堪堪可用。在 github 裏逛了半天,最後能落入法眼的也只有 joi 和 json schema 可用。
json schema 其實很好用,很貼近各種 API 工具的 schema(swagger 直接就是用 json schema),惋惜太 verbose,讓程序員寫這個有點太囉嗦:
而 joi 是 hapi 提供的 validator,接口很人性化,相同的 schema,描述起來代碼量只有前者的 1/3:
並且它還能夠比較容易地逆向輸出(固然,須要各類適配)成 json schema。輸出成 json schema 有什麼好處?能夠用來生成 swagger doc!swagger 是一種 API 描述語言,能夠定義客戶端和服務器之間的協議。swagger doc 能夠生成 API 的文檔和測試UI,好比說:
在接下來的文章中,我會詳細介紹 swagger。
咱們再看 ORM。常用 express 的同窗應該瞭解,express 自己並不對你如何存取數據有過多幹涉,任何人均可以按照本身的需求使用其所須要的數據訪問方式:能夠是 raw db access,也可使用 ORM。這種靈活性在團隊協做的時候是種傷害,它讓你們很容易寫出來風格很不統一的代碼,並且,在寫入數據庫和從數據庫中讀取數據的 normalization,離了 ORM 也會帶來不少 ad-hoc 的代碼。所以,儘管 ORM 揹負着不少罵名,我仍是但願在涉及數據訪問的層面,使用 ORM。
咱們的系統的數據庫是異構的,所以,純種的,只對一類數據庫有效的 ORM,如 Mongoose / Sequelize 就不太合適,上上之選是接口支持多種不一樣數據庫,在須要特殊查詢或者操做的時候還能轉 native 的 ORM。這樣,讓工程師的效率和系統的效率達到一個平衡。在 node.js 下,這樣的 ORM 很少,可用的彷佛只有 waterline。waterline 是 sails.js 開源的一個 ORM,支持多種 db 的混合使用,在各個數據庫沒法統一的操做接口上(好比 mongodb 的 upsert),你能夠方便地將其生成的 model 轉 native,直接使用數據庫的接口。
此外,waterline 的 model 的 schema 使用 json 來描述,這使得它能夠很方便地轉化成 joi schema,在系統的進出口進行 validation。
接下來是日誌系統。一套 API 系統可能包含多臺服務器,因此日誌須要集中收集,處理和可視化。通常而言,咱們能夠用 ELK,或者第三方的服務。若是在設計系統之初就考慮日誌的集中管理,那麼日誌的收集應該考慮用結構化的結構,而非字符串。字符串儘管可使用 grok 來處理,但畢竟效率低,還得爲每種日誌寫 grok 的表達式。因爲 node restify 缺省使用 bunyan 做日誌,而 bunyan 能夠生成 json 格式的日誌,所以直接知足咱們的需求。
最後咱們再看 test framework。一個合格的系統離不開一套合適的 test framework。個人選擇是 ava / rewire / supertest / nyc。ava 是一個 unit test framwork,和 mocha / tape 等常見的 test framework 相似,解決相同的問題,不過 ava 可以併發執行,效率很高,並且對 es6 支持很棒,test case 能夠返回 Promise,ava 處理剩下的事情。有時候咱們須要測試一個模塊裏沒有 export 出來的函數,或者 Mock 一些測試時咱們並不關心的函數,rewire 能夠很方便地處理這樣的問題。supertest 能夠作 API 級別的測試,也就是 functional testing,而 nyc 能夠用來作 test coverage。