PingCode 技術架構揭祕

PingCode研發中心發表於2021-12-29
本文由 Worktile 產品研發部負責人徐海峰分享

PingCode 是一款智慧化研發管理工具,由 Worktile 團隊打造,對標國外的 Jira,致力於研發管理自動化、資料化、智慧化,幫助企業提升研發效能,這款產品 2019 年 Q2 開始啟動,於 2019 年底正式釋出,剛釋出的時候叫 Worktile 研發版,2020年08月31日正式獨立為 PingCode ,那麼作為一款全新的研發工具,很多人會比較關心,這款 SaaS 產品底層的技術架構到底是什麼?今天由我這個在 Worktile 待了八年之久的新生代農民工給大家詳細揭祕一下。

image.png

技術選型

熟悉 Worktile 的朋友一定知道,我們作為一家活的有點久的創業公司,一直都是使用 Node.js 作為服務端底層技術,在2019年之前,我們已經使用 Node.js 開發了6年之久,伴隨著 Node.js 從 0.x 版本升級到了最新的 14.x(當然現在官方已經是 17.x 了),從到處 callback 的時代到大一統的asynca wait,也見證著 Node.js 基金會的分與合,Node.js 有很多優點,但同時也有很多缺點,特別是做中大型的企業級應用,沒有型別系統是一個永遠的痛,好在 2018 年我們引入了 TypeScript,這簡直就是給 Node.js 錦上添了一朵花,我們很慶幸在 2018 年就全面轉向了 TypeScript,那麼作為 2019 年的時間點開啟一個新的產品,我們毫無懸念繼續 ALL IN Node.js + TypeScript,同是加上 MongoDB ​和 Redis​,簡直是絕配。

前端的技術選型也是毫無懸念,我們是國內為數不多 Angular 堅定追捧者之一的公司,Angular 的哲學也非常符合我司的氣質,堅持長期主義,追求極致,什麼招人困難,培養人啊都不是問題,關於 PingCode 的前端想更多瞭解的可以檢視 Worktile 前端工程化之路

以下這張圖基本覆蓋了 PingCode 核心技術棧,之前一個架構叫 MEAN​ (代表 MongoDB + Express + Angular + Node.js),我覺得現在可以叫 MTAN(代表 MongoDB + TypeScript + Angular + Node.js)。

image.png

單體應用到微服務架構

Worktile 過去一直是一個單體應用,甚至過了很多年後才徹底的前後端分離,那麼 PingCode 矩陣式的產品結構形態決定了最合適的架構模式是微服務架構,每個子產品獨立倉儲、獨立開發和獨立部署,PingCode 整體的分層架構圖如下所示:
image.png

我司的倉儲和服務採用的是古希臘神話人物命名法,所以你會看到: chaoserostyphonatlas 等古希臘人物。

微服務最難的就是如何拆分服務,縱向來看,我們提供了一些基礎的服務,分別為:

  • Atlas: 檔案服務,封裝 AWS S3 相關的功能,包含: 上傳、下載、預覽等和檔案相關的功能,在私有部署環境下,無縫切換到 MinIO
  • Iris: 訊息服務,主要包含訊息通知,Feed(實時更新通知)相關功能,主要採用 SocketIORedis
  • Typhon: 帳號服務,包含註冊、登入、成員管理、授權、驗證相關通用功能

PingCode是矩陣式的子產品的結構模式,所以我們可以很輕鬆的按照子產品劃分微服務,當然有部分子產品之間也會有深度的耦合,所以產品形態決定了我們可以選擇一種無腦的橫向劃分式。

  • Agile: 敏捷開發子產品,提供 Scrum 和 Kanban 兩種敏捷開發的專案模板
  • Testhub: 測試管理子產品,提供測試用例,測試計劃等和測試相關的功能點
  • Wiki:知識庫子產品,企業級知識庫。
  • 等等...

我們在 2019 年選擇微服務架構有以下兩個原因:

  • 過去已經有了6年的技術積累,2018年也開始自研了符合我們的 TypeScript 底層框架 CHAOS
  • 同時隨著人員的壯大,需要考慮一種適配多團隊多組織的架構模式,微服務是必選之路

微服務架構帶來的挑戰

採用微服務架構後,對於開發和運維的要求都會變高,這其實不是一個簡單的架構升級,而是整個產研能力的升級,初創公司前期還是單體架構更合適,所以在選擇微服務架構之前先問一下自己:你準備好了嗎?

那麼對於開發和運維來說,微服務到底帶來了哪些挑戰呢?

開發的挑戰

微服務主要給我們開發帶來了三大挑戰,分別為:

image.png

那麼我們先看看抽象​,抽象是一個開發者非常重要的能力,這個能力決定了我們是否可以成長為一名合格架構師,是否可以脫離業務做基礎架構相關工作,過去因為單體應用過於簡單,對於抽象的能力要求不高,微服務後每個服務都是獨立倉儲、獨立開發,隨著複雜度的提升,以前在單體應用下一把梭的方式就不適用了。舉個例子:註冊成功後會建立團隊,團隊建立後每個子產品需要初始化業務資料(比如示例專案)

因為註冊的功能是在 Typhon 帳戶服務中,帳戶服務是一個被其他子產品都呼叫的基礎服務,在單體應用下通過EventBus​傳送一個通知,其他子模組訂閱進行初始化即可,甚至還可以直接呼叫其他模組的初始化邏輯程式碼,但是微服務下每個子服務是獨立的,讓帳戶服務呼叫每個子產品的 RPC 會導致依賴混亂,最終可能會引入訊息佇列,每個子產品加一個初始化的服務訂閱訊息佇列,那麼本身很簡單的功能就變得複雜,微服務架構對於開發人員的要求也就提高了,開發需要站在更高的角度思考如何抽象,服務間如何更好的通訊等等以前在單體應用下不用考慮的問題。

還有就是每個服務都是獨立的,就需要把通用的基礎類庫以及每個服務的 SDK 抽取出來,業務基礎類庫的抽取需要的就是抽象能力​,如果業務抽象都做不好那如何做基礎架構呢。我們基於 TypeScript 抽象了一個底層的框架CHAOS​(相當於 NestJS),同時抽象了底層的業務類庫EROS​和PC-CORE​,下圖是基礎類庫相關的依賴和模組示意圖。

image.png

第二個挑戰就是工程​,上面已經提到了我們會有多個基礎類庫以及服務的 SDK,這些私有類庫包如何管理和釋出等等一些列工程相關問題隨之而來。

對於私有類庫的釋出,我們一開始是使用 GitHub 的一個私有倉儲作為類庫的包,然後在業務中通過配置依賴的 GitHub 連結實現的,使用如下:

為此還單獨開發了個發包工具釋出到 git 倉儲中,感興趣的可以檢視 git-publish
"dependencies: {
  "@worktile/chaos": "git+ssh://git@github.com:worktile/chaos-built.git#1.0.0",
}

後來 GitHub 提供了倉儲的私有 Package,我們就直接使用 GitHub 的私有倉儲進行包管理,這樣就不用單獨建立一個built​倉儲來存放類構建後的產物了,之前還要給開發人員單獨分配這個 built 倉儲的許可權等等。採用 GitHub 私有包後只需要開發人員配置建立一個AccessToken​即可,這樣只要有訪問倉儲的對應許可權就可以安裝或者釋出倉儲私有包的許可權。

對於工程我們還遇到了release​,版本管理​,分支管理​等等一系列問題,這些都是在微服務架構模式下需要一一解決的問題,沒有標準的方案,適合團隊並滿足當前團隊需求的都是好的方案,工程問題也是我們團隊從團伙走向正規軍的必經之路,所以對於開發人員來說除了寫業務程式碼外可能還需要寫開發工具,開發規範文件等等。

第三個挑戰就是協作​,每個團隊負責自己的產品或者服務,服務會有依賴會有呼叫,那麼團隊之間如何協作呢?我們把所有基礎類庫和 SDK 統一採用 Kanban 的方式進行專案管理,大家都可以提交 Issue 和貢獻程式碼,對所有人都是透明的,每個基礎庫都會有個負責人,主要負責 Code Review 和類庫的規劃,當然協作還包含其他方方面面,我們採用敏捷的方式持續迭代,持續改進。

下圖就是基礎設施的 Kanban 專案示例:

image.png

以上是微服務架構對於我們造成的三大挑戰,或許對於抽象,工程之類的問題你可能想到的解決方案就是 Monorepo, 但是 Monorepo 也不是銀彈,前期可能是最好的解決方案,但是隨著倉儲程式碼的龐大和成員的增多,也會遇到一些列的問題,比如構建慢,測試時間長,衝突等等 Monorepo 的問題,所以我們在三年前做了一些三年後才看到收益的事情,這也是我們對 PingCode 產品的信心,但回過頭來看,這些挑戰絲毫不影響我們產品迭代的速度,和競品比起來絲毫不弱,甚至更強。

運維的挑戰

微服務導致服務增多、倉儲增多,依賴關係複雜,那麼對於運維來說,如何管理好這些服務就變得比較困難,我們也是在 2019 年引入 Jenkins 了實現 CI、CD。

有 CI、CD 但如果沒有測試基本是很難前行的,在 PingCode 的服務端中我們也會強制每個 API 都有測試,並且業務程式碼的測試覆蓋率必須在 80% 以上,基礎類庫必須要到 90% 以上,雖然測試覆蓋率指標不完全代表測試的全面性,但至少核心邏輯會被覆蓋,其次對於業務耦合的服務還會有整合測試、持久化測試以及效能測試等,其實測試不是微服務帶來的直接挑戰,單體應用下也需要測試。

除了 CI、CD 外,所有的服務統一採用 Kubernetes 進行容器化部署,服務的監控和預警、ELB、日誌等等和運維相關的挑戰都隨之而來,日誌儲存採用 ElasticSearch ,管理和檢視採用了 Kibana。

單服務架構

微服務會把所有的服務拆分,但是對於一個單服務來說,我們大多數子產品的架構都是類似的,除了通用的功能外,會按照模組進行劃分,每個模組大致分三層包含:Facade 層​ (其實就是 API 或者 Controller 層,這一層只包含路由定義和引數輸入和輸出,不包含業務邏輯),Service 層​(領域層,也是業務邏輯層),Repository 層​(包含對於資料庫單個實體的增刪改查操作,每個 Repository 會對於一個 Entity 實體)。

image.png

下面的程式碼段表示一個ProjectEntity​和ProjectRepository

@collectionName("agile_projects")
@indexes<ProjectEntity>([
    {
        team: Direction.ascending,
    }
])
export class ProjectEntity extends BusinessEntity {
    public name?: string;

    public name_pinyin?: string;

    public description?: string;

    public icon?: string;

    public color?: string;

    public identifier?: string;

    public members?: PrincipalMember[];

    @defaultValue(() => Visibility.private)
    public visibility?: Visibility;
}

@injectable()
export class ProjectRepository extends DefaultBusinessRepository<ProjectEntity> {
    constructor() {
        super(ProjectEntity);
    }
}

一個 Facade 示例程式碼如下:

@facade()
@middlewares(
 typhonClient.passport.authMiddleware(), 
 typhonClient.application.applicationExpireMiddleware(ApplicationType.agile)
)
export class ProjectFacade extends FacadeBase {
    @inject()
    private projectService: ProjectService;

    @post("/project", CreateProjectRequest)
    @middlewares(projectPermission.authCreateProjectMiddleware())
    public async createProject(request: CreateProjectRequest): Promise<Response<ProjectEntity, void>> {
        const project = await this.projectService.createProject(request);
        return new Response(project);
    }
}

關於我們底層框架 CHAOS,這裡再多介紹一下,CHOAS 提供了從應用、路由、容器再到資料訪問層的一個滿足我們業務需求的一整套企業級框架,那麼對於應用和路由,我們是基於 Koa.js 之上的二度封裝,CHAOS 遮蔽了 Koa 相關 API,這樣即使底層切換到 Express 或者更先進的 Web 框架對於使用者來說也是無感的,其次就是 Repository 也是對於 mongodb 模組的二度封裝,除此之外的一些核心功能是完全自研的,包括:

  • Container: 容器,幫助應用輕鬆實現依賴注入
  • Rpc: 微服務 RPC 通訊框架
  • Reminder: 定時任務的基礎類庫,負責提供任務的註冊、執行、錯誤處理等
  • 其他模組 ...

    微前端架構

    和微服務架構一樣,我們也是在 2019 年引入了微前端,因為沒有找到合適的微前端框架,我們自研了 ngx-planet - 一個我認為是最好用的 Angular 框架下的微前端解決方案。以下是整個微前端的架構示意圖:

image.png

Portal​ 是主應用,也叫基座應用,它負責左側的導航、全域性資料管理、通知、搜尋等全域性功能,最重要的是執行時通過 ngx-planet 實現子應用的註冊、載入和銷燬。

每個應用按照子產品劃分,構建提供manifest.json​靜態資原始檔列表,主應用會根據這個資源列表載入響應的指令碼和樣式。

那麼採用微前端架構後對於前端開發帶來的挑戰和微服務是類似的,我也從抽象​、工程​和協作​ 三方面闡述一下。

第一個就是抽象​, 我們在 2018 年已經打造了內部的元件庫,那麼在 2019 年微前端後也開始需要構建業務元件庫,做業務元件其實最能考驗開發者的抽象能力,元件庫的元件可以參考別的元件庫對應元件的設計和API,但是業務元件庫無從參考,我們經常會遇到某個小夥伴費勁心思提交了一個業務元件庫的 PR,結果一 Code Review 發現這個已經支援了或者有比這更好的解決方案,所以後來我們制定了一個規範,當你對於元件庫或者業務元件提交一個新特性的時候描述一下具體的場景和需求,然後思考一下你準備如何設計(包含 API 引數,命名,結構等等)然後在評論中@一下大家,這樣所有人都可以基於你的方案進行頭腦風暴,最終確定一個大家覺得比較完善的方案。

在 PingCode 中我們除了使用了第三方的 highcharts 作為報表庫以外,基本沒有引入過其他第三方庫,不是說我們的團隊有多麼的厲害,而是得益於 Angular 的 CDK , 通過 CDK 可以輕鬆開發元件庫和業務元件庫,CDK 讓我們這種前端 20 人不到的團隊把不可能​變成了可能​(當然組價庫的部分元件也會借鑑(chao) Angular MaterialNG-ZORRO 等優秀元件庫的做法)。有人說我們是 Angular 框架下國內輪子造的最多的公司,其中就包含:

這些都不是 KPI 專案,都是我們在解決 PingCode 和 Worktile 遇到的實際業務問題,無奈 Angular 社群沒有更好的方案,我們滿足了自己同時為了回饋社群開源出去了而已,我們也會持續完善和迭代已經開源的類庫,開源本身對於開發來說也是一個技術挑戰,做好開源專案其實是很不容易的。以上這些特定場景的解決方案都是抽象能力的表現,PingCode 前端開發者也在做這些開源專案的過程提升了抽象能力。

第二個挑戰是工程​,和微服務的一樣,過去沒有采用 Monorepo 直接上的微前端,具體原因就不一一說了,但是到目前為止,對於微前端帶來的核心工程問題,我們都解決了,發包採用 GitHub 的私有 Package,一開始 Angular 編譯類庫特別慢,對於頻繁發包的業務元件庫帶來了不小的挑戰,隨著 Angular 的升級,目前速度已經明顯上升,後續會通過 GitHub 的 Actions 做到自動發包,這樣也就減少了人工的重複操作。對於本地開發效率的提升也一直是我們比較關注的問題,我們本身有一個測試環境,所有的服務和前端應用都部署在這個內部可以訪問的測試環境中,前端的本地開發可以只啟動本地服務,然後通過測試環境的 Portal 訪問本地即可,然後本地的 Portal 也可以使用測試環境的子應用,我們的解決方案主要針對開發單獨做了簡易的代理配置介面,可以直接在微前端測攔截並代理前端資源,無需本地啟動應用和安裝代理工具。

image.png

對於協作​我想說一些和技術框架無關的事情,我本人一直有對程式碼的極致追求,我希望我所在的團隊不是僅僅的完成任務,而是應該想如何做到最好,技術能夠得到更好的提升,即使現在能力達不到但是一定要有一顆追求極致的心,所以我經常看到一些不合適的命名或者不好的設計都會提出來,同時因為團隊壯大後跨團隊之間溝通會更少,為了讓整個團隊的技術氛圍更加積極,我們會堅持雙週一次的技術分享、每日一學、內部技術週刊等等提高大家溝通能力和分享能力的事。

Angular 前端架構

採用微前端後,單個前端的架構就變的比較簡單,PingCode 每個子產品按照特性模組劃分業務,每個特性模組會包含: pages​stores​components​、pipe​services​ 等功能,除了特性模組外還會存在一些全域性的core​shared​,這裡只存放當前子產品的通過元件和服務,跨子產品的基本都已經在業務元件庫中。

PingCode 前端除了 Wiki 編輯器這種特定領域外,最複雜的就是資料管理(也可以叫狀態管理),因為產品的形態決定我們對於資料的實時性要求非常高,保證在不重新整理頁面的情況下資料可以實時更新資料, 資料更新會來自首次載入、編輯和WebSocket,Angular 狀態管理可以參考我幾年前寫的部落格 Angular 真的需要狀態管理麼?主要基於我們封裝的一個小型狀態管理類庫 @tethys/store 實現 Service + Observable​組合,多 Store 的結構圖為:

image.png

資料管理中最值得一說的就是引用資料的維護,PingCode 服務端 API 只會返回原子資料,前端通過 RxJS 進行資料的聚合,基於 @tethys/store 會非常方便,有時間我會單獨寫文章詳細介紹一下,資料格式如下圖所示:

image.png

以上是我對 PingCode 產品的技術架構揭祕,懶懶散散說了很多或許和技術架構無關的話題,希望給讀者帶來一些共鳴和思考,這也是一箇中小研發團隊一路走來的心路歷程,PingCode 技術架構未必是做的最好的架構,但是站在目前的角度看是比較適合的架構,我們會保持初心,繼續前進,為研發提供更好管理工具。


最後,推薦我們的智慧化研發管理工具 PingCode 給大家。

PingCode官網

關於PingCode

PingCode是由國內老牌SaaS廠商Worktile 打造的智慧化研發管理工具,圍繞企業研發管理需求推出了Agile(敏捷開發)、Testhub(測試管理)、Wiki(知識庫)、Plan(專案集)、Goals(目標管理)、Flow(自動化管理)、Access (目錄管理)七大子產品以及應用市場,實現了對專案、任務、需求、缺陷、迭代規劃、測試、目標管理等研發管理全流程的覆蓋以及程式碼託管工具、CI/CD流水線、自動化測試等眾多主流開發工具的打通。

自正式釋出以來,以酷狗音樂、商湯科技、電銀資訊、51社保、萬國資料、金鷹卡通、用友、國汽智控、智齒客服、易快報等知名企業為代表,已經有超過13個行業的眾多企業選擇PingCode落地研發管理。

相關文章