前端開發者該負責寫 API Endpoints 嗎?The Backend For Frontend Pattern (BFF) In Microservices World

莫力全 Kyle Mo
Starbugs Weekly 星巴哥技術專欄
12 min readMay 8, 2021

相信不少前端開發者在求職的時候,都會看到一個被列為加分條件的項目 — 「熟悉後端程式開發者佳」,除了本來職位就是做 Full-Stack 的工作的人以外,不少前端開發者可能會疑惑,現在前端就已經夠複雜了,我會後端技術又能夠用在哪呢?

From 104 — 加分條件:後端開發經驗

讓我們從技術的演進探索一些可能的原因:

Monolithic To Microservices

隨著近年來容器化技術的發展,許多軟體從 Monolithic 單體式架構慢慢轉移到了 Microservices 微服務架構

可能有讀者會覺得,怎麼突然講到微服務去了,有點偏題了吧?不,其實這很重要,因為後端的架構方式改變,連帶影響到前端與後端的溝通方式,在微服務架構下,前端需要與不同的 services 溝通,獲取不同服務提供的資料。

如果不知道 Microservices 微服務是什麼的讀者,可以參考以下這篇文章

API Gateway Pattern In Microservices

在談今天的主角 BFF 以前,要先來介紹一下 API Gateway 這個在 Microservices 中常用的 pattern,因為其實嚴格來說 BFF 不過是 API Gateway Pattern 的一個變形罷了。

這邊的 API Gateway 不是指 AWS 的那個 API Gateway,而是指一種模式 (Pattern),可以說 AWS 的 API Gateway 服務也是以這個模式為根基建構出來的服務,只是提供了更多強大的功能。

剛剛提到當後端轉換成微服務架構後,前端需要與不同的服務去溝通,如果採用最單純的方法的話,就是前端分別與每個服務去做溝通,這種方式又被稱為 Direct Client-To-Microservice Communication

Direct Client-To-Microservice Communication 圖片來源

不過這個方式有幾個明顯的缺點,顯然大多數的應用不會採用現在的架構:

  • Client 與服務間高耦合

Client Side 的應用會與各個服務緊緊耦合,如果今天服務更新,Client Side 也需要做相對應的改變,造成難以維護的狀況。

  • 過多的 Round Trips

Client Side 的單一頁面也許就需要對多個服務進行呼叫,導致 Client Side 與 Server Side 有多個 Network Round Trips,增加了產生 latency 的可能性。

  • 安全性問題

Direct Client-To-Microservice Communication 下,所有服務的 Endpoints 都必須公開給外部世界知道,提升被惡意攻擊的機會。

  • Cross Cutting Concerns

每個服務可能會有一些共通的邏輯,例如 Authorization 或 SSL 加密,與其在各個服務都實作類似的事,統一在單一階層處理顯然是更好的選擇。

看下來顯然 Direct Client-To-Microservice Communication 在微服務架構中似乎不是一個很好的選擇,那來看看 API Gateway Pattern 是什麼,還有它是否可以解決上述的缺點吧!

圖片來源

從上圖可以看出,API Gateway 模式其實就是在 Client Side 與 Services 間的一層 Proxy Server,它的優點與特性主要有:

  • 可以作為 Reverse Proxy
  • Layer 7 的 Routing,使 Client Side 與 Services 解耦
  • 當有需要將應用架構從 Monolithic Refactor 到 Microservices 時,API Gateway 可以使 Client Side 在不知情的狀況下,漸進式的將 API 從單體式過渡到微服務架構。
  • Requests 的 Aggregation,這可以解決上面提到直接與 Services 溝通可能產生的過多的 Round Trips,導致 latency 的問題。Client Side 的一個頁面中可能需要來自不同服務的資料,與其直接從 Client Side 發出多個 Network Requests,Client Side 可以對 API Gateway 發出單一請求,再由 API Gateway Proxy Server 去呼叫各個服務,並將 Client Side 需要的資料先聚集起來,一次交給 Client。假設這邊的 Client Side 指的是瀏覽器好了,這個方法達到了減少 Client Side 與 Server Side 溝通的目標,畢竟一般來說伺服器端的溝通延遲遠小於 Client Side,再者這個方式也讓程式更好維護與擴展。
  • 將一些通用邏輯與功能放到 Gateway 層做,不過這邊需要注意的一點是,我們仍然需要注意 Gateway 的工作量,並不適合把過多的工作搬到這層來,這點稍後會更詳細說明。

不過 API Gateway Pattern 也是有一些缺點的:

  • 產生 Single Point Of Failure 的可能性
  • 因為在 Client 與 Services 間多加了一層,理應來說會增加 Latency,不過這個額外的網路請求的成本往往低於在 Client Side 過於頻繁的直接呼叫微服務所產生的延遲
  • 架構變的複雜,需要額外的開發與維運成本
  • 如果需要一個專門的團隊來開發 Gateway 層,似乎會讓開發效率變得低落,溝通成本也會提升,不過關於這點,今天的主角 BFF 會提供一個解法

Backend For Frontend Pattern In Microservices

上述的 API Gateway Proxy 可以看作是一個 General Purpose 的 API Backend,不管是什麼類型的 Client 都需要經過它,再跟不同的 Services 溝通。

圖片來源

不過當 Client 的種類一多(例如 Web App、Mobile App、Desktop App ),它們又都需要同一個 Backend Endpoint 的資料時,這種方式可能會產生一些問題,原因在於,每個 Client 所需要的資料可能會不太ㄧ樣。舉例來說 ,一般來說手機版的頁面相較於桌面版的頁面來說,因為版面大小的問題,會顯示比較少量的資料,Mobile 與 Desktop 的交互行為也差異很大,例如手機上可能希望盡量減少 Network Request 以節省電量的消耗,再極端一點的例子是不同 Client 種類要求的資料格式不同(例如一個接受 JSON、另一個則是 XML)。面對這些不一致,只能依賴這個 General Purpose API Backend 去針對不同的 Client 做處理,想當然,這樣的程式架構會造成混亂,也不易擴展,想像如果今天要加入新的 Client 種類,只能在 General Purpose API Backend 額外對新的 Client 的需求做對應的邏輯處理。

Sam Newman 在 2015 年首先提出了 「Backend For Frontend 前端模式的後端」的架構模式,主要的概念是根據「User Experience」去切分 Gateway 的種類,白話一點就是根據不同裝置建議獨立的 Gateway Proxy Server,達到「One Backend Per User Experience」。

圖片來源

採用這種架構的優勢有:

  • 更容易根據各個 Client 來調整 API
  • 一個 BFF 專注於特定種類 Client,更易維護與擴展

而這邊要帶回本文的標題,前端開發者在什麼狀況下需要負責開發 API Endpoints 呢?如果採用 BFF 架構,這就會是一個適合的場景。

如果採用了 BFF 架構,最理想的狀況下應該是每個 BFF Backend 都交由對應的 Client 團隊負責開發,例如 Web Frontend Team 應該負責 Web BFF Backend Server 的開發,App Team 則負責 App BFF Backend Server 的開發。如此一來可以針對不同 Client 的資料需求做出迅速的開發或改變,Backend Team 則可以聚焦在 Services 的開發。通常這種架構下,每個 BFF 會選用跟對應 Client 相同的技術棧或程式語言來開發(例如 Web 端使用JavaScript 來做全端開發),我想在這種架構下,前端開發者是需要對後端開發有一定的基礎認識與技術的,同樣的也需要撰寫前端要呼叫的 API Endpoints。

接下來要來談談 BFF 的拆分方式,剛剛提到最基本的拆分方式是依照裝置去拆分,不過不同的拆分方式可是會大大影響著團隊的分工模式的,因此只能說必須依照個別狀況與需求去找到適合的拆分方式,而沒有絕對適合的場景。例如有的團隊會在 App Team 中再細分 iOS Team 與 Android Team,這時候團隊可能適合這樣的架構

圖片來源

以上的架構適合使用在 iOS 與 Android 的行為或是資料非常不一致的狀況,如果兩個平台基本上功能與 UI 都差不多一致,也許更加適合建立單一 Mobile BFF 的架構

畢竟如果兩個平台的行為幾乎一致,硬要拆出兩個 BFF 的結果只是會產生許多重複冗余的程式,還有造成資源與部署成本的浪費而已。

因此 BFF 的拆分方式實際上還是要依照各個團隊的組成去做調整,沒有一定完美的拆分方式。

在採用 BFF 以後,可能會遇到的一個問題,就是不同 BFF 間其實存在一些重複的程式碼與邏輯,有些相同的部分甚至可以拆分出去,遇到這種狀況,我們可能有幾個解法:

  • 多個 BFF 合成一個 BFF (但這顯然違反一開始拆成多個 BFF 的初衷)
  • 將共用的邏輯拆成獨立的 Downstream Services
  • 在 BFF 的上一層再加一層 Edge Service,把通用邏輯放到那層,BFF 則專注於不同 Client 的業務邏輯,如下圖:
圖片來源

不過這個問題ㄧ樣屬於一個開放式的問題,沒有絕對正確的方案,不同狀況有不同適合的解法,有興趣的讀者可以額外參考這篇文章

歸納與結論

本文一開始先提到目前架構的演進,越來越多應用選擇採用微服務的架構,在微服務架構下許多 Pattern 也被提出來,改變了前後端的溝通與開發模式。再來提到了 API Gateway Pattern,理解它的概念後再帶出本篇文章的主角 BFF,實際上它可以算是 API Gateway Pattern 的一種變形,主要希望可以達到「One Backend Per User Experience」。當然它有它的優缺點,適不適合採用這種架構還需要依照現有團隊的組成與架構去做近一步的判斷。它主要的優點有

  • 關注點分離(Separation Of Concerns),後端可以更專注在 Services 的業務邏輯,前端則專注在使用者體驗。
  • 前端團隊擁有比以往更多的 API 的自主權,可以快速因應變化與需求。

因此我認為在採用這種架構下,前端工程師應該成為一個「前端型的全端工程師」,了解一些後端開發的技術,除了 Client Application 以外,也負責開發與維護 BFF Server,提升開發與溝通的效率。

如果想了解業界如何實際使用 BFF Pattern,可以參考 SoundCloud 的經驗分享

(P.S. 其實不同 Client 需要的資料不同的這個問題,GrapgQL 似乎是不錯的解決方案,因此有人也主張使用 GraphQL 來當作 BFF,有興趣的讀者可以參考這篇文章)

References

--

--

莫力全 Kyle Mo
Starbugs Weekly 星巴哥技術專欄

什麼都想學的雜食性軟體工程師 🇹🇼 (https://github.com/kylemocode) 合作與聯繫 📪 oldmo860617@gmail.com IG 技術自媒體:@kylemo.webdev.life