阿里研究员谷朴:API 设计最佳实践的思考
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
API是軟件系統(tǒng)的核心,而軟件系統(tǒng)的復(fù)雜度Complexity是大規(guī)模軟件系統(tǒng)能否成功最重要的因素。但復(fù)雜度Complexity并非某一個(gè)單獨(dú)的問題能完全敗壞的,而是在系統(tǒng)設(shè)計(jì)尤其是API設(shè)計(jì)層面很多很多小的設(shè)計(jì)考量一點(diǎn)點(diǎn)疊加起來的(也即John Ousterhout老爺子說的Complexity is incremental【8】)。成功的系統(tǒng)不是有一些特別閃光的地方,而是設(shè)計(jì)時(shí)點(diǎn)點(diǎn)滴滴的努力積累起來的。
因此,這里我們試圖思考并給出建議,一方面,什么樣的API設(shè)計(jì)是__好__的設(shè)計(jì)?另一方面,在設(shè)計(jì)中如何能做到?
API設(shè)計(jì)面臨的挑戰(zhàn)千差萬別,很難有處處適用的準(zhǔn)則,所以在討論原則和最佳實(shí)踐時(shí),無論這些原則和最佳實(shí)踐是什么,一定有適應(yīng)的場景和不適應(yīng)的場景。因此我們在下面爭取不僅提出一些建議,也盡量去分析這些建議在什么場景下適用,這樣我們也可以有針對性的采取例外的策略。
范圍
本文偏重于__一般性的API設(shè)計(jì)__,__并更適用于遠(yuǎn)程調(diào)用(RPC或者HTTP/RESTful的API)__,但是這里沒有特別討論RESTful API特有的一些問題。
另外,本文在討論時(shí),假定了客戶端直接和遠(yuǎn)程服務(wù)端的API交互。在阿里,由于多種原因,通過客戶端的SDK來間接訪問遠(yuǎn)程服務(wù)的情況更多一些。這里并不討論SDK帶來的特殊問題,但是將SDK提供的方法看作遠(yuǎn)程API的代理,這里的討論仍然適用。
API設(shè)計(jì)準(zhǔn)則:什么是好的API
在這一部分,我們試圖總結(jié)一些好的API應(yīng)該擁有的特性,或者說是設(shè)計(jì)的原則。這里我們試圖總結(jié)更加基礎(chǔ)性的原則。所謂基礎(chǔ)性的原則,是那些如果我們很好的遵守了就可以讓API在之后演進(jìn)的過程中避免多數(shù)設(shè)計(jì)問題的原則。
A good API
- __提供清晰的思維模型 provides a good mental model__:API是用于程序之間的交互,但是一個(gè)API如何被使用,以及API本身如何被維護(hù),是依賴于維護(hù)者和使用者能夠?qū)υ揂PI有清晰的、一致的認(rèn)識(shí)。這種狀況實(shí)際上是不容易達(dá)到的。
- __簡單 is simple__:“Make things as simple as possible, but no simpler.” 在實(shí)際的系統(tǒng)中,尤其是考慮到系統(tǒng)隨著需求的增加不斷的演化,我們絕大多數(shù)情況下見到的問題都是__過于復(fù)雜__的設(shè)計(jì),而非過于簡單,因此強(qiáng)調(diào)簡單性一般是恰當(dāng)?shù)摹?/li>
- __容許多個(gè)實(shí)現(xiàn) allows multiple implementations__:這個(gè)原則看上去更具體,但是這是我非常喜歡的一個(gè)原則。這是Sanjay Ghemawat常常提到的一個(gè)原則。一般來說,在討論API設(shè)計(jì)時(shí)常常被提到的原則是解耦性原則或者說松耦合原則。然而相比于松耦合原則,這個(gè)原則更加有可操作性:如果一個(gè)API自身可以有多個(gè)__完全不同的實(shí)現(xiàn)__,一般來說這個(gè)API已經(jīng)有了足夠好的抽象,和自身的某一個(gè)具體實(shí)現(xiàn)無關(guān),那么一般也不會(huì)出現(xiàn)和外部系統(tǒng)耦合過緊的問題。因此這個(gè)原則更本質(zhì)一些。
最佳實(shí)踐
本部分則試圖討論一些更加詳細(xì)、具體的建議,可以讓API的設(shè)計(jì)更容易滿足前面描述的基礎(chǔ)原則。
想想優(yōu)秀的API例子:POSIX File API
如果說API的設(shè)計(jì)實(shí)踐只能列一條的話,那么可能最有幫助的和最可操作的就是這一條。本文也可以叫做“通過File API體會(huì)API設(shè)計(jì)的最佳實(shí)踐”。
所以整個(gè)最佳實(shí)踐可以總結(jié)為一句話:“想想File API是怎么設(shè)計(jì)的。”
首先回顧一下File API的主要接口(以C為例,很多是Posix API,選用比較簡單的I/O接口為例【1】:
int open(const char *path, int oflag, .../*,mode_t mode */); int close (int filedes); int remove( const char *fname ); ssize_t write(int fildes, const void *buf, size_t nbyte); ssize_t read(int fildes, void *buf, size_t nbyte);File API為什么是經(jīng)典的好API設(shè)計(jì)?
- File API已經(jīng)有幾十年歷史(從1988年算起將近40年),盡管期間硬件軟件系統(tǒng)的發(fā)展經(jīng)歷了好幾代,這套API核心保持了穩(wěn)定。這是極其了不起的。
- API提供了非常清晰的概念模型,每個(gè)人都能夠很快理解這套API背后的基礎(chǔ)概念:什么是文件,以及相關(guān)聯(lián)的操作(open, close, read, write),清晰明了;
- 支持很多的不同文件系統(tǒng)實(shí)現(xiàn),這些系統(tǒng)實(shí)現(xiàn)甚至于屬于類型非常不同的設(shè)備,例如磁盤、塊設(shè)備、管道(pipe)、共享內(nèi)存、網(wǎng)絡(luò)、終端terminal等等。這些設(shè)備有的是隨機(jī)訪問的,有的只支持順序訪問;有的是持久化的有的則不是。然而所有不同的設(shè)備不同的文件系統(tǒng)實(shí)現(xiàn)都可以采用了同樣的接口,使得上層系統(tǒng)不必關(guān)注底層實(shí)現(xiàn)的不同,這是這套API強(qiáng)大的生命力的表現(xiàn)。
例如同樣是打開文件的接口,底層實(shí)現(xiàn)完全不同,但是通過完全一樣的接口,不同的路徑以及Mount機(jī)制,實(shí)現(xiàn)了同時(shí)支持。其他還有Procfs, pipe等。
int open(const char *path, int oflag, .../*,mode_t mode */);例如這里的cephfs和本地文件系統(tǒng),底層對應(yīng)完全不同的實(shí)現(xiàn),但是上層client可以不用區(qū)分對待,采用同樣的接口來操作,只通過路徑不同來區(qū)分。
基于上面的這些原因,我們知道File API為什么能夠如此成功。事實(shí)上,它是如此的成功以至于今天的*-nix操作系統(tǒng),everything is filed based.
盡管我們有了一個(gè)非常好的例子File API,但是__要設(shè)計(jì)一個(gè)能夠長期保持穩(wěn)定的API是一項(xiàng)及其困難的事情__,因此僅有一個(gè)好的參考還不夠,下面再試圖展開去討論一些更細(xì)節(jié)的問題。
Document well 寫詳細(xì)的文檔
寫詳細(xì)的文檔,并保持更新。 關(guān)于這一點(diǎn),其實(shí)無需贅述,現(xiàn)實(shí)是,很多API的設(shè)計(jì)和維護(hù)者不重視文檔的工作。
在一個(gè)面向服務(wù)化/Micro-service化架構(gòu)的今天,一個(gè)應(yīng)用依賴大量的服務(wù),而每個(gè)服務(wù)API又在不斷的演進(jìn)過程中,__準(zhǔn)確的記錄每個(gè)字段和每個(gè)方法,并且保持更新__,對于減少客戶端的開發(fā)踩坑、減少出問題的幾率,提升整體的研發(fā)效率至關(guān)重要。
Carefully define the "resource" of your API 仔細(xì)的定義“資源”
如果適合的話,選用“資源”加操作的方式來定義。今天很多的API都可以采用這樣一個(gè)抽象的模式來定義,這種模式有很多好處,也適合于HTTP的RESTful API的設(shè)計(jì)。但是在設(shè)計(jì)API時(shí),一個(gè)重要的前提是對Resource本身進(jìn)行合理的定義。什么樣的定義是合理的?Resource資源本身是對一套API操作核心對象的一個(gè)抽象Abstraction。
抽象的過程是__去除細(xì)節(jié)的過程__。在我們做設(shè)計(jì)時(shí),如果現(xiàn)實(shí)世界的流程或者操作對象是具體化的,抽象的Object的選擇可能不那么困難,但是對于哪些細(xì)節(jié)應(yīng)該包括,是需要很多思考的。例如對于文件的API,可以看出,文件File這個(gè)Resource(資源)的抽象,是“可以由一個(gè)字符串唯一標(biāo)識(shí)的數(shù)據(jù)記錄”。這個(gè)定義去除了文件是如何標(biāo)識(shí)的(這個(gè)問題留給了各個(gè)文件系統(tǒng)的具體實(shí)現(xiàn)),也去除了關(guān)于如何存儲(chǔ)的組織結(jié)構(gòu)(again,留給了存儲(chǔ)系統(tǒng))細(xì)節(jié)。
雖然我們希望API簡單,但是更重要的是__選擇對的實(shí)體來建模__。在底層系統(tǒng)設(shè)計(jì)中,我們傾向于更簡單的抽象設(shè)計(jì)。有的系統(tǒng)里面,域模型本身的設(shè)計(jì)往往不會(huì)這么簡單,需要更細(xì)致的考慮如何定義Resource。一般來說,域模型中的概念抽象,如果能和現(xiàn)實(shí)中的人們的體驗(yàn)接近,會(huì)有利于人們理解該模型。__選擇對的實(shí)體來建模__往往是關(guān)鍵。結(jié)合域模型的設(shè)計(jì),可以參考相關(guān)的文章,例如阿白老師的文章【2】。
Choose the right level of abstraction 選擇合適的抽象層
與前面的一個(gè)問題密切相關(guān)的,是在定義對象時(shí)需要選擇合適的Level of abstraction(抽象的層級(jí))。不同概念之間往往相互關(guān)聯(lián)。仍然以File API為例。在設(shè)計(jì)這樣的API時(shí),選擇抽象的層級(jí)的可能的選項(xiàng)有多個(gè),例如:
- 文本、圖像混合對象
- “數(shù)據(jù)塊” 抽象
- ”文件“抽象
這些不同的層級(jí)的抽象方式,可能描述的是同一個(gè)東西,但是在概念上是不同層面的選擇。當(dāng)設(shè)計(jì)一個(gè)API用于與數(shù)據(jù)訪問的客戶端交互時(shí),“文件File“是更合適的抽象,而設(shè)計(jì)一個(gè)API用于文件系統(tǒng)內(nèi)部或者設(shè)備驅(qū)動(dòng)時(shí),數(shù)據(jù)塊或者數(shù)據(jù)塊設(shè)備可能是合適的抽象,當(dāng)設(shè)計(jì)一個(gè)文檔編輯工具時(shí),可能會(huì)用到“文本圖像混合對象”這樣的文件抽象層級(jí)。
又例如,數(shù)據(jù)庫相關(guān)的API定義,底層的抽象可能針對的是數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu),中間是數(shù)據(jù)庫邏輯層需要定義數(shù)據(jù)交互的各種對象和協(xié)議,而在展示(View layer)的時(shí)候需要的抽象又有不同【3】。
Prefer using different model for different layers 不同層建議采用不同的數(shù)據(jù)模型
這一條與前一條密切關(guān)聯(lián),但是強(qiáng)調(diào)的是不同層之間模型不同。
在服務(wù)化的架構(gòu)下,數(shù)據(jù)對象在處理的過程中往往經(jīng)歷多層,例如上面的View-Logic model-Storage是典型的分層結(jié)構(gòu)。在這里我們的建議是不同的Layer采用不同的數(shù)據(jù)結(jié)構(gòu)。John Ousterhout 【8】書里面則更直接強(qiáng)調(diào):Different layer, different abstraction。
例如網(wǎng)絡(luò)系統(tǒng)的7層模型,每一層有自己的協(xié)議和抽象,是個(gè)典型的例子。而前面的文件API,則是一個(gè)Logic layer的模型,而不同的文件存儲(chǔ)實(shí)現(xiàn)(文件系統(tǒng)實(shí)現(xiàn)),則采用各自獨(dú)立的模型(如快設(shè)備、內(nèi)存文件系統(tǒng)、磁盤文件系統(tǒng)等各自有自己的存儲(chǔ)實(shí)現(xiàn)API)。
當(dāng)API設(shè)計(jì)傾向于不同的層采用一樣的模型的時(shí)候(例如一個(gè)系統(tǒng)使用后段存儲(chǔ)服務(wù)與自身提供的模型之間,見下圖),可能意味著這個(gè)Service本身的職責(zé)沒有定義清楚,是否功能其實(shí)應(yīng)該下沉?
不同的層采用同樣的數(shù)據(jù)結(jié)構(gòu)帶來的問題還在于API的演進(jìn)和維護(hù)過程。一個(gè)系統(tǒng)演進(jìn)過程中可能需要替換掉后端的存儲(chǔ),可能因?yàn)樾阅軆?yōu)化的關(guān)系需要分離緩存等需求,這時(shí)會(huì)發(fā)現(xiàn)將兩個(gè)層的數(shù)據(jù)綁定一起(甚至有時(shí)候直接把前端的json存儲(chǔ)在后端),會(huì)帶來不必要的耦合而阻礙演進(jìn)。
Naming and identification of the resource 命名與標(biāo)識(shí)
當(dāng)API定義了一個(gè)資源對象,下面一般需要的是提供命名/標(biāo)識(shí)(Naming and identification)。在naming/ID方面,一般有兩個(gè)選擇(不是指系統(tǒng)內(nèi)部的ID,而是會(huì)暴露給用戶的):
- 用free-form string作為ID(string nameAsId)
- 用結(jié)構(gòu)化數(shù)據(jù)表達(dá)naming/ID
何時(shí)選擇哪個(gè)方法,需要具體分析。采用Free-form string的方式定義的命名,為系統(tǒng)的具體實(shí)現(xiàn)留下了最大的自由度。帶來的問題是命名的內(nèi)在結(jié)構(gòu)(如路徑)本身并非API強(qiáng)制定義的一部分,轉(zhuǎn)為變成實(shí)現(xiàn)細(xì)節(jié)。如果命名本身存在結(jié)構(gòu),客戶端需要有提取結(jié)構(gòu)信息的邏輯。這是一個(gè)需要做的平衡。
例如文件API采用了free-form string作為文件名的標(biāo)識(shí)方式,而文件的URL則是文件系統(tǒng)具體實(shí)現(xiàn)規(guī)定。這樣,就容許Windows操作系統(tǒng)采用"D:\Documents\File.jpg"而Linux采用"/etc/init.d/file.conf"這樣的結(jié)構(gòu)了。而如果文件命名的數(shù)據(jù)結(jié)構(gòu)定義為
{disk: string,path: string }這樣結(jié)構(gòu)化的方式,透出了"disk"和"path"兩個(gè)部分的結(jié)構(gòu)化數(shù)據(jù),那么這樣的結(jié)構(gòu)可能適應(yīng)于Windows的文件組織方式,而不適應(yīng)于其他文件系統(tǒng),也就是說泄漏了實(shí)現(xiàn)細(xì)節(jié)。
如果資源Resource對象的抽象模型自然包含結(jié)構(gòu)化的標(biāo)識(shí)信息,則采用結(jié)構(gòu)化方式會(huì)簡化客戶端與之交互的邏輯,強(qiáng)化概念模型。這時(shí)犧牲掉標(biāo)識(shí)的靈活度,換取其他方面的優(yōu)勢。例如,銀行的轉(zhuǎn)賬賬號(hào)設(shè)計(jì),可以表達(dá)為
{account: numberrouting: number }這樣一個(gè)結(jié)構(gòu)化標(biāo)識(shí),由賬號(hào)和銀行間標(biāo)識(shí)兩部分組成,這樣的設(shè)計(jì)含有一定的業(yè)務(wù)邏輯在內(nèi),但是這部分業(yè)務(wù)邏輯是__被描述的系統(tǒng)內(nèi)在邏輯而非實(shí)現(xiàn)細(xì)節(jié)__,并且這樣的設(shè)計(jì)可能有助于具體實(shí)現(xiàn)的簡化以及避免一些非結(jié)構(gòu)化的字符串標(biāo)識(shí)帶來的安全性問題等。因此在這里結(jié)構(gòu)化的標(biāo)識(shí)可能更適合。
另一個(gè)相關(guān)的問題是,__何時(shí)應(yīng)該提供一個(gè)數(shù)字unique ID?__ 這是一個(gè)經(jīng)常遇到的問題。有幾個(gè)問題與之相關(guān)需要考慮:
- 是否已經(jīng)有結(jié)構(gòu)化或者字符串的標(biāo)識(shí)可以唯一、穩(wěn)定標(biāo)識(shí)對象?如果已經(jīng)有了,那么就不一定需要numerical ID;
- 64位整數(shù)范圍夠用嗎?
- 數(shù)字ID可能不是那么用戶友好,對于用戶來講數(shù)字的ID會(huì)有幫助嗎?
如果這些問題都有答案而且不是什么阻礙,那么使用數(shù)字ID是可以的,__否則要慎用數(shù)字ID__。
Conceptually what are the meaningful operations on this resource? 對于該對象來說,什么操作概念上是合理的?
在確定下來了資源/對象以后,我們還需要定義哪些操作需要支持。這時(shí),考慮的重點(diǎn)是“__概念上合理(Conceptually reasonable)__”。換句話說,operation + resource?連在一起聽起來自然而然合理(如果Resource本身命名也比較準(zhǔn)確的話。當(dāng)然這個(gè)“如果命名準(zhǔn)確”是個(gè)big if,非常不容易做到)。操作并不總是CRUD(create, read, update, delete)。
例如,一個(gè)API的操作對象是額度(Quota),那么下面的操作聽上去就比較自然:
- Update quota(更新額度),transfer quota(原子化的轉(zhuǎn)移額度)
但是如果試圖Create Quota,聽上去就不那么自然,因額度這樣一個(gè)概念似乎表達(dá)了一個(gè)數(shù)量,概念上不需要?jiǎng)?chuàng)建。額外需要思考一下,這個(gè)對象是否真的需要?jiǎng)?chuàng)建?我們真正需要做的是什么?
For update operations, prefer idempotency whenever feasible 更新操作,盡量保持冪等性
Idempotency冪等性,指的是一種操作具備的性質(zhì),具有這種性質(zhì)的操作可以被多次實(shí)施并且不會(huì)影響到初次實(shí)施的結(jié)果“the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.”【3】
很明顯Idempotency在系統(tǒng)設(shè)計(jì)中會(huì)帶來很多便利性,例如客戶端可以更安全的重試,從而讓復(fù)雜的流程實(shí)現(xiàn)更為簡單。但是Idempotency實(shí)現(xiàn)并不總是很容易。
- Create類型的idempotency
創(chuàng)建的Idempotency,多次調(diào)用容易出現(xiàn)重復(fù)創(chuàng)建,為實(shí)現(xiàn)冪等性,常見的做法是使用一個(gè)__client-side generated de-deduplication token(客戶端生成的唯一ID)__,在反復(fù)重試時(shí)使用同一個(gè)Unique ID,便于服務(wù)端識(shí)別重復(fù)。 -
Update類型的Idempotency
更新值(update)類型的API,應(yīng)該避免采用"Delta"語義,以便于實(shí)現(xiàn)冪等性。對于更新類的操作,我們再簡化為兩類實(shí)現(xiàn)方式- Incremental(數(shù)量增減),如IncrementBy(3)這樣的語義
- SetNewTotal(設(shè)置新的總量)
IncrementBy?這樣的語義重試的時(shí)候難以避免出錯(cuò),而SetNewTotal(3)(總量設(shè)置為x)語義則比較容易具備冪等性。
當(dāng)然在這個(gè)例子里面,也需要看到,IncrementBy也有有點(diǎn),即多個(gè)客戶請求同時(shí)增加的時(shí)候,比較容易并行處理,而SetTotal可能導(dǎo)致并行的更新相互覆蓋(或者相互阻塞)。
這里,可以認(rèn)為更新增量和設(shè)置新的總量這兩種語義是不同的優(yōu)缺點(diǎn),需要根據(jù)場景來解決。如果必須優(yōu)先考慮并發(fā)更新的情景,可以使用更新增量的語義,并輔助以Deduplication token解決冪等性。 - __Delete類型idempotency__:Delete的冪等性問題,往往在于一個(gè)對象被刪除后,再次試圖刪除可能會(huì)由于數(shù)據(jù)無法被發(fā)現(xiàn)導(dǎo)致出錯(cuò)。這個(gè)行為一般來說也沒什么問題,雖然嚴(yán)格意義上不冪等,但是也無副作用。如果需要實(shí)現(xiàn)Idempotency,系統(tǒng)也采用了Archive->Purge生命周期的方式分步刪除,或者持久化Purge log的方式,都能支持冪等刪除的實(shí)現(xiàn)。
Compatibility 兼容
API的變更需要兼容,兼容,兼容!重要的事情說三遍。這里的兼容指的是向后兼容,而兼容的定義是不會(huì)Break客戶端的使用,也即__老的客戶端能否正常訪問服務(wù)端的新版本(如果是同一個(gè)大版本下)不會(huì)有錯(cuò)誤的行為__。這一點(diǎn)對于遠(yuǎn)程的API(HTTP/RPC)尤其重要。關(guān)于兼容性,已經(jīng)有很好的總結(jié),例如【4】提供的一些建議。
常見的__不兼容__變化包括(但不限于)
- 刪除一個(gè)方法、字段或者enum的數(shù)值
- 方法、字段改名
-
方法名稱字段不改,但是語義和行為的變化,也是不兼容的。這類比較容易被忽視。
更具體描述可以參加【4】。
另一個(gè)關(guān)于兼容性的重要問題是,__如何做不兼容的API變更__?通常來說,不兼容變更需要通過一個(gè)__Deprecation process,在大版本發(fā)布時(shí)來分步驟實(shí)現(xiàn)__。關(guān)于Deprecation process,這里不展開描述,一般來說,需要保持過去版本的兼容性的前提下,支持新老字段/方法/語義,并給客戶端足夠的升級(jí)時(shí)間。這樣的過程比較耗時(shí),也正是因?yàn)槿绱?#xff0c;我們才需要如此重視API的設(shè)計(jì)。
有時(shí),一個(gè)面向內(nèi)部的API升級(jí),往往開發(fā)的同學(xué)傾向于選擇高效率,采用一種叫”同步發(fā)布“的模式來做不兼容變更,即通知已知的所有的客戶端,自己的服務(wù)API要做一個(gè)不兼容變更,大家一起發(fā)布,同時(shí)更新,切換到新的接口。這樣的方法是非常不可取的,原因有幾個(gè):
- 我們經(jīng)常并不知道所有使用API的客戶
- 發(fā)布過程需要時(shí)間,無法真正實(shí)現(xiàn)“同步更新”
- 不考慮向后兼容性的模式,一旦新的API有問題需要回滾,則會(huì)非常麻煩,這樣的計(jì)劃八成也不會(huì)有回滾方案,而且客戶端未必都能跟著回滾。
因此,對于在生產(chǎn)集群已經(jīng)得到應(yīng)用的API,強(qiáng)烈不建議采用“同步升級(jí)”的模式來處理不兼容API變更。
Batch mutations 批量更新
批量更新如何設(shè)計(jì)是另一個(gè)常見的API設(shè)計(jì)決策。這里我們常見有兩種模式:
- 客戶端批量更新,或者
-
服務(wù)端實(shí)現(xiàn)批量更新。
如下圖所示。
API的設(shè)計(jì)者可能會(huì)希望實(shí)現(xiàn)一個(gè)服務(wù)端的批量更新能力,但是我們建議要盡量避免這樣做。__除非對于客戶來說提供原子化+事務(wù)性的批量很有意義(all-or-nothing)__,否則實(shí)現(xiàn)服務(wù)端的批量更新有諸多的弊端,而客戶端批量更新則有優(yōu)勢:
- 服務(wù)端批量更新帶來了API語義和實(shí)現(xiàn)上的復(fù)雜度。例如當(dāng)部分更新成功時(shí)的語義、狀態(tài)表達(dá)等
- 即使我們希望支持批量事物,也要考慮到是否不同的后端實(shí)現(xiàn)都能支持事務(wù)性
- 批量更新往往給服務(wù)端性能帶來很大挑戰(zhàn),也容易被客戶端濫用接口
- 在客戶端實(shí)現(xiàn)批量,可以更好的將負(fù)載由不同的服務(wù)端來承擔(dān)(見圖)
- 客戶端批量可以更靈活的由客戶端決定失敗重試策略
Be aware of the risks in full replace 警惕全體替換更新模式的風(fēng)險(xiǎn)
所謂Full replacement更新,是指在Mutation API中,用一個(gè)全新的Object/Resource去替換老的Object/Resource的模式。API寫出來大概是這樣的
UpdateFoo(Foo newFoo);這是非常常見的Mutation設(shè)計(jì)模式。但是這樣的模式有一些潛在的風(fēng)險(xiǎn)作為API設(shè)計(jì)者必須了解。
使用Full replacement的時(shí)候,更新對象Foo在服務(wù)端可能已經(jīng)有了新的成員,而客戶端尚未更新并不知道該新成員。服務(wù)端增加一個(gè)新的成員一般來說是兼容的變更,但是,如果該成員之前被另一個(gè)知道這個(gè)成員的client設(shè)置了值,而這時(shí)一個(gè)不知道這個(gè)成員的client來做full-replace,該成員可能就會(huì)被覆蓋。
更安全的更新方式是采用Update mask,也即在API設(shè)計(jì)中引入明確的參數(shù)指明哪些成員應(yīng)該被更新。
UpdateFoo {Foo newFoo; boolen update_field1; // update maskboolen update_field2; // update mask }或者update mask可以用repeated "a.b.c.d“這樣方式來表達(dá)。
不過由于這樣的API方式維護(hù)和代碼實(shí)現(xiàn)都復(fù)雜一些,采用這樣模式的API并不多。所以,本節(jié)的標(biāo)題是“be aware of the risk“,而不是要求一定要用update mask。
Don't create your own error codes or error mechanism 不要試圖創(chuàng)建自己的錯(cuò)誤碼和返回錯(cuò)誤機(jī)制
API的設(shè)計(jì)者有時(shí)很想創(chuàng)建自己的Error code,或者是表達(dá)返回錯(cuò)誤的不同機(jī)制,因?yàn)槊總€(gè)API都有很多的細(xì)節(jié)的信息,設(shè)計(jì)者想表達(dá)出來并返回給用戶,想著“用戶可能會(huì)用到”。但是事實(shí)上,這么做經(jīng)常只會(huì)使API變得更復(fù)雜更難用。
Error-handling是用戶使用API非常重要的部分。為了讓用戶更容易的使用API,最佳的實(shí)踐應(yīng)該是用標(biāo)準(zhǔn)、統(tǒng)一的Error Code,而不是每個(gè)API自己去創(chuàng)立一套。例如HTTP有規(guī)范的error code 【7】,Google Could API設(shè)計(jì)時(shí)都采用統(tǒng)一的Error code等【5】。
為什么不建議自己創(chuàng)建Error code機(jī)制?
- Error-handling是客戶端的事,而對于客戶端來說,是很難關(guān)注到那么多錯(cuò)誤的細(xì)節(jié)的,一般來說最多分兩三種情況處理。往往客戶端最關(guān)心的是"這個(gè)error是否應(yīng)該重試(retryable)"還是應(yīng)該繼續(xù)向上層返回錯(cuò)誤,而不是試圖區(qū)分不同的error細(xì)節(jié)。這時(shí)多樣的錯(cuò)誤代碼機(jī)制只會(huì)讓處理變得復(fù)雜
- 有人覺得提供更多的自定義的error code有助于傳遞信息,但是這些信息除非有系統(tǒng)分別處理才有意義。如果只是傳遞信息的話,error message里面的字段可以達(dá)到同樣的效果。
More
更多的Design patterns,可以參考[5] Google Cloud API guide,[6] Microsoft API design best practices等。不少這里提到的問題也在這些參考的文檔里面有涉及,另外他們還討論到了像versioning,pagination,filter等常見的設(shè)計(jì)規(guī)范方面考慮。這里不再重復(fù)。
參考文獻(xiàn)
【1】File wiki?https://en.wikipedia.org/wiki/Computer_file
【2】阿白,域模型設(shè)計(jì)系列文章,https://yq.aliyun.com/articles/6383
【3】Idempotency, wiki?https://en.wikipedia.org/wiki/Idempotence
【4】Compatibility?https://cloud.google.com/apis/design/compatibility
【5】API Design patterns for Google Cloud,?https://cloud.google.com/apis/design/design_patterns
【6】API design best practices, Microsoft?https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design
【7】Http status code?https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
【8】A philosophy of software design, John Ousterhout
原文鏈接
轉(zhuǎn)載于:https://my.oschina.net/u/1464083/blog/2995094
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的阿里研究员谷朴:API 设计最佳实践的思考的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王者荣耀抽韩信要多少钻石
- 下一篇: 适合0基础的web开发系列教程-文本格式