天天看點

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

淘系技術部前端技術專家 張挺

淘寶在 2017 年之前就開始探索 TypeScript 的落地方式,随着時間的推移已經将新的子產品和架構全部遷移到 TypeScript 體系,在 2019 年, TypeScript 應用已經遍地開花,提前完成了非常不錯的布局。

GMTC 大會上,淘系技術部前端技術專家 張挺,分享了淘寶的 Midway 部分想法和實踐,本次分享主要介紹淘寶最近開源的 Midway 架構在新的場景、新的體系下如何和現有的 Egg 體系保持良好的相容性,同時又能在 TypeScript 的使用中有着獨特的體驗,通過針對不同場景的情況,我們引入相同的解決方案,為未來打下了夯實的基礎。

▐ 跨平台方案

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

整個分享的内容基調是基于目前的 Node.js 開發背景來的。

BFF 應用

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

對于阿裡集團來說,大部分的應用都是 BFF 應用,這些應用表現為長尾應用,維護人不斷的疊代流失,可能最後也找不到人維護,而這 70% 的應用被逐漸放棄,但是依舊有一些同學還在不斷的使用,對于一個 BU 來說,是很不利的。而 Serverless 的出現,可能是給這些應用一個機會,一個能擺脫維護,擺脫人員投入的機會,但是具體如何,還要看今年的發展,畢竟 Serverless 和傳統的應用開發有很大的不同。

全棧應用

剩下的就是全棧應用了,去除那些不重要的 BFF 應用,我們還對其做了核心和非核心的劃分,在這些應用中,不乏有承載千萬流量的大應用。而這些應用都由前端同學來維護,整個研發,測試,釋出的流程都必須非常謹慎。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

TypeScript應用

在集團應用中,TS 的使用沒有想象的那麼多,據我們采集的資料,也就隻占 5% 左右,基本都是 midway(TS 版本,内部還有 JS版本),而今年,我們希望新應用全量使用 TS。

在這種場景下,對于業務同學來說,也有很多苦惱,比如業務複雜,接口沒有定義,以前使用 schema,但是沒有很大的推廣開來,這些都需要自己去拿時間來填,反而并不友好。在集團内釋出 RPC 服務,也需要寫 jsdoc,用于比對 java 的類型,在 js 場景場景下,這些都是不得已的選擇。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

這個時候引入 TypeScript,來幫助我們解決這些品質,習慣,方法上的問題,就拿 midway 團隊來說,自從使用了 TypeScript,品質提升的非常明顯,平常需要測試很久的代碼,幾乎不會出現低級的問題,反而暴露出的大多都是邏輯問題。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

面向接口程式設計,也成為了大家的習慣,每次多人協作,也隻需要先定義 interface,再根據 interface 的約定去各自實作,效率也非常高。同時,我們将 RPC 生成的工具替換成了 TypeScript 解析,将 Java 類型和 TS 類型做了一些映射,也避免了再使用 JsDoc 描述的問題。

講了這麼多 TS 的使用,下面來解決具體的問題。

Midway 是淘寶去年開源,面向未來的全棧架構,所謂面向未來,我們希望在未來能夠不斷的疊代,而主代碼不需要做過多的變更,同時在技術疊代的浪潮中,我們的架構也能不斷的适用于新的場景。

Egg.js 解決了 Web 開發的場景,在不斷的演進中,淘寶産生了全棧場景,Egg.js 已經無法滿足目前的需求,一方面集團内需要編寫上層架構,另一方面我們希望有原生的 TS 體驗。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在現有的 Controller - Service 架構中,除了 Controller 是明确意義的,Service 承載了非常多的職能,把 API,服務,邏輯其實都放在了一起,如果想單獨拆分目錄,也不是特别友善。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在 Egg.js 的更新之後,加入 ts-helper 填補了 TS 方面的空缺,不過目前由于目錄約定,編譯前後的檔案是在一起的,略微有一些不舒服。

在體驗方面,不同之處,例如:Egg.js 是支援過程式寫法的,在類的寫法中,由于請求鍊路的關系,比如手動繼承一個基類,這在業務中,如果想要自行再繼承就無法滿足。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

同時,核心的 Loader 機制把屬性方法都挂載到了 app 上,顯得不是特别優雅。這促使我們做了第一代的設計。

▐ 第一代的設計

淘寶使用 IoC 非常早,我們有許多熟悉 Java 的同學非常喜歡 spring,一開始沿用了 XML 的寫法來配置,但是轉到前端來寫,XML 就變成了桎梏,負累重重。

在參考了輕量的 inversify 之後,我們覺得提供兩個簡單的裝飾器是一個最好的辦法。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

@injectable() 提供了暴露類可以被 IoC 注入的能力。而 @inject() 提供了相應的注入屬性的能力。同時 inversify 有個 bindding 的包,提供了自動綁定的能力,我們也沿用了裡面的裝飾器,這才有了自研的 injection 包,裡面包含了 @provide 和 @inject 兩個裝飾器方法。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

經過了 IoC 之後,我們把所有的對象統一放在了 IoC 容器中管理,不再需要關心執行個體的來源,也不在需要自行去建立執行個體(new)。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在使用了 IoC 之後,我們發現所有的寫法都可以變成傳統的 class 形式,封裝繼承多态三大特性都可以完美的使用,不再受到其他限制。

抛開裝飾器,代碼就是原生的 class,不管是測試也好,開發也好,都友善的使用 TS 的類型描述,最直覺,也最簡單。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在集團内,大約有 10 來個中間件,為了讓使用者有 TS 定義,将原有的代碼進行了增強,這都是一次性的工作量,可以造福後人。

▐ 第二代設計

和 Egg.js 解耦

之前我們解決了 Service 的問題, 通過 IoC,我們可以随便建立目錄,調用 API,以及測試。但是在 Web 層,和 egg 耦合的地方還是沿用了 egg 的寫法,雖然有變通的辦法,但是需要在體驗上更進一步。

Midway 基于 Egg.js 進行疊代開發,要實作 egg 的插件化能力,是直接在 package.json 中依賴了 egg 包,同時由于 IoC 的産出,又希望能夠讓各種開發體驗保持一緻,全部使用 class 的寫法,這也促使我們和 egg 進行了解耦,使用裝飾器完成各種 web 層的能力。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)
  • 通過 @config 能力,和 app.config 解耦
  • 通過 @plugin,和 app.xxx 插件解耦
  • 通過 @inject() ctx 和請求鍊路解耦

此外還有 @logger 等裝飾器,提供額外的能力。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

和目錄結構解耦

在做完 IoC 自掃描能力之後,已經完全不需要考慮目錄結構了,如果還需要 egg 的插件能力,目錄還需要保留,如果不需要插件,就可以自由定義目錄,掃描能力會完成一切。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

通過自掃描能力,在極端情況下,可以将原有應用按功能劃分,也可以随意拆分成子子產品,甚至是 npm 包,而每一個子產品都可以随時獨立開發部署,也可以随時聚合成一個大應用。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

和自己解耦

在做完這些之後,我們覺得未來可能要面向不同的場景去了,這個時候如果一味的隻考慮一個架構入口,可能會被受限制,雖然我們将 Midway 的代碼分開抽象,但是核心還是在一起的,各個裝飾器的實作和定義都是在同一個包,這樣擴充插件或者新增裝飾器都需要改動到 Midway 本身。是以需要一次重構把和 Midway 依賴的東西都解耦掉。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

首先将裝飾器的定義都單獨分離出來,形成一個新包,這個包中有所有的裝飾器,以及他們最基本的函數(裝飾器定義)。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

抽離完定義之後,我們就可以将實作部分單獨成為新的包,這個時候才有 midway-web 等包的産生。

▐ 面向未來的設計

所謂面向未來,就要為未來考慮和設計,而幾年 Serverless 的大熱,也為 Node.js 開發者提供了新的機會,而作為集團唯一的 Node.js 架構團隊,自然當仁不讓的投入到了研究的浪潮中。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在考慮跨場景之時,正逢将裝飾器定義與實作分離的時候,我們順便也将通用的能力沉澱了下來,這樣未來不同的場景都可以共享這些能力。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

我們沉澱出了 midway-core 這個包,包含以下幾種能力。

第一種是自掃描注入 IoC 的能力,injection 提供通用綁定能力。

第二種是适配 midway 的請求作用域能力,不同的場景必然有請求,這個能力也屬于通用的能力之一。

第三是統一的裝飾器擴充能力,比如 @config 的擴充。

在 Midway-core 之外,我們也實作了一個 Decorator Manager 用于裝飾器的編碼和管理。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

以新建立一個裝飾器為例,比如 @autoload,某些類加了這個裝飾器,希望能在應用啟動時自動被執行個體化,執行 init 方法。在新的分離體系下,隻需要定義一個裝飾器(标準函數),将這個裝飾器的 key 通過 saveModule 方法進行儲存。

在子產品、插件等任意你希望實作這個裝飾器能力的地方,通過 listModule 就可以把用到這個裝飾器的類通通拿出來,接下去你隻要循環,然後執行個體化這個類,執行方法就行了。通過這樣的機制,我們把所有的裝飾器都進行了改造,實作了整個模式。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在這次改造之後,我們覺得多場景的方案基本可行,在 koa/express 上做了試點,通過編碼之後,基本上在 200 行左右就完成整個功能,同時達到整個代碼使用相同的裝飾器,并且邏輯基本不變。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

在這之後,又逐漸實作了其他的一些場景,同時對這些場景完成了一些工具鍊,配套等等。這些工具鍊有些是複用的,例如:Midway-bin,有些又是特定場景使用。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

Serverless 場景,也是我們的整個場景之一。Serverless 整體分為很多部分,這裡我們隻将将函數代碼部分。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

FaaS 是 Serverless 的實作之一,我們本來覺得在 FaaS 體系中代碼比較簡單,無需架構的幫助,但是在實際調研中,我們發現使用者的代碼還是有不少,同時檔案和複雜度還是有一些,是以也同樣需要架構的幫助。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

但是這個架構必須是非常精簡,非常小,隻需要完成基本的功能即可。由于我們多場景的設計,代碼的整體結構也和原來的基本保持一緻,最終我們實作的 midway-faas,大概在 120 行代碼,保留最基本的 IoC 能力。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

可以看到代碼寫法基本一緻,隻有裝飾器的差別。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

可以看到除了包名不同,入口的裝飾器略有差異外,整個寫法上依舊保持基本的 class 形态。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

除了寫法一緻之外,對于 FaaS 本身,我們還有一些訴求。

1、代碼一緻,能力一緻,這個通過 IoC,基本能夠做到了

2、我們希望一套代碼,能夠部署到多個雲環境

對于不同的平台來說,調用方式(回調,async),函數參數(event),以及描述檔案(spec)都是不同的,要把他們統一其實比較困難,但是經過内部驗證,我們依舊可以在一些地方進行統一。

我們針對不同平台的入口檔案進行包裹,一般來說,入口檔案是通過描述檔案 (spec) 的 handler 字段指定的,例如: index.handler ,指的就是 index.js 檔案的 handler 方法。但是由于 TypeScript 目錄結構的關系,所有的檔案都在 src/dist 目錄下,正好在根目錄空缺出了這個檔案,使得我們可以進行一些黑科技操作。

舉個例子,針對阿裡雲 FC,我們可以做一些 callback 轉 async 的包裹操作,使得使用者端調用的代碼格式保持統一,這部分代碼目前還未開源,這部分方案我們希望盡快,比如在下半年能夠提供給社群。通過這樣的黑科技操作,我們能夠在多個平台之間使得使用者代碼保持一緻性。

淘寶 TypeScript 多場景架構和方案實踐(GMTC 2019 大會分享)

當然 Midway-faas 我們還在演進中,除了保持小體積,基本完整的功能外也想提供更多的能力。我們通過不斷的改進,從解決實際問題出發,和各個子產品解耦,實作不同場景相同的代碼編寫方式,這些都是不斷的思考,不斷的沉澱,未來可能還會有很多挑戰和變化,我們希望也能夠一如既往的疊代下去。

▐ 總結

  • 我們通過 IOC,解決了困擾我們多年的全棧開發問題。
  • 我們通過裝飾器,解決了和某個架構依賴過深的問題。
  • 我們通過多場景,拓寬了 Node.js 的開發職能,也創造了前端的新場景。

本文是淘寶從 Midway5 到 Midway6 開發的實踐積累,過程中的點點滴滴都在字裡行間流出,不知道大家有沒有Get到其中的每次變化的原因,從中能夠了解為什麼要做這些事情,做了之後能夠帶來什麼影響,最後希望本文能夠幫助各位思考和改進。有更多疑問,歡迎在文章留言區回複交流。