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

          有理有據(jù):一篇來(lái)自前端同學(xué)對(duì)后端接口的吐槽!

          共 8036字,需瀏覽 17分鐘

           ·

          2020-07-27 19:23


          點(diǎn)擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”

          回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

          來(lái)源 | juejin.im/post/5cfbe8c7e51d4556da53d07f

          去年的某個(gè)時(shí)候就想寫一篇關(guān)于接口的吐槽,當(dāng)時(shí)后端提出了接口方案對(duì)于我來(lái)說(shuō)調(diào)用起來(lái)非常難受,但又說(shuō)不上為什么,沒(méi)有論點(diǎn)論據(jù)所以也就作罷。最近因?yàn)閷懭珬5木壒?,團(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)歷。

          贈(zèng)書:全球首本VS Code中文書來(lái)了,高效編程秘訣全收錄!

          誰(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)允許用戶創(chuàng)建儀表盤頁(yè)面的功能(如果你對(duì)儀表盤頁(yè)面感到陌生的話,可以想象它是一張集中了不同圖表的頁(yè)面,比如柱狀圖、折線圖、餅圖等等。用戶可以添加自己想要的圖表到頁(yè)面中,并且手動(dòng)調(diào)整它們的尺寸和位置。儀表盤通常用于總覽某個(gè)產(chǎn)品或者服務(wù)的運(yùn)行狀態(tài))。后端同學(xué)的接口初步設(shè)計(jì)是,當(dāng)用戶填寫完基本信息、添加完圖表、點(diǎn)擊創(chuàng)建按鈕之后,我需要連續(xù)調(diào)用兩次接口才能完成一次儀表盤的創(chuàng)建:

          1. 利用用戶填寫的基本信息以及圖表的尺寸和位置創(chuàng)建一個(gè)空的儀表盤
          2. 再向儀表盤中填充圖表的具體信息,比如圖表類型,使用的維度和指標(biāo)等
          很明顯看出他完全是按照自己后端的存儲(chǔ)結(jié)構(gòu)在設(shè)計(jì)接口,不僅是存儲(chǔ)結(jié)構(gòu),甚至存儲(chǔ)過(guò)程都一覽無(wú)余。想象一種極端的情況,那不只提供一些更新數(shù)據(jù)庫(kù)表的接口得了,前端自己把通過(guò)接口把數(shù)據(jù)插入庫(kù)中
          面對(duì)這類底層性質(zhì)的接口,消費(fèi)者在集成時(shí)需要考慮接口的調(diào)用步驟以及理解背后的原理。如果后端的底層結(jié)構(gòu)一旦發(fā)生更改,接口很有可能也需要發(fā)生更改,前端的調(diào)用代碼也需要隨之更改。
          后端研發(fā)可能會(huì)辯解說(shuō):后端用了微服務(wù)啊,不同類型的數(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)移到了用戶身上。不要欺負(fù)我不懂后端,至少我了解加一層類似于 BFF 的 Orchestration Layer 就能解決這個(gè)問(wèn)題
          Netflix 的工程師 Daniel Jacobson 在他的文章 The future of API design: The orchestration layer 中指出, API 無(wú)非是要面對(duì)兩類受眾:
          1. LSUD: Large set of unknown developers
          2. 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ò)類似于 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ù),除非用戶有動(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: {
                code0,
                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)行判斷(換句話說(shuō),上面你看到的接口實(shí)際上是 “接口的接口”)。最終在前端的代碼中也不需要通過(guò) HTTP code 判斷返回是否正常,只需要判斷接口里返回的meta.code即可
          ** 但是誰(shuí)給你們的自信保證后端接口一定是不會(huì)掛的?!** 無(wú)論后端如何保證接口的堅(jiān)固,前端仍然需要首先判斷 HTTP code 是否為 200,再判斷meta.code是否與預(yù)期的符合一致。這和信任無(wú)關(guān),和我程序的健壯有關(guān)。
          既然無(wú)論如何都要對(duì)接口判斷兩次,那為什么不將meta.code與 HTTP code 合二為一?更何況我還需要再本地維護(hù)一份自定義 code 的枚舉值,還需要和后端保證同步。這就涉及到下一個(gè)問(wèn)題了:

          meta 信息的存放位置

          我們需要 meta 信息沒(méi)有錯(cuò),但是我們沒(méi)有那么需要 meta 信息。這體現(xiàn)在幾點(diǎn):
          1. 我們真的需要一個(gè)平行于返回結(jié)果的字段展示 meta 信息嗎?
          2. 每一次請(qǐng)求我們都需要 meta 信息嗎?
          3. 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)行傳遞。例如
          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)的折線圖設(shè)計(jì)查詢接口,查詢以天為單位,也就是說(shuō)該接口只會(huì)根據(jù)查詢的日期返回指定日期的查詢結(jié)果,后端提供的返回?cái)?shù)據(jù)結(jié)構(gòu)如下:
          {
              data: [
                  {
                      date: "2019-06-08",
                      result: [
                          
                      ]
                  }
              ]
          }
          雖然需求很明確的指示只會(huì)返回某天的查詢結(jié)果,但是后端還是決定給我返回一個(gè)數(shù)組。他這么設(shè)計(jì)的理由是為了防止日后需求發(fā)生改變需要返回多日的查詢結(jié)果。
          這看上去是很聰明決策:“看,我預(yù)見(jiàn)性的 cover 了一個(gè)未來(lái)的需求!”,但實(shí)際上愚蠢至極:你的確 cover 了一個(gè)需求,不過(guò)是一個(gè)當(dāng)前并不存在,未來(lái)也不見(jiàn)得會(huì)發(fā)生的需求;而且如果你真的想寫 future-proof 的代碼,那么還有未來(lái)千千萬(wàn)萬(wàn)的需求等待著你實(shí)現(xiàn)。
          問(wèn)題在于沒(méi)有人知道將來(lái)是否真的會(huì)允許同時(shí)查詢多日數(shù)據(jù),即使某天需要支持同時(shí)查詢多日數(shù)據(jù)了,數(shù)據(jù)結(jié)構(gòu)也不一定非要如此。在數(shù)據(jù)分析領(lǐng)域我們面臨的查詢需求并不是線性從單個(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è)接口多次?

          例子六

          目前已經(jīng)有一個(gè)接口能夠取得文章相關(guān)數(shù)據(jù),比如內(nèi)容、評(píng)論、作者等等?,F(xiàn)在我們需要增加一個(gè)新的頁(yè)面用于展示用戶信息。后端給出的建議是:不如使用文章數(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 用戶負(fù)責(zé)。一個(gè)接口本應(yīng)該就專注一件事情
          所以最理想的事情是,為當(dāng)前專注的業(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)的等待和輪詢實(shí)現(xiàn)功能。
          對(duì)于事件驅(qū)動(dòng)類型的需求使用 WebSocket 或者是 Streaming 似乎是更好的選擇。如果是后端之間的交互還可以利用 WebHook。我通常對(duì)新技術(shù)持保留態(tài)度,但是不得不承認(rèn) GraphQL 在處理某些需求上也能夠比 REST API 做的更好。并且大部分廠商對(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ù)寫下去似乎沒(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)用的話,例如 Slack、Github,你會(huì)發(fā)現(xiàn)它們的接口是在不斷迭代的。不斷有舊的接口被淘汰,新的接口投入使用。這種迭代背后不是閑著沒(méi)事干,而是出于實(shí)際的用戶的聲音和需求
          最后推薦我最近閱讀的關(guān)于 API 設(shè)計(jì)的圖書,收益匪淺:
          • Web API 的設(shè)計(jì)與開(kāi)發(fā)
          • Designing Web APIs
          • APIs A Strategy Guide



          往期推薦

          面試:一個(gè)Java字符串到底有多少個(gè)字符?

          億級(jí)數(shù)據(jù)從 MySQL 到 Hbase 的三種同步方案與實(shí)踐

          贈(zèng)書:全球首本VS Code中文書來(lái)了,高效編程秘訣全收錄!

          自建分布式存儲(chǔ)新選擇,性能是Ceph的1.84倍

          項(xiàng)目是如何死掉的?太過(guò)真實(shí)!


          歡迎加入我的知識(shí)星球,聊技術(shù)、說(shuō)職場(chǎng)、侃社會(huì)。

          頭發(fā)很多的中年程序員DD和他的朋友們?cè)谶@里期待你的到來(lái)!

          加入方式:長(zhǎng)按下方二維碼噢


          最近更新:
          周三技術(shù)人分享:為什么架構(gòu)師大多是后端?
          周六社會(huì)人分享:民法典:聊聊那些跟房子相關(guān)的東西


          我的星球是否適合你?

          點(diǎn)擊閱讀原文看看我們都聊過(guò)啥?

          瀏覽 29
          點(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>
                  台湾无码一区二区 | 成人精品视频网址 | 日本高清色视影www | 免费国产福利 | 无码日韩人妻精品久久蜜桃 |