<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          知己知彼:一篇來(lái)自前端同學(xué)對(duì)后端接口的吐槽!

          共 5867字,需瀏覽 12分鐘

           ·

          2020-09-09 23:51

          作者:李熠

          juejin.im/post/6844903861841313806

          前言

          去年的某個(gè)時(shí)候就想寫(xiě)一篇關(guān)于接口的吐槽,當(dāng)時(shí)后端提出了接口方案對(duì)于我來(lái)說(shuō)調(diào)用起來(lái)非常難受,但又說(shuō)不上為什么,沒(méi)有論點(diǎn)論據(jù)所以也就作罷。

          最近因?yàn)閷?xiě)全棧的緣故,團(tuán)隊(duì)內(nèi)部也遇到了一些關(guān)于接口設(shè)計(jì)的問(wèn)題,于是開(kāi)始思考實(shí)現(xiàn)接口的最佳實(shí)踐是什么。在參考了許多資料之后,逐漸對(duì)這個(gè)問(wèn)題有了自己的理解。同時(shí)回想起過(guò)去的經(jīng)驗(yàn),終于恍然大悟自己當(dāng)時(shí)的痛點(diǎn)在哪里。

          既然是吐槽,那么請(qǐng)?jiān)徫医酉聛?lái)態(tài)度的不友善。本文中列舉的所有例子都是我個(gè)人的親身經(jīng)歷。

          誰(shuí)應(yīng)該主導(dǎo)接口的設(shè)計(jì)

          或者更直白一些,應(yīng)該是接口的消費(fèi)方還是提供方來(lái)決定接口的設(shè)計(jì)?

          當(dāng)然是接口的消費(fèi)方

          「接口」最吊詭的地方在于提供方大費(fèi)周章把它實(shí)現(xiàn)了,但它自己卻(幾乎)重來(lái)都不使用。于是這極易陷入一種自嗨的境地,因?yàn)樗静恢澜涌诘暮脡摹>秃帽纫粋€(gè)從來(lái)不嘗自己做的菜的廚子,你指望他的菜能好到哪里去,他的廚藝能好到哪里去。

          上面隱含的前提是(我認(rèn)為)接口是有絕對(duì)好壞之分的,壞的接口消費(fèi)者調(diào)用難受,提供者維護(hù)難受,還導(dǎo)致產(chǎn)品行為別扭體驗(yàn)變差。

          然而接口的好壞與誰(shuí)來(lái)主導(dǎo)設(shè)計(jì)有什么關(guān)系?因?yàn)閴慕涌诋a(chǎn)生的原因之一是提供方只站在開(kāi)發(fā)者的角度解決問(wèn)題:

          例子一 ( Chatty API )

          某次需要實(shí)現(xiàn)允許用戶(hù)創(chuàng)建儀表盤(pán)頁(yè)面的功能(如果你對(duì)儀表盤(pán)頁(yè)面感到陌生的話(huà),可以想象它是一張集中了不同圖表的頁(yè)面,比如柱狀圖、折線(xiàn)圖、餅圖等等。用戶(hù)可以添加自己想要的圖表到頁(yè)面中,并且手動(dòng)調(diào)整它們的尺寸和位置。儀表盤(pán)通常用于總覽某個(gè)產(chǎn)品或者服務(wù)的運(yùn)行狀態(tài))。

          后端同學(xué)的接口初步設(shè)計(jì)是,當(dāng)用戶(hù)填寫(xiě)完基本信息、添加完圖表、點(diǎn)擊創(chuàng)建按鈕之后,我需要連續(xù)調(diào)用兩次接口才能完成一次儀表盤(pán)的創(chuàng)建:

          • 利用用戶(hù)填寫(xiě)的基本信息以及圖表的尺寸和位置創(chuàng)建一個(gè)空的儀表盤(pán)
          • 再向儀表盤(pán)中填充圖表的具體信息,比如圖表類(lèi)型,使用的維度和指標(biāo)等

          很明顯看出他完全是按照自己后端的存儲(chǔ)結(jié)構(gòu)在設(shè)計(jì)接口,不僅是存儲(chǔ)結(jié)構(gòu),甚至存儲(chǔ)過(guò)程都一覽無(wú)余。想象一種極端的情況,那不只提供一些更新數(shù)據(jù)庫(kù)表的接口得了,前端自己把通過(guò)接口把數(shù)據(jù)插入庫(kù)中

          面對(duì)這類(lèi)底層性質(zhì)的接口,消費(fèi)者在集成時(shí)需要考慮接口的調(diào)用步驟以及理解背后的原理。如果后端的底層結(jié)構(gòu)一旦發(fā)生更改,接口很有可能也需要發(fā)生更改,前端的調(diào)用代碼也需要隨之更改。

          后端研發(fā)可能會(huì)辯解說(shuō):后端用了微服務(wù)啊,不同類(lèi)型的數(shù)據(jù)存儲(chǔ)在不同的服務(wù)上,所以你需要和不同的服務(wù)通信才能實(shí)現(xiàn)完整的存儲(chǔ)。他們始終沒(méi)有明白的事情是,后端的實(shí)現(xiàn)導(dǎo)致了接口的碎片化,那是你的問(wèn)題,而不應(yīng)該把這部分負(fù)擔(dān)轉(zhuǎn)移到前端的開(kāi)發(fā)者上,其實(shí)也是間接轉(zhuǎn)移到了用戶(hù)身上。

          不要欺負(fù)我不懂后端,至少我了解加一層類(lèi)似于 BFF 的 Orchestration Layer 就能解決這個(gè)問(wèn)題

          Netflix 的工程師 Daniel Jacobson 在他的文章 The future of API design: The orchestration layer 中指出, API 無(wú)非是要面對(duì)兩類(lèi)受眾:

          • LSUD: Large set of unknown developers
          • SSKD: Small set of known developers

          隨著產(chǎn)品服務(wù)化的趨勢(shì),很有可能需要像 AWS 或者 Github 那樣對(duì)公共開(kāi)發(fā)者即 LSUD 暴露接口。且不說(shuō)上面例子中的接口方案會(huì)不會(huì)被唾沫星子淹死,如此明顯的暴露內(nèi)部服務(wù)的細(xì)節(jié)是非常危險(xiǎn)的事情。

          所以在設(shè)計(jì)接口時(shí),應(yīng)該讓消費(fèi)者來(lái)主導(dǎo)。如果消費(fèi)者沒(méi)能給出很好的建議,那么至少提供者在設(shè)計(jì)時(shí)也應(yīng)該站在消費(fèi)者的立場(chǎng)上來(lái)思考問(wèn)題。又或者,至少想一想如果你自己會(huì)樂(lè)意使用用你自己設(shè)計(jì)出來(lái)的接口嗎?

          使用后端思維設(shè)計(jì)接口不僅體現(xiàn)在 URI 的設(shè)計(jì)上,還有可能體現(xiàn)在請(qǐng)求參數(shù)和返回體結(jié)構(gòu)上:

          例子二

          假設(shè)現(xiàn)在需要一個(gè)請(qǐng)求批量文章的接口,接口同時(shí)返回多篇文章的內(nèi)容,包括這些文章的內(nèi)容,作者信息,評(píng)論信息等等。

          理想情況下,我們期望返回的數(shù)據(jù)是以文章為單位的,比如

          articles:?[
          ?{
          ????id:?,
          ????????author:?{},
          ????????comments:?[]
          ?},
          ????{
          ?????id:
          ????????author:?{},
          ????????comments:?[]
          ????}
          ]

          However, 后端的返回結(jié)果可能是以實(shí)體為單位:

          {
          ????articles:?[],
          ????authors:?[],
          ????comments:?[]
          }

          comments 里包含不同文章的 comment,我必須通過(guò)類(lèi)似于 articleId 的字段對(duì)它們執(zhí)行 group by 操作才能分離出屬于不同文章的評(píng)論。對(duì)其他實(shí)體做同樣的操作,最終手動(dòng)的拼接成前端代碼需要的 articles 數(shù)據(jù)結(jié)構(gòu)

          很明顯這又是按照后端庫(kù)表關(guān)系返回的結(jié)果,嚴(yán)格來(lái)說(shuō)這并不算是 anti-pattern,在 redux 中也鼓勵(lì)將數(shù)據(jù) normalize。但如果前端用不到原始數(shù)據(jù),請(qǐng)不要返回原始數(shù)據(jù)。

          例如我需要在頁(yè)面上展示一個(gè)百分比格式的數(shù)據(jù),除非用戶(hù)有動(dòng)態(tài)調(diào)整數(shù)據(jù)格式的需求,例如千分位、小數(shù)或者是切換精度等等,否則就直接返回給我百分比的字符串就好了,不要返回給我原始的浮點(diǎn)數(shù)據(jù)。

          前端對(duì)數(shù)據(jù)的二次加工還會(huì)給問(wèn)題排查帶來(lái)干擾,如果任何數(shù)據(jù)都需要前端進(jìn)行二次加工,那么所以問(wèn)題的排查都必須從前端發(fā)起,前端確認(rèn)無(wú)誤后再進(jìn)入后端排查流程,這始終會(huì)占用兩個(gè)端的人力,并且 delay 了排查的進(jìn)度

          關(guān)于 meta 信息

          例子三:

          后端接口在返回時(shí)通常會(huì)帶上 meta 信息,meta 信息包含接口的狀態(tài)以及如果失敗時(shí)的失敗原因,便于調(diào)試使用。后端提供的接口的 meta 信息的數(shù)據(jù)結(jié)構(gòu)如下:

          {
          ????meta:?{
          ??????code:?0,
          ??????error:?null,
          ??????host:?"127.0.0.1"
          ????},
          ????result:?[]
          }

          在我看來(lái),以上數(shù)據(jù)結(jié)構(gòu)有兩個(gè)問(wèn)題

          meta 信息包含獨(dú)立的狀態(tài)信息

          在包含狀態(tài)碼的 meta 信息接口設(shè)計(jì)中,一條默認(rèn)的隱藏邏輯是:接口返回的 HTTP status code 一定是 200,數(shù)據(jù)是否真的獲取成功需要通過(guò) meta 里的自定狀態(tài)碼 code 進(jìn)行判斷(換句話(huà)說(shuō),上面你看到的接口實(shí)際上是“接口的接口”)。

          最終在前端的代碼中也不需要通過(guò) HTTP code 判斷返回是否正常,只需要判斷接口里返回的meta.code即可

          但是誰(shuí)給你們的自信保證后端接口一定是不會(huì)掛的?!?無(wú)論后端如何保證接口的堅(jiān)固,前端仍然需要首先判斷 HTTP code 是否為 200,再判斷meta.code是否與預(yù)期的符合一致。這和信任無(wú)關(guān),和我程序的健壯有關(guān)。擴(kuò)展:思考:實(shí)際項(xiàng)目中如何做 RESTful接口 設(shè)計(jì)規(guī)范

          既然無(wú)論如何都要對(duì)接口判斷兩次,那為什么不將meta.code與 HTTP code 合二為一?更何況我還需要再本地維護(hù)一份自定義 code 的枚舉值,還需要和后端保證同步。這就涉及到下一個(gè)問(wèn)題了:

          meta 信息的存放位置

          我們需要 meta 信息沒(méi)有錯(cuò),但是我們沒(méi)有那么需要 meta 信息。這體現(xiàn)在幾點(diǎn):

          • 我們真的需要一個(gè)平行于返回結(jié)果的字段展示 meta 信息嗎?
          • 每一次請(qǐng)求我們都需要 meta 信息嗎?
          • meta 信息一定要在 meta 字段里嗎?

          以請(qǐng)求失敗的錯(cuò)誤信息為例,錯(cuò)誤信息只會(huì)出現(xiàn)在接口非正常返回的情況下,但我們應(yīng)該始終在返回體中用一個(gè)字段為它預(yù)留位置嗎?

          在關(guān)于 meta 信息存在位置的這個(gè)問(wèn)題上,我傾向于將它們整合進(jìn)入 HTTP Header 中。例如meta.code完全可以使用 HTTP code 代替,我看不出始終要保證 200 返回以及自定義 code 的意義在哪里

          而至于其它的 meta 信息,可以通過(guò)以X-開(kāi)頭的自定義 HTTP Header 進(jìn)行傳遞。例如

          https://developer.github.com/v3/rate_limit/

          Github API 中關(guān)于使用頻率限制的信息就放在 HTTP Header 中:

          Status:?200?OK
          X-RateLimit-Limit:?5000
          X-RateLimit-Remaining:?4999
          X-RateLimit-Reset:?1372700873

          Design for today

          例子四

          我們需要為某個(gè)指標(biāo)的折線(xiàn)圖設(shè)計(jì)查詢(xún)接口,查詢(xún)以天為單位,也就是說(shuō)該接口只會(huì)根據(jù)查詢(xún)的日期返回指定日期的查詢(xún)結(jié)果,后端提供的返回?cái)?shù)據(jù)結(jié)構(gòu)如下:

          {
          ????data:?[
          ????????{
          ????????????date:?"2019-06-08",
          ????????????result:?[
          ????????????????
          ????????????]
          ????????}
          ????]
          }

          雖然需求很明確的指示只會(huì)返回某天的查詢(xún)結(jié)果,但是后端還是決定給我返回一個(gè)數(shù)組。他這么設(shè)計(jì)的理由是為了防止日后需求發(fā)生改變需要返回多日的查詢(xún)結(jié)果。

          這看上去是很聰明決策:“看,我預(yù)見(jiàn)性的 cover 了一個(gè)未來(lái)的需求!”,但實(shí)際上愚蠢至極:你的確 cover 了一個(gè)需求,不過(guò)是一個(gè)當(dāng)前并不存在,未來(lái)也不見(jiàn)得會(huì)發(fā)生的需求;而且如果你真的想寫(xiě) future-proof 的代碼,那么還有未來(lái)千千萬(wàn)萬(wàn)的需求等待著你實(shí)現(xiàn)。

          問(wèn)題在于沒(méi)有人知道將來(lái)是否真的會(huì)允許同時(shí)查詢(xún)多日數(shù)據(jù),即使某天需要支持同時(shí)查詢(xún)多日數(shù)據(jù)了,數(shù)據(jù)結(jié)構(gòu)也不一定非要如此。在數(shù)據(jù)分析領(lǐng)域我們面臨的查詢(xún)需求并不是線(xiàn)性從單個(gè)到多個(gè),在其他業(yè)務(wù)領(lǐng)域也是這樣。

          這樣導(dǎo)致的后果是你花費(fèi)多余的時(shí)間實(shí)現(xiàn)了不需要的代碼,并且前端也需要配合這樣的數(shù)據(jù)結(jié)構(gòu)進(jìn)行實(shí)現(xiàn)。并且在將來(lái)的維護(hù)中,每個(gè)看到返回體是數(shù)組的人都會(huì)納悶為什么返回的結(jié)果明明只有一條,還需要用數(shù)組封裝,是不是我遺漏了什么?于是不得不投入精力來(lái)驗(yàn)證是否真的有可能返回更多的數(shù)據(jù)。API 和代碼應(yīng)該是精準(zhǔn)的,準(zhǔn)確表達(dá)你想實(shí)現(xiàn)的一切而不存在有歧義

          有人可能會(huì)說(shuō)不就是多了一層封裝嗎?實(shí)現(xiàn)上也花不了多少的功夫何至于大驚小怪。抱歉我不是針對(duì)這一個(gè) case,而是在強(qiáng)調(diào)任何場(chǎng)景下無(wú)論實(shí)現(xiàn)的難易都不應(yīng)該添加無(wú)意義的代碼,“勿以惡小而為之”就是這個(gè)道理

          “關(guān)注當(dāng)下”還有另一個(gè)維度含義:

          例子五

          目前我們已經(jīng)有創(chuàng)建單個(gè)文章的接口,現(xiàn)在需要支持批量創(chuàng)建文章。后端給出的建議是:不如調(diào)單個(gè)接口多次?

          搜索Java知音公眾號(hào),回復(fù)“后端面試”,送你一份Java面試題寶典.pdf

          例子六

          目前已經(jīng)有一個(gè)接口能夠取得文章相關(guān)數(shù)據(jù),比如內(nèi)容、評(píng)論、作者等等。現(xiàn)在我們需要增加一個(gè)新的頁(yè)面用于展示用戶(hù)信息。后端給出的建議是:不如使用文章數(shù)據(jù)接口,里面已經(jīng)包含了作者信息,這樣就不用開(kāi)發(fā)新的接口了


          以上的例子看似都是想實(shí)現(xiàn)對(duì)接口的復(fù)用,但實(shí)際上起到的是事倍功半的效果

          在例五中,雖然語(yǔ)義上“創(chuàng)建五篇文章”和“連續(xù)五次創(chuàng)建一篇文章”是等效的,但是在實(shí)現(xiàn)和操作層面并不是如此。且不說(shuō)調(diào)用五次和調(diào)用一次的性能大不相同,批量創(chuàng)建的五篇文章可能存在順序關(guān)系,可能需要事務(wù)操作。

          在例六中雖然能夠達(dá)到我們實(shí)現(xiàn)的效果,但這不能算是接口的復(fù)用,只能算是接口的 hack(hack 和復(fù)用的區(qū)別在于是否用物品的初衷功能做事情)。并且 hack 接口是有風(fēng)險(xiǎn)的,對(duì)于接口的提供者而言他們更關(guān)心接口服務(wù)“正統(tǒng)”的消費(fèi)者,在這個(gè) case 中接口的存在是為了展示完整的文章信息,如果有一天“文章信息”這個(gè)需求發(fā)生了變化很有可能會(huì)導(dǎo)致作者信息同時(shí)發(fā)生變化,縮減字段甚至取消字段。那么它們沒(méi)有義務(wù)這些 hack 用戶(hù)負(fù)責(zé)。一個(gè)接口本應(yīng)該就專(zhuān)注一件事情

          所以最理想的事情是,為當(dāng)前專(zhuān)注的業(yè)務(wù)開(kāi)發(fā)獨(dú)立的接口。在例六的例子中,可能我們?cè)陂_(kāi)發(fā)一個(gè)獨(dú)立請(qǐng)求作者的信息的接口時(shí)實(shí)現(xiàn)代碼完全復(fù)制自另一個(gè)接口的實(shí)現(xiàn),但是接口的隔離在長(zhǎng)遠(yuǎn)看來(lái)能給功能的維護(hù)帶來(lái)更大的便利

          不僅限于 REST API

          “接口”是一個(gè)概念。在概念之下如何實(shí)現(xiàn)它我們擁有很多種選擇。目前看來(lái)絕大部分的方式是通過(guò) REST API 來(lái)達(dá)成的,也并沒(méi)有什么事情是 REST API 無(wú)法做到的,但事實(shí)上這幾年技術(shù)的進(jìn)步給了我們更多的選擇,如果選擇更有針對(duì)性的實(shí)現(xiàn)方案,效果會(huì)更好

          例如在實(shí)時(shí)數(shù)據(jù)的場(chǎng)景下,理論上是由后端(有數(shù)據(jù)更新時(shí))驅(qū)動(dòng)前端視圖的更新,這理應(yīng)是 push 操作。但是在傳統(tǒng)實(shí)現(xiàn)中,我們不得不仍然通過(guò)被動(dòng)的等待和輪詢(xún)實(shí)現(xiàn)功能。

          對(duì)于事件驅(qū)動(dòng)類(lèi)型的需求使用 WebSocket 或者是 Streaming 似乎是更好的選擇。如果是后端之間的交互還可以利用 WebHook。我通常對(duì)新技術(shù)持保留態(tài)度,但是不得不承認(rèn) GraphQL 在處理某些需求上也能夠比 REST API 做的更好。并且大部分廠(chǎng)商對(duì)于 GraphQL 接口的支持表明它是可行的。

          我了解實(shí)現(xiàn) API 來(lái)只是后端實(shí)現(xiàn)功能的一個(gè)很小的環(huán)節(jié),在接口背后是更多業(yè)務(wù)邏輯的修改和庫(kù)表結(jié)構(gòu)的更迭。甚至說(shuō)接口部分有一半都是交給框架來(lái)實(shí)現(xiàn)的。但是,哪怕只有很小的機(jī)會(huì),也應(yīng)該把這個(gè)環(huán)節(jié)做到盡善盡美。

          結(jié)束語(yǔ)

          對(duì)于糟糕的接口設(shè)計(jì)我還能繼續(xù)沒(méi)完沒(méi)了的抱怨下去,但突然然覺(jué)得洋洋灑灑的繼續(xù)寫(xiě)下去似乎沒(méi)有太大意義。講真我不是來(lái)真的大吐苦水的,只是想表達(dá)接口設(shè)計(jì)也至關(guān)重要。

          在工作中痛心的看到很多問(wèn)題明明用一些很基礎(chǔ)的技巧就能夠解決,而大家卻對(duì)它熟視無(wú)睹以造成兩敗俱傷的境地。以上就是我認(rèn)為的在接口設(shè)計(jì)中需要遵循的一些原則和考慮要素,相信能夠解決大多數(shù)的痛點(diǎn)和避免部分的問(wèn)題

          后端同學(xué)們,如果你們有心讓接口變得更好,多聽(tīng)聽(tīng)“消費(fèi)者”的反饋。如果你們嘗試使用過(guò)第三方接口開(kāi)發(fā)過(guò)應(yīng)用的話(huà),例如 Slack、Github,你會(huì)發(fā)現(xiàn)它們的接口是在不斷迭代的。不斷有舊的接口被淘汰,新的接口投入使用。

          這種迭代背后不是閑著沒(méi)事干,而是出于實(shí)際的用戶(hù)的聲音和需求 最后推薦我最近閱讀的關(guān)于 API 設(shè)計(jì)的圖書(shū),收益匪淺:

          • Web API ?的設(shè)計(jì)與開(kāi)發(fā)
          • Designing Web APIs
          • APIs A Strategy Guide
          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧洲成人午夜精品无码区久久 | A一级黄色片在线看 | 在线三级片视频 | 一本道一区二区 | 40岁无码视频看看 |