iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Software Development

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

[用NestJS闖蕩微服務!] DAY21 - Monorepo (下)

  • 分享至 

  • xImage
  •  

Nx Console

Nx Console Logo

圖片來源

Nx Console 是一個 Nx 官方推出的 VSCode Extension,可以幫助開發者減少輸入 Nx Command 的時間,透過圖形化介面,快速整合並產生不同 Stack 的程式碼,大幅提升開發效率,是一套非常強大且好用的工具。

補充:Extension 可以從這裡進行安裝。

下方是 Nx Console 提供的控制面板,可以看到共有三大區塊:

  • Projects:所有 Nx Workspace 下的專案,可以透過此處針對特定專案執行 Task,比如:build
  • Nx Cloud:Nx Cloud 服務相關的控制項。
  • Common Nx Commands:常用的 Nx Command,透過點選的方式來完成 Command 所執行的事情。

Nx Console Panel

建立 Application

透過面板的「Generate (UI)」來選擇程式碼產生器,上一篇選擇了 NestJS Stack,所需的套件和程式碼產生器已經安裝完成,所以會在跳出來的程式碼產生器選單中看到非常多與 NestJS 相關的選項:

Nx Create Application1

此處選擇 @nx/nest - application 來建立 Application,此時會跳出一個表單來填寫該 Application 相關的設置參數,其中 name 為必填項目,目的是要為該 Application 命名。下方是輸入名稱「ground」、directory 為「apps/ground」後的狀態,可以看到終端機內出現了建立的檔案名稱與路徑,但這只是預覽模式,此時還不會真的建立檔案:

Nx Create Application2

確認表單內容填妥後,點選右上角的「Generate」按鈕來建立檔案:

Nx Create Application3

移除專案

如果有專案要棄用需要移除,一樣可以透過 Nx Console 來執行。點選「Generate (UI)」選擇 @nx/workspace - remove

Nx Remove Project1

選擇後會跳出表單讓使用者選擇要刪除的專案。下方是選擇「ground-e2e」後的狀態,在終端機會顯示刪除的檔案名稱與路徑,但與建立 Application 的行為相同,僅作為預覽使用並沒有真的刪除檔案:

Nx Remove Project2

點選右上角的「Generate」按鈕來刪除檔案:

Nx Remove Project3

建立 Library

建立 Library 的方式與建立 Application 的方式大同小異,同樣是透過「Generate (UI)」這個選項來進行,不過選擇的程式碼產生器是 @nx/nest - library

Nx Create Library1

選擇後會跳出表單讓使用者填寫建置的相關參數,其中 name 為必填項目。這邊可以看到 buildablepublishable 的選項,buildable 的意思是這個 Library 是不是一個可以獨立建置的 Library,而 publishable 的意思則是能不能發佈成獨立套件的 Library,當然也可以都不勾選,那就把該 Library 當作 Application 會使用的模組即可。下方是輸入名稱「todo」、directory 為「libs/todo」、importPath 為「@playground/todo」後的狀態,可以看到終端機內出現了建立的檔案名稱與路徑,但同樣是預覽模式並沒有真的建立檔案:

Nx Create Library2

點選右上角的「Generate」按鈕就會建立:

Nx Create Library3

補充importPath 可以幫助我們決定該 Library 程式碼被匯入時使用的路徑,以上方範例來說,可以從 @playground/todo 匯入該 Library 的程式碼。

Nx 強大功能

Nx 除了上述產生程式碼的功能外,還有許多強大的功能可以幫助我們管理整個 Monorepo,甚至可以加速整體執行 Task 的時間,就讓我帶大家一探究竟吧!

Graph UI

前一篇有介紹到 Project Graph 與 Task Graph 兩個重要概念,但在 Monorepo 變得龐大時很難靠想像將這些 Dependency 關聯起來,對於開發者來說就比較不好掌握它們之間的關係,那麼有沒有方法把這些 Graph 用圖形的方式呈現出來呢?答案是可以的,Nx CLI 有提供相關指令可以架設 Nx Graph UI 讓開發者探索 Project Graph 與 Task Graph。打開終端機將路徑移動到 Nx Workspace 下,並輸入下方指令:

$ npx nx graph

Nx 會將 Graph UI 架設在 4211 Port 並自動開啟 UI:

Nx Graph UI

探索 Project Graph

在 UI 的左上角有一個選擇器,預設情況下會是「Projects」,意思就是現在探索的 Graph 即為 Project Graph。畫面左半邊的面板可以搜尋要探索的專案,在 Monorepo 規模較大的時候,查看特定專案的 Dependency 非常有用,原因是一次呈現所有專案的 Dependency 會相當雜亂,造成過多雜訊,不過如果規模不大的話,可以直接點選「Show all projects」 來看所有專案形成的 Project Graph:

Nx Project Graph UI

從上圖可以看到 todolist 依賴於 todo,這是因為我在 todolist 專案的 AppModule 使用了 todo 專案提供的 TodoModule,根據上一篇的描述,Nx 會自動推斷專案之間的 Dependency:

import { Module } from '@nestjs/common';
import { TodoModule } from '@playground/todo';
// ...

@Module({
  imports: [TodoModule],
  // ...
})
export class AppModule {}

從圖中還可以觀察到 todolisttodolist-e2e 的 Implicit Dependency,原因是在 todolist-e2eproject.json 有使用 implicitDependencies 屬性,並指定 todolist

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

探索 Task Graph

將左上角的選擇器改為「Tasks」來探索 Task Graph:

Nx Task Graph UI1

將「Target Name」選擇 serve 來觀察所有專案中 serve Task 的 Task Graph,並點選「Show all tasks」:

Nx Task Graph UI2

劃分 Boundaries

在 Nx 的架構下,我們會將程式碼以 Library 的形式拆分成各個模組,當規模越來越大的時候,Library 的數量可能會成長到上百個甚至上千個,如果這些 Library 可以自由互相引用的話,最終很可能會陷入難以控管的 循環依賴(Circular Dependency),因此 Nx 提供了劃分 Boundary 的機制,讓開發者可以根據 Library 的性質賦予不同的 標籤(Tag),再根據 Tag 的性質做進一步的依賴限制,達到降低 Circular Dependency 的效果。

以我們先前建立的 Nx Workspace 當作例子,假如希望 todo 只有 Todo 相關 Domain 的 Application 或 Library 可以使用,那可以替 todo 添加 Tag 來標示它的 Boundary。下方是 project.json 的範例,在 tags 屬性加上 scope:todo 的 Tag:

{
  "name": "todo",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "libs/todo/src",
  "projectType": "library",
  "tags": ["scope:todo"],
  "targets": {}
}

補充:範例中使用 scope:todo 作為 Tag 名稱,但真實情況應該根據團隊共識進行命名,這邊蠻推薦使用 scope: 前綴來標示它的 domain Boundary,另外還可以根據 Library 的性質添加 Tag,比如:type:util

由於 todolist 也屬於 Todo 相關 Domain,所以在 project.json 中加上 scope:todo Tag:

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

最後,需要編輯 Nx Workspace 根目錄中的 .eslintrc.json,在 rules 內有一個名為 @nx/enforce-module-boundaries 的 Rule,這就是定義 Boundary 限制的地方。在這條 Rule 中,有一個 depConstraints 的屬性,在初始設置中會有 sourceTag*onlyDependOnLibsWithTags["*"] 的設定,意思是不論 Tag 是什麼都可以引用所有 Library:

{
  // ...
  "overrides": [
    {
      // ...
      "rules": {
        "@nx/enforce-module-boundaries": [
          "error",
          {
            "enforceBuildableLibDependency": true,
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "*",
                "onlyDependOnLibsWithTags": ["*"]
              }
            ]
          }
        ]
      }
    },
    // ...
  ]
}

我們需要將預設的設定進行調整,把 sourceTag 改為 scope:todoonlyDependOnLibsWithTags 改為 ["scope:todo"]

{
  // ...
  "overrides": [
    {
      // ...
      "rules": {
        "@nx/enforce-module-boundaries": [
          "error",
          {
            "enforceBuildableLibDependency": true,
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "scope:todo",
                "onlyDependOnLibsWithTags": ["scope:todo"]
              }
            ]
          }
        ]
      }
    },
    // ...
  ]
}

此時如果在 ground 使用 todo 提供的程式碼會報錯:

Nx Boundary Result

快取回放機制

在架構龐大的情況下,如果每次執行 Task 都要重新執行所有 Dependency Task,那勢必會花費非常大量的時間,於是 Nx 想到了方法來解決這個問題,將 Task 執行完畢的結果進行快取,每次只重新計算有異動的部分,如此一來,就可以大幅加速執行 Task 的時間。

不過這套機制要能發揮效果的前提是需要將 Application 與 Library 依照 Nx 官方的建議進行合理的拆分,如果都集中在某一個 Application 或 Library 來實現功能,那對 Nx 而言就需要重新計算 Task,這也是為什麼上一篇提到如果不把非共用程式碼抽成 Library 會讓 Nx 效益打折的原因之一。

來進行一段快取回放的實驗,透過 Nx Console 執行 todolistbuild Task:

Nx Cache Replay1

Nx Cache Replay2

執行完畢後,終端機會顯示成功的訊息:

Nx Cache Replay3

建置完的程式碼會放在 dist 資料夾底下,此時我們將此資料夾刪除,來驗證再次執行時是否有正確執行快取回放:

Nx Cache Replay4

再次執行 build Task 可以看到終端機的訊息,是從快取回放的結果,在速度上也相當快:

Nx Cache Replay5

Affected 機制

在 PR 階段執行測試、建置等 Task 可以確保合併進去的程式碼不會造成 Workspace 內的專案發生問題,但隨著規模越來越大,要針對每個專案執行這些 Task 顯得不切實際,於是 Nx 想到了方法來解決這個問題,只要將執行 Task 的範圍縮小至 這個 PR 改動影響的範圍 即可,透過指定 Git 的 起始點(Base)結束點(Head) 讓 Nx 判斷這過程中改動了哪些專案,進而推論出受影響的專案,這套機制就叫 Affected

Nx Affected Concept

從上方概念圖可以發現,假如只異動了 todolist,那在 Affected 機制下僅需要針對 todolist 執行相關 Task,但如果異動的是 utility,那依賴於它的 todolistuser 都必須執行相關 Task。

用下方 Git Graph 來進行 Affected 實驗:

Nx Affected Git Graph

圖中最近的這三個 Commit 內容分別是:

  • e233e760:新增 todo Library。
  • d407467d:在 todolist Application 使用 todo Library 的程式碼。
  • 9dabfd31:新增 ground Application。

如果我們以 Commit e233e760 作為 base、以 Commit 9dabfd31 作為 head 去執行 build Task,那麼會建置 groundtodolist。下方是透過 Nx Console 執行的操作:

Nx Affected1

Nx Affected2

小結

回顧一下今天介紹的內容,在一開始介紹了 Nx Console 這套 Nx 官方推出的 VSCode Extension,透過圖形化介面減少 Nx Command 的輸入,讓開發者更方便管理專案。接著,詳細介紹了如何使用 Nx Console 建立應用程式、刪除專案以及建立 Library。接著,深入介紹 Nx 快取回放機制和 Affected 機制,這些功能幫助加速專案的建置和測試過程。除此之外,還探討了 Nx 的 Graph UI 和 Boundary 機制,提供視覺化的 Project Graph、Task Graph 與循環依賴的控制方法,使 Monorepo 的管理更高效且易於維護。

微服務管理策略的部分告一段落,這兩天充分學習到 Nx 這套強大的 Monorepo 管理工具, 後續的篇章會基於 Nx 來進行範例程式碼的實現。下一篇我們將進入 微服務的設計模式篇,敬請期待!


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

尚未有邦友留言

立即登入留言