博客 / 詳情

返回

搭建前後端之橋

隨着前後端分離,開發的門檻降低了,我們不再要求團隊中的每個開發都是全棧工程師,這樣更容易找到項目的合適人選。團隊也劃分成了前端和後端兩個團隊。前端負責消費 API 並展示頁面,後端負責提供 API。這兩個團隊可以並行開發互不影響,大大提升了效率。雖然前後端分離解決了很多問題,但同時也帶來了新的困擾。

前後端分離帶來的困擾

溝通成本

前後端成為兩個獨立團隊之後,協作的問題便隨之而來。通過什麼來協作呢?契約。簡單來説,就是預先定義好精準的接口,比如接口的 URL,包含哪些參數、返回值,每個值的類型,是否為空等等。定義好之後,前後端就按照契約進行開發。但是在實際場景中,卻經常出現問題。

舉個真實的例子。有一次,後端在重構時修改了一個字段名,同時也修改了契約測試,但是卻忘了告訴前端。由於這個頁面的使用頻率不高,前端也工作在別的地方,因此那個頁面掛了許久都沒有人發現。這些隱藏 Bug 會給我們的應用帶來隱患,同時也會增加開發的負擔。

在實際開發過程中,保證人人都遵守契約是一個很困難的事情,因為人都可能會犯錯。

大量的模板代碼

即便團隊中所有人都能嚴格遵循契約,但集成 API 仍舊是個苦力活。我需要定義請求的 URL、Method、參數、參數類型以及返回值類型等等。於是項目中就充斥着下面這樣的模板代碼:

interface ICreateBookRequestData {
 bookId: string;
 category: string;
 date: string;
 createdBy: string;
}

interface IBook {
 id: string;
 author: string;
 name: string;
 price: string;
 publishDate: string;
 publishVendor?: string;
}

export const createBook = createRequestAction<ICreateBookRequestData, IBook>(
 "@@books/createBook",
 (data) => ({
   url: "/books/book",
   method: "PUT",
   data,
 }),
);

在多人協作時,為了減少衝突,我們通常會按照業務場景,將請求相關的代碼存儲到不同文件。比如 login.api.ts 存放登錄相關的請求代碼,account.api.ts 中存放賬户相關的請求代碼。但是這會造成另一個問題,就是類型的重複定義和定義不一致的問題。

在上面的代碼中,我們定義了請求響應數據的類型:IBook。對於後端來説,這個數據類型是可複用的,其他接口也可以使用它。但是對於前端來説,很難確定這個數據類型在哪些地方被使用了。因此在不同的文件中,可能會重複定義相同的類型。由於不同的人對同一個數據的類型的理解可能不一致,還會造成定義不一致的問題。比如在 A 文件的 IBook 中我們定義 publishVendor 是一個可選的屬性,而在 B 文件中我們可能再次定義 IBook 並把 publishVendor 定義為一個必需的屬性。如下所示:

interface IBook {
  id: string;
  author: string;
  name: string;
  price: string;
  publishDate: string; 
  publishVendor: string;
}

連接前後端

為了解決上面的問題,我們實現了一個自動化工具,將割裂的前端和後端重新連接起來。簡單來説,就是通過 Swagger JSON 自動生成調用 API 所需的代碼以及類型定義。

OpenAPI 規範(以前稱為 Swagger 規範)為 RESTful API 定義了一個與語言無關的標準接口,允許人和計算機發現和理解服務的功能,而無需訪問源代碼、文檔或開發者工具。Swagger 是一套圍繞 OpenAPI 規範構建的開源工具,可以幫助我們生成、描述、調用和可視化 RESTful 風格的服務。

有了自動化工具之後,只需要在終端中執行一行命令,就能立刻生成項目中所有 API 相關的代碼以及類型定義。這樣就不用再寫模板代碼了,節省了很多時間。同時,由於所有代碼都是通過 Swagger JSON 生成的,當接口發生變動時,我們不用再去查看文檔或者詢問後端修改了什麼,只需要通過命令就能知道哪些接口發生了變化並自動更新對應的前端代碼。因為所有代碼都是自動生成的,重複定義的問題也就不存在了。對於簡單業務場景來説,集成 API 就是一行代碼的事:

// getBooksUsingGET 方法由工具自動生成, useTempData 會發起 HTTP 請求並返回響應數據

const [books] = useTempData(getBooksUsingGET, { bookType }, [bookType]);

// 拿到 books 數據,渲染 UI

落地和優化

當我實現完這個工具的第一個可用版本,準備在項目中推行時,卻發現還有一些問題亟待解決:

Q: 如何保證生成代碼的一致性?

A: 每次運行命令都會從遠程服務器上去獲取 Swagger JSON,以保證數據來源的一致性。生成新的代碼之後覆蓋之前的文件即可。這樣就能保證每個人生成的代碼與當前服務器上的 API 是一一對應的。不過需要注意的是,生成的文件不能手動修改,否則修改最終會被覆蓋。

Q: 如何快速得知 API 的變化?

A: 跟 package-lock.json 一樣,我們會對生成的代碼進行排序,以減少生成文件的變化。重新運行命令之後,通過 git diff 就能準確得知 API 的變化。

Q: 如何進行多人協作?

A: 如果後端修改了一個字段名,可能會導致前端所有用到這個字段的地方都發生編譯錯誤。這時如果大家都去修改編譯問題,不僅可能產生衝突,還會造成時間的浪費。雖然這個問題在沒有自動化工具之前一樣存在,但仍然需要解決。好在這個問題發生的頻率不高,我們可以和項目成員約定:如果有人正好在做這個功能,那麼就由他來修改,否則就由指定的人去協調安排。通過自動化工具,可以更快地完成修改,從而減少阻塞別人工作的時間。

Q: 當後端進度落後於前端時,如何保證先有 Swagger 定義?

A: 由於前後端是兩個獨立的團隊,所以進度也常常不同。後端可能無法先於前端實現好 API,甚至無法和前端同時開始去做一個功能。而這套方案依賴於 Swagger 定義,必須先有 Swagger 定義才能生成代碼。如果前端要先於後端完成某個功能,必須先和後端商定好 API Schema 再進行開發。定義好 API Schema 之後,隨之更新 Swagger 定義即可(後端無需實現具體功能)。

最後

自動化工具為前後端搭建了一座橋樑,當後端發生變動時,前端也能及時得知並做出相應修改。再也不用擔心後端悄悄改接口了!到目前為止,自動化工具已經為我們項目生成了上萬行代碼。不僅提升了大家的效率,也減少了因為不遵循契約帶來的隱藏 Bug。前端終於不用寫大量模板代碼了,集成 API 也變成了一件很容易的事情。

如果對代碼實現感興趣,可以移步這裏:ts-codegen,或者看看我之前寫的這篇文章:基於 React 和 Redux 的 API 集成解決方案。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.