工程師與貓
ESC
Content
    ↑↓ navigate open esc close
    Published on

    將公司專案轉換成 Monorepo 遇到的那些事 - 第九篇 如何管理 Monorepo 中的多個團隊合作

    Authors
    • avatar
      Name
      Alex Yu
    Team Collaboration

    如何管理 Monorepo 中的多個團隊合作

    在現代軟體開發中,隨著專案規模的擴大,多個團隊合作開發已經成為常態。當我們採用 monorepo 架構時,雖然帶來了許多優勢,但也同時為多團隊合作帶來新的挑戰。本文將深入探討在 monorepo 環境中如何有效管理多個團隊的合作流程,包含團隊間的程式碼共享和衝突解決方案。

    多團隊合作的挑戰

    在傳統的多儲存庫架構中,團隊分工相對清晰,每個團隊負責自己的儲存庫,合作模式相對簡單。但在 monorepo 中,所有團隊共享同一個程式碼庫,這會帶來一系列挑戰:

    1. 程式碼所有權與責任模糊

    當所有團隊的程式碼都在同一個儲存庫中時,很容易出現「誰是這段程式碼的擁有者」的問題。有時候一個功能或模組可能涉及多個團隊的領域,導致責任邊界模糊不清。這種情況下,當出現問題時,可能沒有特定團隊願意承擔修復責任,或者多個團隊可能同時修改相同的程式碼區域。

    2. 程式碼審查壓力大

    在大型 monorepo 中,每天可能有數十甚至上百個 Pull Request 需要審查。這不僅增加了團隊負擔,也可能導致程式碼審查品質下降。特別是當一個 PR 涉及多個團隊的專業領域時,找到合適的審查者變得更加困難。

    3. 分支管理與合併衝突

    多個團隊同時開發時,分支管理變得複雜,合併衝突的頻率也會增加,尤其是當多個團隊修改共享元件或核心程式碼時。這些衝突不僅耗費開發時間去解決,還可能導致整合問題和更嚴重的技術債。

    4. 建置和測試的效能問題

    當專案規模越來越大,完整的建置和測試時間會越來越長,這會影響團隊的開發效率。如果沒有良好的增量建置和測試策略,每次小改動都可能觸發整個系統的重新建置和測試,大大延長了回饋循環。

    團隊合作的最佳實踐

    基於多年在大型 monorepo 專案中的經驗,以下是一些經過實戰檢驗的最佳實踐:

    程式碼審查流程

    CODEOWNERS 機制

    PR CODEOWNERS

    在 GitHub 和 GitLab 等平台上,我們可以利用 CODEOWNERS 檔案清晰地定義程式碼的所有權。這是一個非常實用的功能,它不僅可以明確每段程式碼的擁有權,還能自動將 PR 分配給相應的擁有者,有效減少了手動指派審查者的時間。

    以下是一個實際的 CODEOWNERS 範例:

    # 全域預設擁有者
    *                   @org/core-team
     
    # 前端團隊負責的區域
    /apps/web/          @org/frontend-team
    /libs/ui/           @org/ui-team
    /libs/shared/       @org/shared-team
     
    # 後端團隊負責的區域
    /apps/api/          @org/backend-team
    /libs/data-access/  @org/data-team
     
    # 特殊檔案的擁有者
    package.json        @org/devops-team
    nx.json             @org/devops-team
    .github/            @org/infra-team

    這種方式可以:

    • 明確每段程式碼的擁有權
    • 自動通知相關團隊進行程式碼審查
    • 防止未經授權的修改
    • 提高程式碼品質,確保專業團隊審查相應區域
    • 減少手動分配審查者的管理負擔

    程式碼所有權與團隊結構

    團隊結構與 Nx Workspace 結構對應

    在我們的實務中,我們根據領域驅動設計 (DDD) 的概念將團隊和程式碼結構對齊。每個團隊負責一個或多個業務領域,例如:

    • 產品團隊:負責使用者面對的產品功能
    • 平台團隊:負責底層平台和共享基礎設施
    • 體驗團隊:負責 UI 元件和使用者體驗
    • 資料團隊:負責資料處理和分析

    我們的 Nx workspace 結構也相應地組織:

    my-workspace/
    ├── apps/
       ├── product-a/
       ├── product-b/
       └── admin/
    ├── libs/
       ├── product/
       ├── feature-a/
       └── feature-b/
       ├── platform/
       ├── core/
       └── utils/
       ├── experience/
       ├── ui/
       └── design-system/
       └── data/
           ├── models/
           └── services/

    這種結構使每個團隊都能在自己的領域內自主工作,同時透過共享 libs 實現程式碼重用。

    複雜 PR 的審查者選擇

    除了利用 CODEOWNERS 自動分配外,對於複雜的 PR,我們也會依循以下原則手動選擇額外的審查者:

    • 選擇熟悉相關程式碼的人員
    • 確保至少有一位具有該領域專業知識的成員
    • 考慮團隊成員的工作負載,避免同一人同時審查太多 PR

    程式碼審查分級制度

    我們實施了一個基於變更範圍和風險的分級審查制度:

    1. 小型變更(如文案修改、簡單 bug 修復):只需一位團隊成員審查
    2. 中型變更(如新功能、重構):需要相關模組的擁有者審查
    3. 高風險變更(如核心架構修改、跨多個團隊的變更):需要資深工程師和架構師審查,並可能需要進行系統設計評審

    這種分級制度確保了程式碼審查的效率和品質,讓重要的變更得到足夠的重視,同時避免小改動被拖延。

    定時程式碼審查會議

    每個團隊每週安排固定的程式碼審查會議,處理積壓的或複雜的 PR。這不僅加速了審查流程,還提供了知識分享的機會。會議遵循以下形式:

    • 開發者簡要介紹 PR 的目的和實現方式(5分鐘)
    • 團隊集體審查關鍵部分(10-15分鐘)
    • 解決任何疑問和討論潛在問題(10分鐘)
    • 制定清晰的後續步驟

    這種做法特別適合處理跨團隊的複雜 PR,能加快審查過程並避免來回溝通的延遲。

    分支策略與衝突管理

    基於 trunk-based 開發的適應性策略

    在我們的經驗中,嚴格的 trunk-based 開發模式在大型 monorepo 中效果最好,但需要一些調整:

    • 主分支保護:main 分支必須受到保護,所有程式碼必須透過 PR 合併
    • 短生命週期的功能分支:鼓勵小型、頻繁的提交,功能分支存活時間不超過 2-3 天
    • 頻繁同步主分支:每天至少一次從主分支同步到功能分支,減少合併衝突

    以下是我們實際使用的 git workflow:

    # 建立功能分支
    git checkout -b feature/user-profile
     
    # 進行開發,頻繁提交
    git add .
    git commit -m "feat: add user profile avatar component"
     
    # 每天同步主分支
    git fetch origin main
    git rebase origin/main
     
    # 推送到遠端
    git push origin feature/user-profile -f  # 因為 rebase 後需要強制推送
     
    # 建立 PR 後,可能需要再次同步主分支
    git checkout main
    git pull
    git checkout feature/user-profile
    git rebase main
    git push origin feature/user-profile -f

    這種方法可以:

    • 減少長期分支造成的合併困難
    • 鼓勵更小、更容易審查的修改
    • 保持主分支的穩定性
    • 降低整合問題的風險

    建置和測試優化

    使用 Nx 的分散式快取

    Nx 提供了強大的分散式快取功能,可以大幅提升建置和測試速度。在我們的專案中,使用 Nx Cloud 後,CI 時間減少了 70%。

    // nx.json
    {
      "tasksRunnerOptions": {
        "default": {
          "runner": "@nrwl/nx-cloud",
          "options": {
            "cacheableOperations": ["build", "test", "lint", "e2e"],
            "accessToken": "your-nx-cloud-token"
          }
        }
      }
    }

    如我們在第八篇文章中討論的,也可以使用 S3 作為快取後端來替代 Nx Cloud:

    // nx.json
    {
      "tasksRunnerOptions": {
        "default": {
          "runner": "@pellegrims/nx-remotecache-s3",
          "options": {
            "cacheableOperations": ["build", "lint", "test", "e2e"],
            "s3Options": {
              "region": "ap-northeast-1",
              "bucketName": "your-nx-cache-bucket"
            }
          }
        }
      }
    }

    這種快取機制對多團隊合作特別有價值:

    • 團隊不會重複執行已經由其他團隊完成的任務
    • 從一個 PR 獲得的快取結果可以用於其他 PR
    • 大大減少了等待時間,提高開發效率

    影響分析與智能測試

    Nx 的影響分析功能可以幫助我們只運行受影響的測試,這在大型團隊中尤為重要:

    # 只運行受當前變更影響的測試
    nx affected:test
     
    # 只建置受影響的應用程式
    nx affected:build
     
    # 自動化 CI 腳本
    echo "Running tests for affected projects"
    nx affected:test --configuration=ci

    在我們的實務中,這使得平均 CI 運行時間從 45 分鐘減少到 8 分鐘,極大提高了開發效率。具體是透過:

    • 只測試受影響的程式碼,而不是整個程式碼庫
    • 利用 Nx 的相依圖來精確確定哪些測試需要運行
    • 平行執行測試以進一步節省時間

    合作工具與流程

    共享元件庫管理

    在多團隊合作中,共享元件的管理尤為重要。我們建立了一套完整的流程:

    共享元件開發流程

    1. 提案階段:任何新的共享元件首先需要提交提案,包含用例、API 設計和技術細節
    2. 審查階段:由架構委員會審查提案,確保與現有系統的一致性
    3. 開發階段:由提案團隊或平台團隊開發,必須包含完整檔案和測試
    4. 試用階段:在有限範圍內試用,收集回饋
    5. 正式發布:完成試用後,正式發布並通知所有團隊

    這個流程確保了我們的共享元件高品質且適合實際需求,同時避免了重複開發。

    使用 Storybook 作為元件展示平台

    我們使用 Storybook 作為所有共享元件的展示平台,這不僅方便了開發者了解可用元件,也成為了溝通的橋樑:

    // libs/ui/buttons/src/lib/primary-button.stories.tsx
    import { Meta, Story } from '@storybook/react';
    import { PrimaryButton, PrimaryButtonProps } from './primary-button';
     
    export default {
      title: 'UI/Buttons/PrimaryButton',
      component: PrimaryButton,
      argTypes: {
        onClick: { action: 'clicked' },
        variant: {
          control: {
            type: 'select',
            options: ['default', 'success', 'warning', 'danger'],
          },
        },
      },
    } as Meta;
     
    const Template: Story<PrimaryButtonProps> = (args) => <PrimaryButton {...args} />;
     
    export const Default = Template.bind({});
    Default.args = {
      children: '按鈕',
      variant: 'default',
    };
     
    export const Success = Template.bind({});
    Success.args = {
      children: '成功按鈕',
      variant: 'success',
    };

    這個 Storybook 成為了團隊設計參考的權威來源。它提供了:

    • 每個元件的即時展示
    • 互動式檔案,顯示 API 和用法
    • 不同變體和狀態的視覺呈現
    • 程式碼範例和最佳實務

    衝突解決機制

    即使有了良好的流程和工具,衝突仍然不可避免。以下是我們處理衝突的方式:

    架構審查委員會

    對於跨團隊的架構決策或衝突,我們建立了一個架構審查委員會,由各團隊的資深工程師組成。委員會負責:

    • 評估重大架構變更
    • 仲裁團隊間的技術分歧
    • 制定共享標準和最佳實務

    委員會定期開會討論提案和問題,並且對於重大決策有最終裁決權。這種集中治理模式確保了整個系統的架構一致性和技術方向的統一。

    定期同步會議

    每個團隊選派代表參加每週的跨團隊同步會議,會議內容包括:

    • 分享各團隊當前的工作重點
    • 討論可能影響其他團隊的變更
    • 協調需要多團隊參與的專案
    • 分享學習心得和最佳實務

    這些會議通常控制在 30 分鐘以內,重點是交流資訊而不是詳細討論。會議紀錄會共享給所有團隊成員,以保持透明度。

    實際案例分析

    以下是一個在 monorepo 環境中多團隊合作的實際案例:

    案例:跨團隊共享元件庫升級

    背景

    我們需要對共享 UI 元件庫進行一次重大升級,涉及到設計變更和 API 重構,影響到所有使用這些元件的產品團隊。

    挑戰

    1. 不同產品團隊有不同的發布週期和優先級
    2. 某些元件在不同產品中有不同的客製化需求
    3. 需要確保向後相容或提供平滑遷移路徑

    解決方案

    1. 合作開發新版本

      • UI 團隊負責核心元件的開發
      • 各產品團隊派代表參與設計審查和API定義
      • 使用 feature flags 控制新功能的啟用
    2. 漸進式發布策略

      • 首先發布 alpha 版本供早期使用者試用
      • 基於回饋改進,發布 beta 版本
      • 在非關鍵產品中先行應用
      • 所有產品完成遷移後,發布穩定版
    3. 詳細檔案和遷移指南

      • 為每個變更撰寫詳細說明
      • 提供遷移步驟和範例程式碼
      • 針對常見問題製作 FAQ

    結果

    升級過程持續了 6 個月,但最終所有團隊都順利完成了遷移。關鍵成功因素:

    • 各團隊的早期參與
    • 自動化工具減少了手動遷移工作
    • 漸進式發布降低了風險

    結論與建議

    經過多年在大型 monorepo 中管理多團隊合作的經驗,我總結出以下關鍵建議:

    1. 明確的治理結構

    建立明確的程式碼所有權和決策流程,避免責任模糊導致的延遲和衝突。CODEOWNERS 檔案和 RFC 流程是非常實用的工具。在我們的實務中,清晰的治理結構幫助我們減少了 30% 的程式碼審查時間,並且讓團隊間的合作更加順暢。

    2. 重視自動化

    在多團隊合作中,自動化不僅是提高效率的工具,更是確保一致性和減少錯誤的關鍵。投資於自動化測試、CI/CD、程式碼生成和重構工具,會帶來長期收益。我們發現,自動化流程幫助我們將發布頻率提高了 2 倍,同時保持了高品質標準。

    3. 溝通與透明

    頻繁而有效的溝通是多團隊合作的基石。確保團隊之間的資訊流通暢,包括定期同步會議、技術分享、檔案更新等。透明的決策過程和清晰的溝通管道能有效減少誤解和衝突。

    4. 漸進式變更

    避免大規模、一次性的變更,而是採用漸進式的方法,分階段實施,這樣可以降低風險並及早發現問題。在我們的經驗中,將大型變更分解為多個小步驟,成功率提高了 80%。

    5. 建立共享知識庫

    在 monorepo 中,知識共享尤為重要。建立完善的檔案系統、設計決策紀錄、技術部落格等,幫助團隊成員了解整個系統。我們使用了 Storybook 和內部技術分享會等多種方式,讓知識在團隊間流通。


    在多團隊合作的 monorepo 中,管理複雜度是一場持久戰。透過清晰的結構、良好的流程、有效的工具和持續的改進,我們可以將這種複雜度轉化為團隊的競爭優勢,讓多個團隊能夠在同一個程式碼庫中和諧高效地合作。

    正如我們在系列前幾篇文章中所討論的,良好的 monorepo 結構設計(第三篇)、有效的循環依賴管理(第五篇)、合理的版本控制(第六篇)、優化的測試流程(第七篇)和高效的效能解決方案(第八篇)都是多團隊合作的基礎。本文作為系列的第九篇,聚焦於人與組織的合作層面,與前面的技術討論相輔相成,共同構成了 monorepo 成功的關鍵要素。

    希望這篇文章能對你在 monorepo 中管理多團隊合作有所幫助。如果有任何問題或經驗想要分享,歡迎在評論區留言交流。