釘釘背後的故事(1)— Typescript 解決複雜資料結構
dingding.jpeg
我最近也是一直在做辦公系統,所以自然也少不了關注競爭對手,其中少不了阿里的釘釘,釘釘最初只有 4 個前端,繼續 nw.js 開發出他們的第一版桌面應用,可能是因為沒有 native 工程師吧。先來看看,nw.js 我接觸還是比較早,還是 2015 我就做了個 Demo。2016 年底實際做了一個專案,用這個封裝了web 的即時通訊打包成 Mac 和 window 版本桌面。
web 框架釘釘最初用得就是 angular,現在好像也使用 react。angular 我接觸的比較早,好時候正好閒著在家接觸 google 最新版的 angular2。因為之前已經接觸過一段 javascript 也用過 jquery 做過一些專案。不過 angular2 完全推翻了我對 javascript 的認知,以及對前端 web 應用開發過程認知。我通過學習 angular2 到現在回頭看收益匪淺。然後就開始學習了 react。
typescript 我也是接觸比較早的,angular2 需要用 typescript 開發,所以才邊學 angular 變學 typescript 的。typescript 是 c# 語言架構師設計的。當初還不瞭解為什麼要用 typescript,隨著不斷學習現在逐漸瞭解為什麼使用 typescript 來開發的原因。
png-city-map-this-program-is-a-bit-more-complicated-than-the-fly-through-cities-a-bit-under-300-lines-of-code-but-it-s-still-quite-simple-this-speaks-to-the-power-of-408.png
業務複雜,資料模型複雜業務邏輯複雜
釘釘有上百 rpc 介面,這些介面的地址、引數和返回值都需要各個平臺的前端進行了解,這些介面可能被不同語言進行呼叫,所以需要一種通用描述語言來講介面對應不同的語言。為了滿足不同業務在釘釘有幾十種的訊息推送。
avengers-age-of-ultron.jpeg
所以在前端有大量資料結構,javascript 作為動態語言,沒有資料型別的提示和檢查,對應釘釘這樣複雜大型的應用 javascript 可能顯得力不從心,但是 javascript 對於跨平臺又是最好選擇,所以釘釘引入了 typescript 對型別進行控制。
釘釘的解決方法使用 typescript 開發前端
通過定義介面描述 idl 來支援多平臺的應用開發
實時性要求高,對非同步要求也是很高的
前端採用 rxjs 進行響應式程式設計。Rx 在響應式程式設計中已經佔據不可動搖位置,我在 Android 程式設計中就用大 RxAndroid。不過看他是不是可以擋得住 Kotlin 的 corutine 的衝擊
iron-man-vs-thanos.jpg
下面就是一個示例,這是示例是由釘釘開發者大會上演示示例
export const getUserById(){ } user = { "uid":"1", "nickName":"zidea", "sex":"M", "bio":"", "isActive":true, "org":{ "mainOrgId":"", "orgList":[ { } ] } }
這麼複雜資料型別我們需要經驗和反覆才能記下來,而且有的資料型別是可選的例如這裡 org
欄位內容如果個人註冊那麼他使用者資訊中就沒有這個欄位。釘釘又不能把這些潛在的個人使用者擋在門外,所以這個 org 欄位是可選。
我們可以通過 typescript 以介面形式定資料型別和限制資料型別,這樣使用這個資料型別時候就會在編譯中檢查,從而避免一些不必要的麻煩。
export enum SEX{ Male = 'M', Female = 'F' } export interface IDepartment { departId:string, departName:string } export interface IOrgItem { orgId:string, orgName:string, departmentList:IDepartment[] } export interface IOrg{ mainOrgId:string; orgList:IOrgItem[] } export interface IUSer{ uid:number; nickName:string; sex:SEX; avatar:string; bio:string; isActive:boolean; org?:IOrg; } export const getUserById = (uid:number):Promise<IUSer> =>{ return fetch('/users?id=' + uid).then( (reponse) => reponse as IUSer ) }
而且大家注意到了 org? 表示 org 是可選。