搭建前后端之橋

bridge.gif

隨著前后端分離,開發的門檻降低了,我們不再要求團隊中的每個開發都是全棧工程師,這樣更容易找到項目的合適人選。團隊也劃分成了前端和后端兩個團隊。前端負責消費 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(
"@@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 集成解決方案

?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。