前端 保存后端傳來數據的id_一篇來自前端同學對后端接口的吐槽

e88b401d010756417a730735558271d5.png

前言

去年的某個時候就想寫一篇關于接口的吐槽,當時后端提出了接口方案對于我來說調用起來非常難受,但又說不上為什么,沒有論點論據所以也就作罷。最近因為寫全棧的緣故,團隊內部也遇到了一些關于接口設計的問題,于是開始思考實現接口的最佳實踐是什么。在參考了許多資料之后,逐漸對這個問題有了自己的理解。同時回想起過去的經驗,終于恍然大悟自己當時的痛點在哪里。

既然是吐槽,那么請原諒我接下來態度的不友善。本文中列舉的所有例子都是我個人的親身經歷。

誰應該主導接口的設計

或者更直白一些,應該是接口的消費方還是提供方來決定接口的設計?

當然是接口的消費方

「接口」最吊詭的地方在于提供方大費周章把它實現了,但它自己卻(幾乎)重來都不使用。于是這極易陷入一種自嗨的境地,因為他更本不知道接口的好壞。就好比一個從來不嘗自己做的菜的廚子,你指望他的菜能好到哪里去,他的廚藝能好到哪里去。上面隱含的前提是(我認為)接口是有絕對好壞之分的,壞的接口消費者調用難受,提供者維護難受,還導致產品行為別扭體驗變差。

然而接口的好壞與誰來主導設計有什么關系?因為壞接口產生的原因之一是提供方只站在開發者的角度解決問題:

例子一 (Chatty API)

某次需要實現允許用戶創建儀表盤頁面的功能(如果你對儀表盤頁面感到陌生的話,可以想象它是一張集中了不同圖表的頁面,比如柱狀圖、折線圖、餅圖等等。用戶可以添加自己想要的圖表到頁面中,并且手動調整它們的尺寸和位置。儀表盤通常用于總覽某個產品或者服務的運行狀態)。后端同學的接口初步設計是,當用戶填寫完基本信息、添加完圖表、點擊創建按鈕之后,我需要連續調用兩次接口才能完成一次儀表盤的創建:

  1. 利用用戶填寫的基本信息以及圖表的尺寸和位置創建一個空的儀表盤
  2. 再向儀表盤中填充圖表的具體信息,比如圖表類型,使用的維度和指標等

很明顯看出他完全是按照自己后端的存儲結構在設計接口,不僅是存儲結構,甚至存儲過程都一覽無余。想象一種極端的情況,那不只提供一些更新數據庫表的接口得了,前端自己把通過接口把數據插入庫中面對這類底層性質的接口,消費者在集成時需要考慮接口的調用步驟以及理解背后的原理。如果后端的底層結構一旦發生更改,接口很有可能也需要發生更改,前端的調用代碼也需要隨之更改。后端研發可能會辯解說:后端用了微服務啊,不同類型的數據存儲在不同的服務上,所以你需要和不同的服務通信才能實現完整的存儲。他們始終沒有明白的事情是,后端的實現導致了接口的碎片化,那是你的問題,而不應該把這部分負擔轉移到前端的開發者上,其實也是間接轉移到了用戶身上。不要欺負我不懂后端,至少我了解加一層類似于 BFF 的 Orchestration Layer 就能解決這個問題Netflix 的工程師 Daniel Jacobson 在他的文章 The future of API design: The orchestration layer 中指出, API 無非是要面對兩類受眾:

  1. LSUD: Large set of unknown developers
  2. SSKD: Small set of known developers

隨著產品服務化的趨勢,很有可能需要像 AWS 或者 Github 那樣對公共開發者即 LSUD 暴露接口。且不說上面例子中的接口方案會不會被唾沫星子淹死,如此明顯的暴露內部服務的細節是非常危險的事情。所以在設計接口時,應該讓消費者來主導。如果消費者沒能給出很好的建議,那么至少提供者在設計時也應該站在消費者的立場上來思考問題。又或者,至少想一想如果你自己會樂意使用用你自己設計出來的接口嗎?使用后端思維設計接口不僅體現在 URI 的設計上,還有可能體現在請求參數和返回體結構上:

例子二

假設現在需要一個請求批量文章的接口,接口同時返回多篇文章的內容,包括這些文章的內容,作者信息,評論信息等等。理想情況下,我們期望返回的數據是以文章為單位的,比如

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

However, 后端的返回結果可能是以實體為單位:

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

comments 里包含不同文章的 comment,我必須通過類似于 articleId 的字段對它們執行 group by 操作才能分離出屬于不同文章的評論。對其他實體做同樣的操作,最終手動的拼接成前端代碼需要的 articles 數據結構很明顯這又是按照后端庫表關系返回的結果,嚴格來說這并不算是 anti-pattern,在 redux 中也鼓勵將數據 normalize。但如果前端用不到原始數據,請不要返回原始數據。例如我需要在頁面上展示一個百分比格式的數據,除非用戶有動態調整數據格式的需求,例如千分位、小數或者是切換精度等等,否則就直接返回給我百分比的字符串就好了,不要返回給我原始的浮點數據。前端對數據的二次加工還會給問題排查帶來干擾,如果任何數據都需要前端進行二次加工,那么所以問題的排查都必須從前端發起,前端確認無誤后再進入后端排查流程,這始終會占用兩個端的人力,并且 delay 了排查的進度

關于 meta 信息

例子三:

后端接口在返回時通常會帶上 meta 信息,meta 信息包含接口的狀態以及如果失敗時的失敗原因,便于調試使用。后端提供的接口的 meta 信息的數據結構如下:

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

在我看來,以上數據結構有兩個問題

meta 信息包含獨立的狀態信息

在包含狀態碼的 meta 信息接口設計中,一條默認的隱藏邏輯是:接口返回的 HTTP status code 一定是 200,數據是否真的獲取成功需要通過 meta 里的自定狀態碼 code 進行判斷(換句話說,上面你看到的接口實際上是 “接口的接口”)。最終在前端的代碼中也不需要通過 HTTP code 判斷返回是否正常,只需要判斷接口里返回的meta.code即可** 但是誰給你們的自信保證后端接口一定是不會掛的?!** 無論后端如何保證接口的堅固,前端仍然需要首先判斷 HTTP code 是否為 200,再判斷meta.code是否與預期的符合一致。這和信任無關,和我程序的健壯有關。既然無論如何都要對接口判斷兩次,那為什么不將meta.code與 HTTP code 合二為一?更何況我還需要再本地維護一份自定義 code 的枚舉值,還需要和后端保證同步。這就涉及到下一個問題了:

meta 信息的存放位置

我們需要 meta 信息沒有錯,但是我們沒有那么需要 meta 信息。這體現在幾點:

  1. 我們真的需要一個平行于返回結果的字段展示 meta 信息嗎?
  2. 每一次請求我們都需要 meta 信息嗎?
  3. meta 信息一定要在 meta 字段里嗎?

以請求失敗的錯誤信息為例,錯誤信息只會出現在接口非正常返回的情況下,但我們應該始終在返回體中用一個字段為它預留位置嗎?在關于 meta 信息存在位置的這個問題上,我傾向于將它們整合進入 HTTP Header 中。例如meta.code完全可以使用 HTTP code 代替,我看不出始終要保證 200 返回以及自定義 code 的意義在哪里而至于其它的 meta 信息,可以通過以X-開頭的自定義 HTTP Header 進行傳遞。例如Github API 中關于使用頻率限制的信息就放在 HTTP Header 中:

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

Design for today

例子四

我們需要為某個指標的折線圖設計查詢接口,查詢以天為單位,也就是說該接口只會根據查詢的日期返回指定日期的查詢結果,后端提供的返回數據結構如下:

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

雖然需求很明確的指示只會返回某天的查詢結果,但是后端還是決定給我返回一個數組。他這么設計的理由是為了防止日后需求發生改變需要返回多日的查詢結果。這看上去是很聰明決策:“看,我預見性的 cover 了一個未來的需求!”,但實際上愚蠢至極:你的確 cover 了一個需求,不過是一個當前并不存在,未來也不見得會發生的需求;而且如果你真的想寫 future-proof 的代碼,那么還有未來千千萬萬的需求等待著你實現。問題在于沒有人知道將來是否真的會允許同時查詢多日數據,即使某天需要支持同時查詢多日數據了,數據結構也不一定非要如此。在數據分析領域我們面臨的查詢需求并不是線性從單個到多個,在其他業務領域也是這樣。這樣導致的后果是你花費多余的時間實現了不需要的代碼,并且前端也需要配合這樣的數據結構進行實現。并且在將來的維護中,每個看到返回體是數組的人都會納悶為什么返回的結果明明只有一條,還需要用數組封裝,是不是我遺漏了什么?于是不得不投入精力來驗證是否真的有可能返回更多的數據。API 和代碼應該是精準的,準確表達你想實現的一切而不存在有歧義有人可能會說不就是多了一層封裝嗎?實現上也花不了多少的功夫何至于大驚小怪。抱歉我不是針對這一個 case,而是在強調任何場景下無論實現的難易都不應該添加無意義的代碼,“勿以惡小而為之” 就是這個道理“關注當下” 還有另一個維度含義:

例子五

目前我們已經有創建單個文章的接口,現在需要支持批量創建文章。后端給出的建議是:不如調單個接口多次?

例子六

目前已經有一個接口能夠取得文章相關數據,比如內容、評論、作者等等。現在我們需要增加一個新的頁面用于展示用戶信息。后端給出的建議是:不如使用文章數據接口,里面已經包含了作者信息,這樣就不用開發新的接口了以上的例子看似都是想實現對接口的復用,但實際上起到的是事倍功半的效果在例五中,雖然語義上 “創建五篇文章” 和“連續五次創建一篇文章”是等效的,但是在實現和操作層面并不是如此。且不說調用五次和調用一次的性能大不相同,批量創建的五篇文章可能存在順序關系,可能需要事務操作。在例六中雖然能夠達到我們實現的效果,但這不能算是接口的復用,只能算是接口的 hack(hack 和復用的區別在于是否用物品的初衷功能做事情)。并且 hack 接口是有風險的,對于接口的提供者而言他們更關心接口服務 “正統” 的消費者,在這個 case 中接口的存在是為了展示完整的文章信息,如果有一天 “文章信息” 這個需求發生了變化很有可能會導致作者信息同時發生變化,縮減字段甚至取消字段。那么它們沒有義務這些 hack 用戶負責。一個接口本應該就專注一件事情所以最理想的事情是,為當前專注的業務開發獨立的接口。在例六的例子中,可能我們在開發一個獨立請求作者的信息的接口時實現代碼完全復制自另一個接口的實現,但是接口的隔離在長遠看來能給功能的維護帶來更大的便利

不僅限于 REST API

“接口” 是一個概念。在概念之下如何實現它我們擁有很多種選擇。目前看來絕大部分的方式是通過 REST API 來達成的,也并沒有什么事情是 REST API 無法做到的,但事實上這幾年技術的進步給了我們更多的選擇,如果選擇更有針對性的實現方案,效果會更好例如在實時數據的場景下,理論上是由后端(有數據更新時)驅動前端視圖的更新,這理應是 push 操作。但是在傳統實現中,我們不得不仍然通過被動的等待和輪詢實現功能。對于事件驅動類型的需求使用 WebSocket 或者是 Streaming 似乎是更好的選擇。如果是后端之間的交互還可以利用 WebHook。我通常對新技術持保留態度,但是不得不承認 GraphQL 在處理某些需求上也能夠比 REST API 做的更好。并且大部分廠商對于 GraphQL 接口的支持表明它是可行的。我了解實現 API 來只是后端實現功能的一個很小的環節,在接口背后是更多業務邏輯的修改和庫表結構的更迭。甚至說接口部分有一半都是交給框架來實現的。但是,哪怕只有很小的機會,也應該把這個環節做到盡善盡美。

結束語

對于糟糕的接口設計我還能繼續沒完沒了的抱怨下去,但突然然覺得洋洋灑灑的繼續寫下去似乎沒有太大意義。講真我不是來真的大吐苦水的,只是想表達接口設計也至關重要。在工作中痛心的看到很多問題明明用一些很基礎的技巧就能夠解決,而大家卻對它熟視無睹以造成兩敗俱傷的境地。以上就是我認為的在接口設計中需要遵循的一些原則和考慮要素,相信能夠解決大多數的痛點和避免部分的問題后端同學們,如果你們有心讓接口變得更好,多聽聽 “消費者” 的反饋。如果你們嘗試使用過第三方接口開發過應用的話,例如 Slack、Github,你會發現它們的接口是在不斷迭代的。不斷有舊的接口被淘汰,新的接口投入使用。這種迭代背后不是閑著沒事干,而是出于實際的用戶的聲音和需求最后推薦我最近閱讀的關于 API 設計的圖書,收益匪淺:

  • Web API 的設計與開發
  • Designing Web APIs
  • APIs A Strategy Guide

作者:李熠

鏈接:http://juejin.im/post/5cfbe8c7e51d4556da53d07f

文章著作權歸作者所有,如有侵權,請聯系小編刪除。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/457997.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/457997.shtml
英文地址,請注明出處:http://en.pswp.cn/news/457997.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2018-2019-1 《信息安全系統設計基礎》教學進程

《信息安全系統設計基礎》教學進程 目錄 考核方式暑假準備教學進程 第01周學習任務和要求第02周學習任務和要求第03周學習任務和要求第04周學習任務和要求第05周學習任務和要求第06周學習任務和要求第07周學習任務和要求第08周學習任務和要求第09周學習任務和要求第10周學習任務…

python字符串是什么_python字符串詳解

字符串詳解 在python中引號引起來的就是字符串 字符串是用來存儲少量數據 索引 索引(下標) 通過索引可以精確的定位到某個元素 name "meat" meat 每一個字母叫做一個元素 # 0123 從左向右代表每一個字母 #-4-3-2-1 從右向左每一個字母 print(name[-1]) 輸出 t name …

最小編輯代價

最小編輯代價問題: 對于兩個字符串A和B,我們需要進行插入、刪除和修改操作將A串變為B串,定義c0,c1,c2分別為三種操作的代價,請設計一個高效算法,求出將A串變為B串所需要的最少代價。 給定兩個字…

Android中的數據庫

2019獨角獸企業重金招聘Python工程師標準>>> 1.1. 什么時候使用數據庫 有大量相似結構的數據需要存儲的時候就可以使用數據庫。 1.2. SQLite的簡介 SQLite是一款輕量級的數據庫。它的設計目標是嵌入式的,而且目前已經在很多嵌入式產品中使用了它。Androi…

python計算績效工資_python實現 --工資管理系統

原博文 2017-07-25 22:41 ? # -*- coding: utf-8 -*- __author__ hjianli # import re import os info_message """Alex 100000 Rain 80000 Egon 50000 Yuan 30000 """ #序列字典 xulie_...01669 相關推薦 2019-09-28 21:13 ? Python python…

為Windows Server 2012 R2指定授權服務器

為Windows Server 2012 R2指定授權服務器在Windows Server 2008 R2的終端服務中,可以手動指定授權服務器,而在Windows Server 2012 R2中,默認只能通過"遠程桌面連接服務"管理器,指定授權服務器,而要使用遠程…

spring5高級編程_Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新

簡介是什么讓java世界變得更好,程序員變得更友愛,禿頭率變得不是那么的高,讓程序員不必再每天996,有時間找個女朋友?是Spring。是什么讓企業級java應用變得簡單易懂,降低了java程序員的進入門檻&#xff0c…

關于resolve非泛型方法不能與類型實參一起使用

今天mvc新建三層時,寫到bll層中一直報下面的錯誤,檢查了幾遍趕腳并沒有什么錯。最后發現缺少一些引用。 如下面的圖,少添加了下面的兩個引用.Unity是微軟模式與實踐團隊開發的一個輕量級、可擴展的依賴注入容器, Microsoft.Practices.Unity.C…

設計模式-Singleton

2019獨角獸企業重金招聘Python工程師標準>>> Singleton算是知道的設計模式中最簡單的最方便實現的了,模式實現了對于類提供唯一實例的方法,在很多系統中都會用到此模式。在實際項目中使用全局變量,或者靜態函數等方式也可以達到這…

dump分析工具_Java應用CPU過高,如何排查?參考解決思路和常用工具總結

本文總結了一些常見的線上應急現象和對應排查步驟和工具。分享的主要目的是想讓對線上問題接觸少的同學有個預先認知,免得在遇到實際問題時手忙腳亂。畢竟作者自己也是從手忙腳亂時走過來的。只不過這里先提示一下。在線上應急過程中要記住,只有一個總體…

st官網下載stm32固件庫方法

進入www.st.com官網------把網站改成中文(就在右上方)----點擊產品-----選擇右側的微控制器選項------選擇左側的STM32 32位ARM CortexMCU-----選擇左側的STM32F1系列-----選擇STM32103-----選擇中間部分mcu對應型號(我用的是STM32F103ZE)---…

mysql5.5提示Deprecated: mysql_query(): The mysql extension is deprecated

解決方法1&#xff1a;在php程序代碼里面設置報警級別 <?php error_reporting E_ALL & ~E_DEPRECATED 方法2&#xff1a;禁止php報錯 display_errors On 改為 display_errors Off 方法3&#xff1a;使用mysqli或者PDO 建議大家盡快取消mysql&#xff0c;全部都走…

JavaScript強化教程 —— Cocos2d-JS極速調試技巧

本文為 H5EDU 機構官方 HTML5培訓 教程&#xff0c;主要介紹&#xff1a;JavaScript強化教程 —— Cocos2d-JS極速調試技巧 本文教大家一個調試Cocos2d-JS的小技巧&#xff0c;我都是這么用的&#xff0c;特意來告訴大家這個輕量快速的調試技巧。1.首先我們需要安裝官方的cocos…

dos攻擊命令_Kali Linux系列之拒絕服務攻擊(DOS)實戰(上)

(你的世界是個什么樣的世界&#xff1f;你說&#xff0c;我們傾聽!)-----------------小百科拒絕服務攻擊即是攻擊者想辦法讓目標機器停止提供服務&#xff0c;是黑客常用的攻擊手段之一。其實對網絡帶寬進行的消耗性攻擊只是拒絕服務攻擊的一小部分&#xff0c;只要能夠對目標…

stm32定時器配置

stm32通用定時器 STM32的定時器是個強大的模塊&#xff0c;定時器使用的頻率也是很高的&#xff0c;定時器可以做一些基本的定時&#xff0c;還可以做PWM輸出或者輸入捕獲功能。 時鐘源問題&#xff1a; 名為TIMx的有八個&#xff0c;其中TIM1和TIM8掛在APB2總線上&#xff0c;…

SQL 養成一個好習慣是一筆財富

來源&#xff1a;MR_ke 鏈接&#xff1a;http://www.cnblogs.com/MR_ke/archive/2011/05/29/2062085.html 我們做軟件開發的&#xff0c;大部分人都離不開跟數據庫打交道&#xff0c;特別是erp開發的&#xff0c;跟數據庫打交道更是頻繁&#xff0c;存儲過程動不動就是上千行&a…

【JAVA】StringTokenizer 迭代方式對字符串進行分割

StringTokenizer是一個用來分隔String的應用類&#xff0c;相當于VB的split函數。1.構造函數public StringTokenizer(String str)public StringTokenizer(String str, String delim)public StringTokenizer(String str, String delim, boolean returnDelims)第一個參數就是要分…

python數組定義_python定義數組

廣告關閉 騰訊云11.11云上盛惠 &#xff0c;精選熱門產品助力上云&#xff0c;云服務器首年88元起&#xff0c;買的越多返的越多&#xff0c;最高返5000元&#xff01; 一、一維數組 1. 直接定義matrix2. 間接定義matrixprint(matrix)輸出&#xff1a;3. 數組乘法matrix*5print…

Android-語言設置流程分析

Android手機語言切換行為&#xff0c;是通過設置-語言和輸入法-語言來改變手機的語言&#xff0c;其實這個功能很少被用戶使用。 以Android5.1工程源碼為基礎,從設置app入手來分析和學習語言切換的過程:一、語言設置界面&#xff1a;首先在設置app中找到語言設置這個Preference…

charles 安裝 ssl_最全面的解決Charles手機抓包的證書問題(步驟非常詳細)

源自公眾號文章: 徹底解決Charles手機抓包的證書問題簡介: Charles 抓包是日常開發當中經常會用到的技術, 在 Android 6 之前, 手機系統既信任系統內置的證書, 也信任用戶自己安裝的證書, 但是在 Android 7 之后, 卻發生了變化, 手機系統只信任系統內置的根證書. 當然了, 這是為…