2001 年,我還在上大二時,微軟發(fā)布了 .Net framework 的第一個 RC。當(dāng)時 .Net 聲勢浩大,微軟在各個主要高校組織了夏令營,來選拔來年 Microsoft Asia 開發(fā)者大會的團(tuán)隊。當(dāng)時我印象深刻的是,跟隨著 .Net 一起新鮮出爐的 WSDL(Web Service Description Language),它「第一次」(也許)以公開協(xié)議的方式來描述通用的HTTP 客戶端和服務(wù)器之間的 API。在 WSDL 的約定下,API 的請求和響應(yīng)以 XML SOAP 的形式封裝。

在那個狂野的,沒有 API 的概念的時代,WSDL 簡直就是一股清流。可惜它思想太超前,服務(wù)描述太繁雜,使得一個非常簡單的 API 動輒生成成百上千行 XML 格式的 WSDL。在那個客戶端和服務(wù)器能力還十分有限的年代,WSDL 幾乎沒有激出任何水花,就被扔到了歷史的故紙堆中。

可能早期微軟把太多的賭注放在了曲高和寡的 WSDL(以及服務(wù)發(fā)現(xiàn)協(xié)議 UDDI)上,其主打做 web 開發(fā)的 ASP.Net 一直不溫不火,根本無法與紅遍天的 PHP 相提并論。在 2005 年之前,可以說,(在 web 世界里),PHP 是宇宙中最好的語言。

2005-2010:從混沌到有序 — Ruby on Rails 橫空出世

然而,成也蕭何敗也蕭何,脫胎于 Web 開發(fā)的 PHP,與 Web 的親和性是其優(yōu)勢,也是其后續(xù)沒落的原因 —— 畢竟,當(dāng) Web 軟件越來越復(fù)雜,需要跟越來越多的 web 以外的世界(比如操作系統(tǒng))打交道時,跟其他通用的腳本語言,如 Python/Ruby 相比,PHP 就盡顯劣勢。

尤其是,當(dāng) Ruby on Rails(以下簡稱 rails)這個引領(lǐng)一個時代的 web 框架橫空出世后,PHP 尷尬的發(fā)現(xiàn),自己的優(yōu)勢,可能就只剩下多年來積攢的生態(tài)系統(tǒng),以及在這個生態(tài)下滋養(yǎng)著的一大堆開發(fā)者了。

rails 是一個足以載入史冊的框架:它把軟件開發(fā)中的很多非常有益的概念、模式和思想(包括但不限于 ORM,CoC,MVC 等)糅合在自己體內(nèi),構(gòu)建了一個強(qiáng)大同時非常易用的 web 開發(fā)系統(tǒng)。在 rails 下,哪怕你是個 web 開發(fā)的小白,在學(xué)習(xí)了 rails 的開發(fā)文檔后,也能很快撰寫出一套讓很多 web 開發(fā)老鳥艷羨的系統(tǒng)。在 rails 諸多創(chuàng)新之中,要數(shù) ActiveRecord 最為經(jīng)驗,它以簡潔優(yōu)雅的表述,顛覆了人們傳統(tǒng)上對數(shù)據(jù)庫的認(rèn)知,并且?guī)缀鯌{借一己之力,把 ORM 捧上了神壇。

隨著 rails 一起成長的還有 XMLHttp object (俗稱 Ajax)的標(biāo)準(zhǔn)化,以及 JSON 的廣泛使用。其中,Google 通過其旗下的 gmail / google maps 大大促進(jìn)了人們對 Ajax 的認(rèn)知,而 PHP5 和 rails 3 則將 JSON 在廣大開發(fā)者中推廣開來,使其逐漸取代笨拙低效的 XML。有意思的是,Ajax 最初是 Asynchronous Javascript And XML,JSON 普及后,這個 XML 再也沒人提及。

rails 的成功催生了一系列迷弟迷妹 —— 各個語言的,無論是高仿 rails,或者受 rails 啟發(fā)的框架如雨后春筍般冒出,好不熱鬧。這其中,光是我深度使用過的框架就有:symfony,django 和 Phoenix framework。由 rails 刮起的 ORM 之風(fēng)愈演愈烈,它幾乎成為了 web 開發(fā)者訪問數(shù)據(jù)庫的唯一標(biāo)準(zhǔn)。漸漸的,存儲過程(stored procedure / function)被雪藏,觸發(fā)器(trigger)被遺忘,數(shù)據(jù)庫復(fù)雜而迷人的權(quán)限管理被棄之不顧,取而代之的是用一個幾乎具有 root 權(quán)限的用戶來連接數(shù)據(jù)庫,而權(quán)限的管理全部被前移到了應(yīng)用層。這和 ORM 所倡導(dǎo)的「一套代碼處理多種數(shù)據(jù)庫」有莫大的聯(lián)系。事實上,ORM 帶給大家切換數(shù)據(jù)庫的好處,可能僅限于開發(fā)環(huán)境用 sqlite,生產(chǎn)環(huán)境用 postgres 這樣的便利。但從管理的角度,ORM 讓開發(fā)者繞過 DBA(或者干脆不要 DBA)進(jìn)行快速開發(fā),對于小型項目,可以高效開發(fā),且不需要構(gòu)建數(shù)據(jù)庫領(lǐng)域的專有技能,畢竟培養(yǎng)一個 web 工程師,兩三個月的訓(xùn)練營就可以讓一個素人很好掌握開發(fā)框架,進(jìn)行「高效」 CRUD 開發(fā);而培養(yǎng)一個合格的 DBA,需要整個計算機(jī)體系知識的沉淀。早年間 DBA 還是個熱門的職位,后來在 rails 以及其一眾小弟的推波助瀾下,DBA 幾乎在中小型企業(yè)中銷聲匿跡。

2010-2015:移動互聯(lián)網(wǎng) — API 飛上枝頭變鳳凰

現(xiàn)在回過頭來看,2010年前后,也就是我創(chuàng)業(yè)做途客圈前后,算是近年來少有的互聯(lián)網(wǎng)創(chuàng)新領(lǐng)域集中爆發(fā)的幾年。這其中,最大的功臣要數(shù)喬幫主 2007 年推出的驚世駭俗的「三個」產(chǎn)品:初代 iPhone。它完全顛覆了我們對手機(jī)的認(rèn)知,顛覆了對輸入的認(rèn)知,以及,顛覆了我們的生活。

隨后,大獲成功的 iPhone 4(及 4s)真正把我們的生活扯入了移動互聯(lián)網(wǎng)時代 —— 作為當(dāng)時最成功最流行的 3G 手機(jī),iPhone 4讓移動應(yīng)用進(jìn)入到主流用戶的視野。由于移動應(yīng)用擁有自己的 UI 層,不像瀏覽器那樣,UI 層是由服務(wù)器返回的 HTML 渲染出來,因而移動端和服務(wù)器之間有著強(qiáng)烈的對簡潔高效且標(biāo)準(zhǔn)化的 API 層的需求。在這種需求的催生下,REST(Representational state transfer)這個當(dāng)時已不新鮮的概念漸漸從象牙塔走入了工業(yè)界。人們發(fā)現(xiàn),與其自己隨機(jī)指定一套 HTTP API 的規(guī)約,不如遵循 HTTP/1.1 規(guī)范,讓 API 的表述和規(guī)范靠攏。這個時期,各個框架要么開始內(nèi)建對 RESTful API 的支持,要么在框架之上,獨立出一套專門為 API 優(yōu)化的框架,比如 2012 年就比較成熟的 django REST framework:

也許是受到了移動互聯(lián)網(wǎng)的沖擊,也許是看到了客戶端和服務(wù)器彼此隔離帶來的巨大好處,web 開發(fā)也漸漸向 REST API 靠攏。在早期的 backbone.js 的引領(lǐng)下,web app 的 API 化在 react 發(fā)布后迅速升溫,并在后續(xù)的幾年得到了主流開發(fā)者的認(rèn)可。到目前為止,純服務(wù)器渲染返回 HTML 的 web 應(yīng)用可能只剩下半壁江山。

這個時期,如雨后春筍般綻放的眾多 REST API framework 給開發(fā)者帶來的巨大好處是,你即便不掌握 HTTP/1.1 協(xié)議的細(xì)節(jié),也可以做出像樣的 API,來處理客戶端和服務(wù)器間的交互。

然而,并不是所有的 API 框架都足夠嚴(yán)謹(jǐn),足夠遵循協(xié)議本身。很多 API 框架,在處理復(fù)雜的協(xié)議流程時,要么會有自相矛盾的處理,要么把這些細(xì)節(jié)完全交由開發(fā)者處理。然而,你如何保證只熱衷于進(jìn)行 CRUD 的開發(fā)者能夠正確使用 ETag 作為樂觀鎖(optimistic locking)進(jìn)行條件更新(conditional update)呢?

在 web 世界不為人知的角落,Erlang 的 webmachine 盡著最大的努力來確保 API 的處理符合 HTTP 協(xié)議。得益于 erlang 強(qiáng)大的 pattern matching 的能力,webmachine 在內(nèi)部構(gòu)建了一張龐大的決策樹,涵蓋了 API 處理的每一個細(xì)節(jié),連每個錯誤返回的狀態(tài)碼都精益求精。

我曾經(jīng)一度把玩過 liberator,相對于我當(dāng)時在生產(chǎn)環(huán)境使用的比較流行的 eve 和 django rest framework 來說, liberator 真的是優(yōu)秀很多。

然而,移動互聯(lián)網(wǎng)不是小眾語言和小眾框架的戰(zhàn)場。何況,API 畢竟是客戶端和服務(wù)器共同的約定,在那個年代,服務(wù)端的嚴(yán)謹(jǐn)會給客戶端帶來不小的困惑:相較于 412 Preconditional failed 而言,客戶端工程師更鐘情于一招鮮吃遍天的 400 Bad request。

?-2016:我的第一次 API 工具的探索

由于在途客圈和 Juniper web security team 有了不少對 API 開發(fā)的思考和沉淀,我一直有心做一個自己的 API 開發(fā)框架。在加入 Tubi,理順我們當(dāng)下的 API 結(jié)構(gòu)后,我便以 eve 和 liberator 為藍(lán)圖,nodejs restify 為基石,嘗試著構(gòu)建了一個 UAPI 系統(tǒng),目的是以 pipeline 的形式處理 API 的流程,讓公司的 nodejs 開發(fā)者只需要專注在業(yè)務(wù)邏輯,其它的交由框架完成:

UAPI 算是個成功的 API 系統(tǒng),它在 Tubi 一直使用了六年多,直到現(xiàn)在還在局部使用。對客戶端而言,它最大的好處是輸入和輸出都可以強(qiáng)制類型(如果定義了 validators 的話),這樣,不符合要求的輸入會在 API 處理流程很早的時候就被捕獲,進(jìn)而返回詳盡的錯誤。

在 UAPI 演進(jìn)的過程中,我也感受到了它的諸多局限和問題。其中最大的問題是:框架的使用者是開發(fā)者,而開發(fā)者如果沒有得到充足的培訓(xùn),會遺漏、誤用、濫用框架的某些能力。比如在 UAPI 中,API 的類型安全不是強(qiáng)制的,因而有的 API 在一開始對 Request 中的各個部分做了類型檢查,但隨著 API 的迭代,往往新添加的 HTTP 頭,并沒有妥善定義相應(yīng)的類型檢查,于是開發(fā)者在業(yè)務(wù)邏輯中東一塊西一塊做各種校驗,最終導(dǎo)致不優(yōu)雅的,甚至混亂的表達(dá)。

UAPI 的詳情我就不展開了,感興趣的可以參考我之前的系列文章:再談 API 的撰寫 – 架構(gòu)

也許在 UAPI 上我犯下的最大的錯誤,就是沒有強(qiáng)制類型檢查,把是否需要類型安全的選擇交給了開發(fā)者。

2015-2020:類型安全 — 新的共識

并不只有我自己有類型安全的切膚之痛,似乎整個行業(yè)都發(fā)現(xiàn)了 RESTful API 在這一點上的不完善。2015 年,facebook 首先用開源的內(nèi)部項目 GraphQL 向業(yè)界打出了意圖取代 RESTful API 的一記重拳。GraphQL 從輸入和輸出入手,在 HTTP 協(xié)議之上定義了一套查詢語言 —— 客戶端和服務(wù)器之間需要定義好支持的 query / mutation / subscription 的 schema,以及輸入和輸出數(shù)據(jù)結(jié)構(gòu)的 type。

GraphQL 提出了一個看待 API 的全新視角:客戶端使用者可以根據(jù)需要靈活定義他們想查詢的數(shù)據(jù),而不需要看服務(wù)端老爺們的臉色。在固執(zhí)的 RESTful API 的原教旨主義者眼里,API 應(yīng)該嚴(yán)格對應(yīng)資源,因而一個 app 頁面如果包含三種不同的資源,那么它就要訪問三個不同的 API 來獲得結(jié)果。對客戶端來說,這額外多了兩個浪費用戶寶貴等待時間的 roud trip,為什么不能一個查詢就獲得我想要的數(shù)據(jù),且僅包含我想要的數(shù)據(jù)呢?

這個想法很有創(chuàng)意,但它忽視了靈活性帶來的可能并不值得的復(fù)雜性。GraphQL 的理想情況一直沒有很好地達(dá)成,因為服務(wù)端不可能為一個多層隨意嵌套的查詢?nèi)?zhǔn)備數(shù)據(jù)。同時 GraphQL 還有其他很多設(shè)計上考慮不周的問題,其中最讓人詬病的是,對 HTTP 協(xié)議的無視,也就導(dǎo)致整個 HTTP 生態(tài)和 GraphQL 工作地很別扭,還有查詢時 n+1 的問題(data loader 只是個特定場景的解決辦法)。

其實仔細(xì)想想,GraphQL 并沒有領(lǐng)先到足以完全讓大家告別 RESTful API 的地步。其實 RESTful 服務(wù)器可以構(gòu)建 proxy API 來訪問若干其它 API,來解決一個 round trip 就能滿足客戶端的需求,同時也可以使用 partial response 來讓客戶端精確指定它想要的數(shù)據(jù)。這樣下來,GraphQL 最重要的優(yōu)勢便蕩然無存。

2016 年,google 開源了 gRPC。它使用 protobuf IDL 來解決輸入輸出的類型安全問題,并且采用 HTTP/2 來支持應(yīng)用層的多路復(fù)用(multiplex)。gRPC 在設(shè)計時瞄準(zhǔn)的就是 server-server 的使用場景,因而它可以使用二進(jìn)制數(shù)據(jù)來達(dá)到最好的效率。由于我們這里只著重談 client/server 的 API 演進(jìn),就不展開談 gRPC。

2017 年,OpenAPI v3 問世,REST 的世界終于也有了自己的類型安全。然而 OpenAPI 并不強(qiáng)制輸入輸出的類型安全,這跟 UAPI 有同樣的問題:隨著公司 OpenAPI spec 的不斷迭代,API 中某些新添加的字段,很容易被忽略,日積月累下來,問題會越來越多。

類型安全對 API 系統(tǒng)的意義不僅僅是輸入輸出有更加嚴(yán)格的校驗,錯誤的輸入能在很早的時候就被發(fā)現(xiàn)這么簡單。它還打開了一扇新的大門:代碼生成。無論是 GraphQL / gRPC,還是 OpenAPI,它們都可以根據(jù) schema 生成客戶端 SDK,甚至服務(wù)端的 stub 代碼。

當(dāng)然,寫 schema 本身是一件很痛苦的事情,尤其是對于我們這些能寫代碼就不想寫文檔的開發(fā)者。于是,人們開始追尋取巧的辦法:可不可以只寫代碼,然后通過代碼來生成相應(yīng)的 schema?

schema first, or code first, this is a question.

大部分支持 GraphQL 或者 OpenAPI 的框架遵從程序員的本性,讓你可以專注于寫代碼,順帶生成相應(yīng)的 schema。這是典型的 code first 的思維。

而 schema first 的代表要數(shù) gRPC —— 你撰寫 protobuf 定義,相應(yīng)的編譯器會替你生成代碼。

這兩種方案的背后,實際上是框架思維和編譯器思維的較量。

在我看來,code first 背后的框架思維,就像地心說,它一開始很簡單,很容易上手,但隨后你就不得不添加越來越多的本輪和均輪來對模型不斷校正,使其適應(yīng)在發(fā)展變化中的正確性的保證。

而 schema first 背后的編譯器思維,就像日心說,是「少有人走的路」,(因為要寫解析器或者編譯器)開頭異常艱難,但一旦成型,日后會越來越輕松,只需在不斷拓展編譯器的邊界。

2018:我的第二次 API 工具的探索

在使用過多種 code first 的框架來構(gòu)建 GraphQL / OpenAPI 的系統(tǒng)后,我開始構(gòu)思自己的下一個 API 開發(fā)工具:goldrin。

這一次,我的目標(biāo)是:

  1. 定義一門「語言」,來描述我們的 API
  2. 撰寫不同方向上的 Parser(Code generator),將其轉(zhuǎn)換成特定場景的代碼
  3. 將 Parser 構(gòu)建在 build pipeline 中,可以一次 build,生成各種結(jié)果
  4. 生成的結(jié)果要能很方便地擴(kuò)展,以及和系統(tǒng)里的其他部分整合

這可能是我在 arcblock 的征途中,除了 forge 框架外,另一個很有意義的成就。這個項目的目標(biāo)如此宏大,某種意義上說也是為了彌補(bǔ)開發(fā)人員的不足,所以很多時候,受限的資源反倒更能驅(qū)動創(chuàng)新。

在這個目標(biāo)的驅(qū)動下,goldrin 實現(xiàn)了從一個類似 ansible 的,用來描述數(shù)據(jù)類型以及在數(shù)據(jù)類型上允許進(jìn)行的操作的 schema,構(gòu)建出相應(yīng)的數(shù)據(jù)庫表的定義,GraphQL server 端實現(xiàn),以及文檔的定義。這套系統(tǒng)最大的好處是:無論是客戶端開發(fā)者,還是后端開發(fā)者,都可以撰寫幾十行 YAML 就得到一個可以運行的,和數(shù)據(jù)庫緊密連接的 API playground。然后你可以在此基礎(chǔ)上不斷調(diào)整,讓 API 從原型一步步走到令人滿意的,可發(fā)布的版本,期間幾乎不用撰寫代碼(可能需要簡單的 mock resolver)。當(dāng) API 的接口成型后,我們可以再撰寫代碼,重載特定的 resolver,使其擁有更高效,更優(yōu)雅的實現(xiàn)。

如果大家對此感興趣,可以 google:Use goldorin to build absinthe ecto enabled GraphQL APIs,看看我在 2018 年 ElixirConf 上的 lightening talk。很遺憾的是,由于當(dāng)時我還想在 goldrin 中提供對 gRPC 的支持后再開源,導(dǎo)致這一項目一直沒有開源,直到我離開。對于這個項目,我沒有像 UAPI 那樣留下一個系列文章,只有一篇短文:思考,問題和方法

2020:我的第三次 API 工具的探索

如果說 goldrin 是一個被外部環(huán)境倒逼出來的急中生智,quenya,則更多像是我在無拘無束的條件下,把我之前做過的諸多系統(tǒng)回溯一下,集大成的找樂子項目。這一次,我試圖從 OpenAPI v3 spec 出發(fā),構(gòu)建一切可以自動化生成的代碼,甚至包括 API 的測試。

quenya 說實話,在思路上并沒有比 goldrin 進(jìn)步多少,它的核心還是一個編譯器,從 OpenAPI 中挖掘各種有用的信息,生成相應(yīng)的代碼。對此感興趣的同學(xué),可以看我的這個系列文章:構(gòu)建下一代 HTTP API – 總覽

2020-至今:低代碼時代 — API 何去何從?

讓我們快進(jìn)到 2020 年。

低代碼開發(fā)平臺(Low-code development platform)雖然在 2014 年就被作為一個正式的名稱被提出,但其開始打開局部市場,獲得大規(guī)模融資,以及進(jìn)一步的發(fā)展,也就是近兩年的事情。低代碼的概念其實一直挺模糊,但大家的共識是:用戶可以無需太多編碼,通過「描述」其需求(可以在 GUI 上操作,也可以是撰寫某種簡單易懂的 schema),就能夠構(gòu)建完全可使用的應(yīng)用軟件。低代碼描繪了一個程序員之外的更廣泛的人群可以構(gòu)建應(yīng)用程序的美好世界。

然而,有應(yīng)用程序的地方,就需要 API,而構(gòu)建 API,則離不開開發(fā)者的參與。雖然過去二十年,API 開發(fā)的自動化程度已經(jīng)大大提升,但我們還沒有到達(dá)一個可以完全自動生成 API 的階段。這還怎么低代碼?

如果我們重新審視 API 的作用,我們會發(fā)現(xiàn),作為客戶端和服務(wù)端數(shù)據(jù)的橋梁,API 解析客戶端的請求,從服務(wù)端某個 data store(可能是數(shù)據(jù)庫,也可能是其他服務(wù)的數(shù)據(jù)等),獲取相應(yīng)的數(shù)據(jù),然后按照 API 的約定返回合適的結(jié)果。

既然 API 的目的是提供數(shù)據(jù),而數(shù)據(jù)往往有其嚴(yán)苛的 schema,同時 API 的 schema 大多數(shù)時候就是數(shù)據(jù) schema 的子集,那么,我們是不是可以從數(shù)據(jù) schema 出發(fā),反向生成 API 呢?

乍一看,這個思路和我之前做的 goldrin 類似,但 goldrin 定義了新的「語言」,由外及內(nèi)地生成 API 以及數(shù)據(jù)的 schema,而這個想法是,以數(shù)據(jù)庫 schema 為單一數(shù)據(jù)來源,由內(nèi)及外地生成 API schema,甚至 API 本身。

這并不是一個新的思想,早在 2015 年,postgREST 就開展了類似的嘗試,只是這種離經(jīng)叛道的思路和那個 ORM 還如日中天的時代格格不入。在 DBA 幾乎絕跡于江湖后,有哪個初創(chuàng)企業(yè)會把自己的后端圍繞著一個特定的數(shù)據(jù)庫(postgres)構(gòu)建,并且?guī)缀跤帽M這個數(shù)據(jù)庫每一個非標(biāo)準(zhǔn)的功能,完全不考慮可遷移性呢?

再加上 postgREST 是用 haskell 這樣一門小眾的語言開發(fā),更使得好奇它的人多,而使用它的人少之又少。

簡單介紹一下 postgREST 的思路。使用 postgREST,開發(fā)者只需正常定義數(shù)據(jù)庫中的表,視圖,函數(shù),觸發(fā)器等,并為它們的使用權(quán)限賦予相應(yīng)的角色即可。postgREST 可以根據(jù)數(shù)據(jù)庫的 infoschema,掌握詳細(xì)的 metadata,并用這些 metadata 來驗證 API 的輸入,也就是 Request,如果驗證通過,會根據(jù) Request 生成相應(yīng)的 SQL 查詢,然后把結(jié)果序列化成客戶端需要的結(jié)構(gòu),以 Response 返回。舉個例子,對于這樣一個 API 請求:GET /people?age=gte.18&student=is.true ,postgREST 會驗證數(shù)據(jù)庫中包含 people 表或者視圖,并且其含有 age / student 這兩個字段,前者是整型,后者是布爾型。如果一切符合,并且用戶具備 people 表或者視圖的 SELECT 權(quán)限,那么它就會生成 select * from people where age >= 18 and student = true 這樣一條查詢,返回相應(yīng)的 JSON(默認(rèn)客戶端 accept: application/json)。

postgREST 還跟 postgres 的 RLS(Row Level Security)深度綁定,來解決用戶個人信息安全訪問和更新的需求。比如用戶只能修改自己的帖子,但可以讀別人的帖子這樣的業(yè)務(wù)需求,如果沒有 RLS,很難從數(shù)據(jù)庫級別直接安全地實現(xiàn)。

postgREST 這樣一個小眾的工具進(jìn)入到很多人的視野,還要歸功于 supabase B 輪八千萬美金的巨額融資。它為 postgREST 提供了 GUI,搖身一變成為 firebase 的挑戰(zhàn)者,DBaaS 新生代的翹楚。

另一個有著同樣思路,但采取了不同路徑的產(chǎn)品 Hasura,今年早些時候 C 輪融了一億美金。與 supabase 背后的 postgREST 不同的是,Hasura 把寶押在了 GraphQL。Hasura 試圖回答一個問題:有沒有可能把 GraphQL 的 query 一對一轉(zhuǎn)換成 SQL 語句?

我們知道 GraphQL 查詢會被編譯成 Graph AST,而 SQL 查詢會被編譯成 SQL AST,所以上述那個問題就變?yōu)椋篏raph AST 可以被安全高效地轉(zhuǎn)換成 SQL AST 么?

看看 Hasura 的天量融資,你就可以猜到,這條路走得通。撰寫自己的編譯器雖然是一條「少有人走的路」,但一旦走通,其迸發(fā)的能量是巨大的,而且有意想不到的效果。前面提到的 GraphQL 令人詬病的 n+1 的問題,在 Hasura 面前都不是是個事,因為引發(fā) n+1 問題的嵌套查詢,翻譯成 SQL 就是一個 INNER JOIN,于是 n+1 問題就這么被悄無聲息地解決了。

那么,Hasura 是如何實現(xiàn)這一切的呢?我并沒有深入研究,然而當(dāng)我打開 Hasura graphql-engine 的源碼,驚奇的發(fā)現(xiàn),除了 20 多萬行 typescript/javascript 代碼,和 3 萬多行 golang 代碼外,它還有 13 萬行的 Haskell 代碼。莫非,Hasura 也從 postgREST 那里「偷師」?稍稍查詢一下,發(fā)現(xiàn)代碼中確實有一些 postgREST 的痕跡。

2022:我的第四次 API 工具的探索(頭腦風(fēng)暴)

在仔細(xì)研讀了 postgREST 的用戶文檔后,我大概摸清了它的產(chǎn)品思路。于是我一時技癢,展開頭腦風(fēng)暴,思考如果做一個類似的工具,我該怎么做?

首先,我并不喜歡 postgREST 的查詢方式,它的 DSL 在我看來有些蹩腳。我希望通過 x-fields 和 x-filter 這兩個 HTTP 頭,來實現(xiàn) postgREST 里 querystring 所表達(dá)的內(nèi)容:

對于 x-fields,它有略微復(fù)雜的,但繼承自 postgREST 的字段選擇語法,我可以使用一個 parser combinator(比如 Rust 下的 nom)來解析它,這樣就可以清晰地知道,字段名如何重命名,以及字段來自于哪張表(如果有 JOIN 的話)。x-filter 我還沒想好如何表述,但我覺得 SQL 中的表達(dá)式就夠用了。對于 x-filter,我們可以也用 parser combinator 來解析,或者干脆使用某個SQL 解析器(比如 Rust 下的 sqlparser)解析。解析出來的 metadata 可以和數(shù)據(jù)庫中的 infoschema 比對,來驗證請求的合法性,這一點和 postgREST 完全一致。最終,從 x-fields / x-filter 中解析出來的內(nèi)容,連同 rang 頭(用于分頁)一起,就可以構(gòu)建出一個完整的,合法的 SQL 查詢,最終得到返回的結(jié)果。

到目前為止,這個系統(tǒng)和 postgREST 如出一轍,沒什么了不起的。

平心而論,我覺得這樣的 API 系統(tǒng),用于內(nèi)部系統(tǒng),還說得過去,但用于外部系統(tǒng),就過于暴露數(shù)據(jù) schema 的細(xì)節(jié),同時讓 API 的接口和數(shù)據(jù)本身過于耦合。這樣一來帶有安全隱患(很容易被嗅探),二來不利于在 API 接口保持不變的情況下升級數(shù)據(jù) schema。對此,postgREST 給出的答案是使用 view 來隔離 table schema 的細(xì)節(jié),但我覺得還不夠完善。我需要一個能夠在外部看來,更加自然,更加簡單的 API。

在計算機(jī)的世界里,這樣的問題往往可以通過添加一個新的層級來實現(xiàn)。我并不需要改動已有的設(shè)計 —— 它對于內(nèi)部系統(tǒng)來說還是相當(dāng)不錯的設(shè)計,我只需要在這個設(shè)計之上,迭加一層。于是我有了這樣的思路:

開發(fā)者可以使用 CREATE API(我胡謅的新 SQL 語法) 來創(chuàng)建一個 API 的描述。這個 todos API,包含兩個參數(shù):來自 auth header 的 jwt token,以及來自 querystring 里的 completed。API 的 metadata 中包含了一些詳盡的配置,以及 API 的參數(shù)如何作用到配置中。有了這樣的一種 API 配置,用戶可以用圖中更自然地方式訪問 API,而 API 自身沒有暴露任何數(shù)據(jù)庫的邏輯。

整個過程,比之前的方案多了個 API 的定義過程,由于使用的是描述性語言,所以,很難誤用,并且以后還能很方便的用 GUI 來表述,也算是一種低代碼了。

看到這里,有經(jīng)驗的同學(xué)可能會質(zhì)疑:API 的數(shù)據(jù)源又不止于數(shù)據(jù)庫,如果數(shù)據(jù)來源于 gRPC 服務(wù)器,那又該如何?

好問題!此刻我們需要修改 CREATE API 的描述,使其明確表達(dá)其數(shù)據(jù)源是什么。在下圖的例子里,數(shù)據(jù)源是 grpc_todos:

而 grpc_todos 由 CREATE SOURCE 來定義:

CREATE SOURCE grpc_todos WITH JSON({
"source": "wasm",
"wasm": {
"lib": "todo.wasm",
"fn": {
"name": "get_todos",
"args": [...],
"return": ".."
}
}
});

再一次地,我們看到,使用編譯器的思路去解決問題,是多么地舒服:我們可以不斷擴(kuò)展新的語法,撰寫新的解析器去處理問題。這非常符合 open-close 原則。

這里 source 我使用 webassembly,并不是為了裝 B,而是我希望這樣的工具就像 postgREST 一樣,你不需要,也無法對其二次開發(fā)。如果需要擴(kuò)展,那么 webassembly 或者 JS 就是最佳的選擇。它可以集成 wasmtime 來處理 webassembly,也可以集成 deno_core 來安全地支持 typescript/javascript 擴(kuò)展。

以上關(guān)于第四次 API 工具的探索的一切不靠譜的想法,都只存在于我的腦海中,我的 excalidraw,以及我的 PPT 里。

本來這篇文章應(yīng)該在上周末發(fā)表出來,可是我一時技癢,把周末可用的時間勻給了代碼實現(xiàn),于是我在撰寫了(主要是通過 psql -E 偷師 psql 命令是如何查詢的)上百行 SQL,從postgres 中獲取關(guān)于 relation / function / columns / constraints 的 infoschema,將它們構(gòu)建成 materialized view,然后利用這一信息,自動構(gòu)建了簡單的,沒有任何安全限制的 API

本文章轉(zhuǎn)載微信公眾號@程序人生

上一篇:

2025年AIAgent開發(fā)框架怎么選?

下一篇:

從0到1搭建本地RAG問答系統(tǒng):Langchain+Ollama+RSSHub技術(shù)全解析
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費