iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
Software Development

用 NestJS 闖蕩微服務!系列 第 20

[用NestJS闖蕩微服務!] DAY20 - Monorepo (上)

  • 分享至 

  • xImage
  •  

微服務的程式碼管理策略

在 Monolithic 架構下,經常會將程式碼放在同一個 Git Repository,包含:前端、後端等。不過在微服務架構下,因為不再將所有功能放在一起,而是由各個團隊負責實作、維運自己負責的服務,於是在程式碼管理上有兩大策略:PolyRepoMonorepo

補充:實務上採用 PolyRepo 還是 Monorepo 要看組織文化與團隊共識,並沒有絕對的好壞。

PolyRepo

PolyRepo 是一種以 多個 Git Repository 來拆分、管理程式碼的策略。在微服務架構下,因為每個服務都有自己的生命週期、技術與團隊,將程式碼放在不同的 Git Repository 是目前主流的做法,也是最直覺拆分程式碼的策略。

下方是採用 PolyRepo 的優點:

  • 自主性強:每個服務都可以獨立進行版本控制、CI/CD 配置和部署。
  • 技術多樣:不同服務之間可以視情況選用最適合的技術。
  • 職責明確:每個團隊只需要專注於自己負責的 Git Repository,可以避免跨團隊的干擾。

當然,PolyRepo 也是有缺點的:

  • 版本管理具挑戰性:服務之間有可能會因為 API 異動需要同步更新多個 Git Repository,增加管理複雜度。
  • 依賴管理困難:當微服務有一定規模時,要完整掌握服務之間的依賴會變得困難,文件的管理也會變得複雜。
  • 共享程式碼較為複雜:跨服務要共享程式碼,可能會另外拆分 Git Repository 來進行管理,當拆分的越多,在版本管理、文件管理上都會變得複雜。

Monorepo

Monorepo 將程式碼集中於單一 Git Repository 進行管理,與 Monolithic 架構不同的地方在於,Monorepo 是以拆分服務的方式進行管理,並非混合成同一個服務。近年來,這類型的管理方式也逐漸流行起來。

下方是採用 Monorepo 的優點:

  • 一致性:所有服務共享相同的開發工具、依賴管理與版本控制策略,可以保證版本一致。
  • 協作效率高:不同服務之間的變更可以同步管理,減少跨服務的兼容問題。
  • 共享知識與資源:在共享程式碼與文件方面更加透明且方便,大幅提升知識透明度與減少重複性。

但 Monorepo 也是有缺點的:

  • 管理的挑戰:當服務規模不斷擴展,Git Repository 的管理也會變得越來越複雜,會需要使用強大的 Monorepo 工具來輔助管理。
  • 複雜的 CI/CD:需要花費較多功夫在處理 CI/CD,避免資源的浪費。

補充:本篇文章將會聚焦在 Monorepo 的管理策略上。

Nx

Nx Logo

圖片來源

Nx 是一套強大的 Monorepo 管理工具,支援多項熱門框架,包含:React、Angular、NestJS 等。組織可以透過 Nx 來定義程式碼的 邊界(Boundary),確保程式碼的依賴關係與品質,甚至還可以利用 Nx 的程式碼產生器來輔助產生基礎程式碼,進而標準化開發流程。作為一套專業且強大的 Monorepo 工具,其包含但不限於上述的功能,就讓我帶大家一探究竟 Nx 的強大吧!

Application 與 Library

應用程式(Application) 是 Nx Workspace 中最重要的角色,以微服務的角度來說,就會是各個服務,比如:用 NestJS 撰寫的訂單服務、用 Express 撰寫的金流服務等。由此可知,一個 Nx Workspace 可以有數十個甚至數百個 Application。

函式庫(Library) 與我們一般認知的函式庫不同,在 Nx 的世界裡,Library 是 模組化(Modularized) 程式碼的手段,並不完全是把程式碼共享化的方式。

一般來說,會鼓勵開發者們盡量將程式以 Library 的方式進行拆分,變成各個獨立的模組,並視情況進行 組合(Compose),最終於 Application 將 Library 組合成一個完整應用。如果不太理解上述的概念,我們可以將 Application 看作是一個應用程式的 容器(Container),而 Library 可以看作是應用程式的某個功能,會在 Container 將 Library 組合起來變成一個完整的應用。

注意:部分初學者會抗拒將非共用的程式碼切成 Library,但這樣其實 會讓 Nx 的效益打折,所以在使用 Nx 時,需要轉換思維,不要把 Library 只視為共享程式碼。至於為什麼會讓 Nx 的效益打折呢?這部分後面的篇章會做更詳細的說明。

Nx Application And Library Concept

Project Graph

在 Monorepo 的架構下,Application、Library 之間一定存在 依賴(Dependency) 關係,那麼要如何運用 Nx 知道它們之間的關聯呢?Nx 在建立 Application、Library 的時候,會有一個名為 project.json 的檔案,Nx 會偵測整個 Workspace 中有哪些 project.json 以識別該 Application、Library 的存在,再進一步根據程式碼、安裝的依賴項目來推定 Dependency,最終形成 Project Graph。下方是 project.json 的範例,可以看到 projectTypeapplication,表示這個專案是 Application,它的程式碼位置透過 sourceRoot 進行指向,並運用 name 替該專案命名:

{
  "name": "todolist",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/todolist/src",
  "projectType": "application",
  "targets": {},
}

補充:Nx 為了避免頻繁偵測 Project Graph 導致效能問題,它會將 Project Graph 進行快取,並只針對變動的檔案進行分析。

Nx Project Graph Concept

雖然在大多數情況下 Nx 可以自動推斷出 Dependency,但有些 Dependency 可能會因為沒有程式碼上的相依關係導致推斷不出來,這時候可以透過手動的方式添加 Dependency。下方是 project.json 的範例,透過 implicitDependencies 標示 user,表示該專案與 user隱性依賴(Implicit Dependency) 關係:

{
  "name": "todolist",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/todolist/src",
  "projectType": "application",
  "implicitDependencies": ["user"],
  "targets": {}
}

Task

Dependency 的關係會影響專案之間處理 任務(Task) 的先後順序,舉例來說,TodoList Application 依賴於 Todo 功能、Todo 功能依賴於 Utility Library,那麼要建置 TodoList 就必須先建置它的依賴項目 Todo,確保有元件可以使用,同樣的道理,在建置 Todo 之前必須先建置 Utility,如下圖所示:

Nx Task Concept

在規模不大、依賴關係單純的情況下,要手動處理或是撰寫腳本來排序並執行特定 Task 並不是很麻煩的事情,但隨著規模擴展,這個問題將會變得越來越難處理,更不用說如果要 並行處理(Parallel) Task 同時滿足執行順序的情況。於是 Nx 設計了 Task Graph 來解決這個問題,基於 Project Graph 讓 Nx 推斷出 Task 的執行順序,並且找出可以 Parallel 的 Task。

那麼 Task 該如何設定呢?只需要在 project.json 中設定 target 區塊即可。下方是 project.json 的範例,可以看到 targets 設定了 serve 這個 Task,其中,使用了 @nx/js:node 這個 executor 作為 執行器(Executor)

{
  "name": "todolist",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/todolist/src",
  "projectType": "application",
  "targets": {
    "serve": {
      "executor": "@nx/js:node",
      // ...
    }
  }
}

補充executor 是用來決定這個 Task 該如何執行的重要角色,通常會與專案使用的 Stack 有關,以上方的範例來說,@nx/js:node 就是 Node.js 常用的 Executor。

雖然說 Task Graph 是基於 Project Graph 來推斷的,但一般情況下 Task Graph 並 不會 與 Project Graph 相同,可以用上方 TodoList 的例子來說明,TodoList 一定需要等待 Todo 建置完成,而 Todo 一定需要等待 Utility 建置完成;但 TodoList、Todo 與 Utility 執行單元測試可以不用有依賴關係,此時就可以 Parallel 處理,如下圖所示:

Nx Task Graph No Dependency Concept

那麼 Task 之間的 Dependency 該如何建立呢?每個 Task 都可以透過 dependsOn 來決定在執行這個 Task 之前,有哪些 Dependency 應該優先處理。下方是 project.json 的範例,在 serve 這個 Task 執行之前,應該先執行自己專案的 build Task:

{
  "name": "todolist",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/todolist/src",
  "projectType": "application",
  "targets": {
    "serve": {
      "executor": "@nx/js:node",
      "dependsOn": ["build"],
      // ...
    },
    "build": {
      // ...
    }
  }
}

另一種情況是需要先建置 Dependency 的專案,在寫法上需要做一些調整,將 dependsOn 內的 build 改成 ^build,這樣 Nx 就知道要 先執行 Dependency 的 build Task

{
  "name": "todolist",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "apps/todolist/src",
  "projectType": "application",
  "targets": {
    "serve": {
      "executor": "@nx/js:node",
      "dependsOn": ["^build", "build"],
      // ...
    }
  }
}

初探 Nx Workspace

有了 Nx 的基本知識後,就來實際建置 Nx Workspace。透過下方指令即可進行建置流程:

$ npx create-nx-workspace

此時 CLI 會詢問 Workspace 的名稱:

Nx create workspace question1

輸入完名稱之後,可以選擇預設建立的 APP 要用什麼 Stack,並安裝該 Stack 所需的相關套件、程式碼產生器。以我們的情境來說會選擇「NestJS」:

Nx create workspace question2

Nx 可以建置單一專案的 Standalone 或是 Integrated Monorepo,這邊我們會選擇「Integrated Monorepo」:

Nx create workspace question3

接著會詢問預設建立的 Application 名稱為何,這邊可以自由發揮:

Nx create workspace question4

Nx 很貼心地詢問是否要建立 Dockerfile,由於只是簡單的 Demo,這裡選擇了「No」:

Nx create workspace question5

CI 的部分也會提供多樣選擇,由於只是簡單的 Demo,這裡選擇「Do it later」:

Nx create workspace question6

最後,會詢問要不要使用 雲端快取(Remote Caching),這塊是 Nx 獨有的功能,會將執行過的事情快取起來放到雲端,這樣所有專案的參與者都可以共享相同的 Cache。由於只是簡單的 Demo,這裡選擇「No」:

Nx create workspace question7

補充:建立 Workspace 的方式會因為版本不同而有些微變動。

以上步驟完成後,就會建置出一個 Workspace 囉,可以看到預設建立的 Application 在 apps 資料夾底下:

Nx create workspace question8

小結

回顧一下今天所介紹的內容,在一開始講解了 PolyRepo 與 Monorepo 的優劣,再進一步聚焦在 Monorepo 的管理工具 - Nx。該工具解決了 Monorepo 管理、效能上的挑戰,基於 Project Graph 的機制,讓執行 Task 可以參照其路徑找出相關 Dependency,進而繪製出 Task Graph,讓龐大且複雜的 Task 流程變得更加容易管理與執行。最後,透過 create-nx-workspace 將 Nx Workspace 建置起來。

下一篇將會進一步介紹 Nx 相關的功能,敬請期待!


上一篇
[用NestJS闖蕩微服務!] DAY19 - OpenTelemetry (下)
下一篇
[用NestJS闖蕩微服務!] DAY21 - Monorepo (下)
系列文
用 NestJS 闖蕩微服務!21
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言