
BFF —— Backend for frontend(服務(wù)于前端的后端),是為了讓后端API滿足不同的前端使用場景而演進(jìn)出來的一種模式。這篇文章我們主要來看看在BFF的實施過程中可能遇到哪些”坑“,為了能夠快速理解后面講到的問題,我們先來簡單回顧下BFF的由來和應(yīng)用場景。
BFF的由來
隨著移動設(shè)備的快速發(fā)展以及產(chǎn)品對用戶交互體驗的關(guān)注度增強,前后端分離的架構(gòu)模式也逐漸被大多數(shù)企業(yè)所采用。在這種模式下,統(tǒng)一的后端API很難滿足在不同場景下對用戶體驗的不同需求。BFF模式應(yīng)運而生,一方面BFF隔離了因前端UI展示對后端API的需求,企業(yè)可以在后端構(gòu)建核心業(yè)務(wù)能力,另一方面,BFF可以根據(jù)已有的后端API,快速滿足前端在UI展示上的需求,來不斷提升用戶體驗;
另外,從知識管理的角度,BFF模式讓知識邊界定義的更清晰,后端專注于構(gòu)建業(yè)務(wù)能力,不需要考慮前端各種場景適配的問題;而前端更關(guān)注用戶體驗,可以隨時獨立發(fā)布更新。
BFF的應(yīng)用場景

面向前端
BFF為前端而生,隨著前端技術(shù)(iOS、Android、小程序、Web等)的不斷發(fā)展,不同前端對后端要求有很大差異,后端服務(wù)很難提供滿足多個前端的統(tǒng)一接口,BFF則可以針對前端的特定需求,做出適配:
- 針對前端UI展示邏輯的不同,對后端API返回的數(shù)據(jù)進(jìn)行裁剪和重新組織,提供面向前端的定制化格式的數(shù)據(jù)
- 根據(jù)前端業(yè)務(wù)需求,對后端多個API返回的數(shù)據(jù)進(jìn)行聚合
- 對一些特定場景的數(shù)據(jù)進(jìn)行緩存,提高性能,進(jìn)而提升用戶體驗
面向后端
BFF隔離了前端UI展示對后端API的定制化需求,可以很好地支持后端服務(wù)的演進(jìn):
- 從單體應(yīng)用遷移到微服務(wù)架構(gòu),后端架構(gòu)的演進(jìn)可以通過BFF來進(jìn)行隔離,參見《 前后端分離演進(jìn):不能微服務(wù),那就 BFF 隔離 》。
- 微服務(wù)架構(gòu)內(nèi)部的演進(jìn),微服務(wù)的拆分,也可以借助BFF來減少對前端的影響,參見我的另外一篇文章《迭代開發(fā)中的微服務(wù)拆分》。
BFF實現(xiàn)中的坑
BFF模式在前后端分離的架構(gòu)模式下的確有很多好處,完美隔離了前后端,貌似從此以后前端干前端的,后端干后端的,不會再有沖突,一切是那么美好。然而事實并非如此,在實際實施BFF的過程中,大家仍然會遇到各種問題,下面介紹幾個常見的實施BFF過程中的問題:
重復(fù)代碼
通常情況下我們會為每個不同的前端構(gòu)建一個BFF,還可能會為一些特定的場景建立BFF(如對第三方系統(tǒng)提供API),不可避免的,多個BFF之間會出現(xiàn)大量的重復(fù)代碼,比如可能會存在相同的數(shù)據(jù)轉(zhuǎn)換邏輯,相同的API數(shù)據(jù)聚合邏輯等等。
重復(fù)代碼通常被認(rèn)為是一種壞味道,但在BFF這個場景下,這些重復(fù)的代碼是為了某一個特定的前端服務(wù)的,因此處理這個重復(fù)要相對謹(jǐn)慎,如果一味粗暴地去掉這些重復(fù),很有可能引發(fā)某一個前端需求變化會影響到其他前端應(yīng)用的情況。
如果確實有些代碼重復(fù)且相對穩(wěn)定,可以嘗試采取下面的方法來消除重復(fù):
- 共享庫,將重復(fù)的代碼抽取出來放到共享庫中,不同的BFF通過依賴共享庫來達(dá)到代碼復(fù)用,但也要考慮哪些代碼能夠抽取到共享庫中,可以參考我之前的文章 《消滅微服務(wù)的壞味道 之 共享庫 》。
- 將重復(fù)的代碼放到后端服務(wù)中,這種方法適用于重復(fù)的代碼實際承載了真實的業(yè)務(wù)邏輯,而不只是在做簡單的數(shù)據(jù)聚合和數(shù)據(jù)格式轉(zhuǎn)換,那么應(yīng)該把這些重復(fù)的代碼下沉到指定的后端服務(wù)中。
趨向于ESB
除了大量的重復(fù)代碼,在微服務(wù)架構(gòu)下,另外一個問題通常會出現(xiàn)在業(yè)務(wù)演進(jìn)過程中。當(dāng)新的業(yè)務(wù)需求產(chǎn)生時,具體要在哪個后端服務(wù)中實現(xiàn)有時候不是一個很容易回答的問題,特別是不同的服務(wù)有不同的團隊歸屬時,如果每個服務(wù)的歸屬團隊都認(rèn)為新的業(yè)務(wù)需求不是自己服務(wù)的業(yè)務(wù)范疇,最可能的結(jié)果就是讓BFF負(fù)責(zé)幫忙組合各個服務(wù)的功能,完成這個新的業(yè)務(wù)需求。漸漸地,BFF朝著ESB的方向發(fā)展,變成了集成各個微服務(wù),對外提供新能力的中間件。
這種趨勢某種程度上違反了BFF設(shè)計的初衷,BFF為了適配前端而生,而真實的業(yè)務(wù)能力應(yīng)該由后端服務(wù)來承接,而不是BFF。
要解決這個問題,首先要清晰的定義每個服務(wù)的業(yè)務(wù)職責(zé),為了避免服務(wù)過多很難劃分清晰邊界,最開始服務(wù)盡量不要拆的太??;另外要建立規(guī)則來處理新的需求,哪些需求屬于BFF的范疇,哪些屬于業(yè)務(wù)能力,業(yè)務(wù)能力應(yīng)該下沉到后端服務(wù)中,如果現(xiàn)有服務(wù)無法承載這種能力,可以考慮新增服務(wù),而不是寫在BFF中。
性能問題
還有一個比較明顯的問題就是性能問題,想象下面的場景(相信你一定遇到過),既然有了BFF,前端的設(shè)計真的可以放飛自我了,可以把想展示的信息一股腦都放在一起展示,極端情況下BFF可以獲取到任意服務(wù)的數(shù)據(jù)進(jìn)行組合,我曾見過在一個BFF接口中調(diào)用了后端服務(wù)幾十次來拼湊前端需要的數(shù)據(jù),可想而知這個接口的性能一定不會好。
為了解決這種情況帶來的性能問題,通常會采用并發(fā)獲取數(shù)據(jù)然后拼裝的方式,這明顯增加了代碼實現(xiàn)和維護(hù)的復(fù)雜度,往往會引發(fā)新的問題。
因此,在實際開發(fā)過程中要非常警惕這種問題的發(fā)生,如果一個BFF接口調(diào)用了3個以上接口,那就要分析下后端服務(wù)拆分的合不合理,前端UI展示的數(shù)據(jù)是否有必要放在一起,否則性能問題會逐漸成為一個不可避免的問題。
小結(jié)
架構(gòu)設(shè)計是通過合理的組件拆分以及定義組件之間的關(guān)系,將系統(tǒng)整體的復(fù)雜性分散到不同的組件中,在更低的維度上解決問題,分而治之。BFF在前后端分離的架構(gòu)模式下隔離了前端和后端的關(guān)注點,特別是在多個前端或第三方的情況下,BFF都是非常好的選擇。然而在實際實現(xiàn)過程中,仍然要時刻警惕,明確BFF設(shè)計的初衷,避免因引入BFF而帶來了更多的問題。
參考資料
[Pattern: Backends For Frontends] https://samnewman.io/patterns/architectural/bff/
[BFF @ SoundCloud] https://www.thoughtworks.com/insights/blog/bff-soundcloud
[Frontend Architectural Patterns: Backends-For-Frontends] https://medium.com/frontend-at-scale/frontend-architectural-patterns-backend-for-frontend-29679aba886c