Backend For Frontend (BFF)
零.背景
服務端需要支援多種前端裝置下的使用者體驗時,常常面臨現有API與某一端UI緊耦合 的情況
比如為PC端頁面設計的API需要支援移動端,發現現有介面從設計到實現都與桌面UI展示需求強相關,無法簡單適應移動端的展示需求
一.現狀
一個後端API層對多個前端(PC端、移動端等)的話,一般是支援一端後,再新增更多功能來支援其它端
It’s tempting to design a single back-end API to support all clients with a reusable API.
多個前端UI要求的API相似的話,這樣做沒什麼問題(稍微增強下現有API就能支援另一種前端體驗),但移動端UI體驗通常與PC端不一樣
Valuable services support many variations in clients, such as mobile versus web and different forms of web interface.
例如:
-
螢幕空間小,所能顯示的資料也少
-
建立多個連線會增加耗電,前端做資料聚合成本高
-
互動方式差異很大,後端API需要支援的功能也不同。比如PC端填表單,移動端掃碼……
所以,移動端通常要:
-
進行不同的(或更少的)API呼叫,比如做聚合
-
展示不同的(或更少量的)資料
另外,一個後端API通常要為多個前端應用提供支援,此時單一後端可能會成為版本迭代的瓶頸,因為每版工作量巨大(對接N個前端應用)。從而產生一個龐大的獨立後端團隊,負責給多個前端團隊提供API,然後各前端團隊都需要與該團隊溝通變更,而該團隊要平衡多個前端團隊的需求優先順序……繼而面臨跨團隊協作低效、資源協調困難 等問題
二.BFF的由來
由於以上種種,我們不再寄希望於一個大後端為多端體驗統一提供API支援,而是給每種使用者體驗對應一個後端(one backend per user experience),稱為Backend For Frontend (BFF) ,譯作使用者體驗適配層
Consequently it’s often best to define different back-end services for each kind of front-end client.
概念上,把每個前端應用拆成兩部分:客戶端應用與服務端的部分(BFF)。其中,BFF是面向特定使用者體驗的,由實現這部分UI的前端團隊負責實現及維護(即UI與對應的BFF由同一個團隊負責)
These back ends should be developed by teams aligned with each front end to ensure that each back end properly meets the needs of its client.
同一個團隊的優勢在於:
-
更容易根據UI來定義或調整API
-
簡化了客戶端、服務端的釋出流程(依賴項更少了)
-
一個BFF只專注於一個UI,更小也更靈活
從服務的角度看,BFF實際上是限制了單一服務所支援的消費者(指前端應用)數量,從而讓它們更易於使用(更貼合前端需要)和更改,並幫助開發前端應用的團隊保留更多的自主權:
The simple act of limiting the number of consumers they support makes them much easier to work with and change, and helps teams developing customer-facing applications retain more autonomy.
三.具體實現
要求BFF與使用者體驗一對一,也就是要把一個大後端拆成多個小後端
細分粒度
既然要拆分,那應該按什麼來分?細分到什麼程度?
不難想到3種拆分方式:
-
使用者體驗級(UI級):每一種UI互動對應一個BFF,比如PC端1個,移動端3個(例如小屏手機、中屏手機和大屏手機三者UI互動差異很大的話,有必要拆成3個BFF)
-
團隊級:每個前端團隊對應一個BFF,按現有組織結構來分
建議進行使用者體驗級拆分,因為端級拆分如果多端UI類似的話,介面大概率是能夠直接複用的,沒必要拆,而組織結構是能夠靈活調整的,不應該限制技術方案
對接下游服務(微服務)
每個BFF需對接多個下游服務,那麼勢必存在幾個問題:
-
如何對接多個技術棧不同的下游服務?
-
如何管理、如何組合這些呼叫?
-
某一個呼叫失敗時,如何保障可用性?
統一的RPC協議能夠抹平下游服務技術棧的差異;對於非同步呼叫的管理,可以藉助ReactiveX/RxJava" rel="nofollow,noindex" target="_blank">RxJava 、Finagle 等事件機制來簡化這些非同步流程控制;部分呼叫失敗時的可用性問題,則可以通過在BFF層容錯,同時前端保證可接受不完整的響應內容來解決
複用問題
拆開之後,多個BFF之間容易產生冗餘程式碼,尤其是一些通用的後端邏輯(如授權、認證、限流等等)
為了消除BFF間的程式碼冗餘,一般採用兩種做法:
-
要麼多BFF合一
-
要麼在BFF之上加一層Edge API service
多B合一的話,又回到了最初的問題,因為想要靈活性才拆開,又因為想要複用而合起來……白折騰了。另一個選項是加一層閘道器服務(Edge API service) ,把通用邏輯放進去,讓BFF得以專注業務邏輯:
It validates and authenticates incoming requests; it enforces rate limits to protect the platform from undue load, and it routes requests to the appropriate upstream services. Factoring these high-level features into the edge service is a huge win for platform simplicity.
回到複用問題本身,我們想消除冗餘,又不想因為抽離可複用程式碼而導致BFF間緊耦合,所以就有了一種折衷的態度:容忍BFF間冗餘、消除單BFF內冗餘 。也就是允許一定程度的BFF間冗餘
當然,複用的前提是多BFF技術棧相同,然後識別出冗餘部分,並重構掉。具體地,到需要抽離公用部分的階段,有幾個選擇:
-
提出公共庫
-
抽出去作為獨立service
-
下沉到下游服務
但公共庫通常是耦合的主要來源 ,比如呼叫下游服務的公共邏輯會引發BFF間耦合。獨立service的方案相對更好一些,可以進一步把新service概念化到領域模型裡。類似地,也可以把公用邏輯下沉到下游服務,讓平級的下游服務變成有依賴的樹結構
無論怎樣解決複用問題,都應該在有必要進行復用 時才去做,一般原則是:
Creating an abstraction when you’re about to implement something for the 3rd time.
四.應用場景
與移動裝置相比,PC裝置效能足夠好,那麼是不是能直接調多個下游服務(成本不很高),不走BFF呢?
實際上,與直接面向前端應用的下游服務相比,BFF的意義在於適合用來實現:
-
服務端模板
-
對資料進行聚合(合併多個介面呼叫)、編排(格式化成前端想要的樣子)、裁剪(去掉前端不需要的資訊)
-
快取聚合呼叫的結果
-
給某種UI體驗(如移動端)提供特定功能
-
供第三方使用的API,便於維護因第三方限制而新增的那部分邏輯
因為BFF是位於下游服務之上的一層,並且細分到使用者體驗粒度,所以要比下游服務更靈活,尤其適合為第三方提供定製API等差異化場景
五.業界實踐
按照BFF理念,把大後端按前端體驗拆分開,如下圖:
具體實踐中,BFF通常不是圖示的樣子,主要變化在於:
-
按業務線拆分BFF
-
加一層閘道器,負責實現路由、認證、監控、限流熔斷、安全等功能
按業務線拆分的BFF更像是建立在下游基礎服務之上的業務型微服務 ,只是這些微服務由對應業務的前端團隊負責開發維護。廣義的,可以理解為更細粒度的BFF,即每塊業務對應一個BFF(不再按使用者體驗差異去分)
閘道器層負責實現通用的邊界服務,如認證、限流等,讓BFF更專注於業務相關的部分:
前端體驗 -------------------- ^^ || 閘道器 ------------ BFFBFF ---------- ^ ^^ ^ /\/\ -------------------- 下游服務
更有甚者,把閘道器層也拆開與BFF一一對應:
前端體驗 -------------------- ^^ || 閘道器閘道器 ---------- BFFBFF ---------- ^ ^^ ^ /\/\ -------------------- 下游服務
P.S.另外,還有不引入BFF,而只添一層轉發服務來解決資料的聚合、編排、裁剪等問題的,類似於GraphQL,但容易出現無意義的資料透傳,所以一般不是全覆蓋的(並非所有資料請求都走轉發服務):
前端體驗 -------------------- ^^ || 轉發服務| --------| ^^| /\| -------------------- 下游服務
探索
實踐中對BFF的探索方向主要有3個:
-
穩定性:保障BFF的可靠性,如通過日誌、錯誤分析、監控、報警等手段
-
同構:讓BFF與前端體驗使用相同的技術棧,如基於Node的同構方案
-
一體化:一方面針對下游服務提供mock方案,另一方面允許同構、非同構應用共存
畢竟在BFF模式下,要求前端開發掌握一定程度的全棧知識(如服務端技能,運維、安全等知識),所以,自然地想要通過同構或一體化方案來提升開發體驗,降低門檻,讓技術無感化
希望越來越多的開發者,可以不用再關注流程、構建、環境、部署等各種事,希望能做到技術無感化(Techless),讓每一位開發著能安安靜靜的快樂編碼。
六.優勢
關注點分離
BFF模式最大好處是關注點分離(separation of concerns) ,下游服務可以專注於業務領域模型,前端UI專注於使用者體驗:
後端可以專注於業務領域,更多從領域模型的視角去思考問題,頁面視角的資料則交給前端型全棧工程師去搞定。領域模型與頁面資料是兩種思維模式,通過BFF可以很好地解耦開,讓彼此更專業高效。
從分工的角度看:
BFF模式不僅僅是一種技術架構,從社會分工角度講,BFF更是一種多元價值導向的分層架構,每一層都有不錯的空間去施展。
自主權
從團隊角度看,傳統的前後端分離主要問題在於:
-
服務的擴充套件性及複雜度
-
前後端團隊的多對一關係,因為前端團隊必然需要細分(由於技術實現的差異,需要專人來做)
-
跨團隊協作成本,例如前端UI開發過程中,後端API可能發生變化(存在跨團隊的溝通協調成本)
在BFF模式下,API的owner是負責實現對應使用者體驗的前端團隊,也就是說前端團隊擁有API的自主權 ,可以快速調整變化
前端與BFF團隊一體化的另一個好處是,可以靈活選擇由客戶端實現還是服務實現 (比如通用性強的由服務實現,甚至為了快速發版過審也可以由服務實現),而不需要跨團隊協調