Theme / v0.10.8

Codebase Memory

程式碼記憶與知識圖譜 MCP

實戰範例

實戰範例 003:新進開發者利用圖譜快速熟悉超大型 Monorepo

實戰演練新入職的工程師如何利用 Codebase Memory 知識圖譜,在幾分鐘內理清百萬行代碼庫的核心業務流程與模組依賴。

實戰背景

新入職的工程師在面對公司內部累積多年、代碼量高達上百萬行的超大型 Monorepo 專案時,通常會面臨巨大的心理壓力與認知迷茫。

痛點與問題描述: 新進工程師 James 被指派了第一個任務:「修復訂單處理系統中的重複扣款 Bug」。然而,James 對專案的目錄結構、各服務模組是如何交互的、以及訂單被創建後究竟是流向哪個 class 進行處理,完全一無所知。傳統的方法是去閱讀可能已經過時了半年的 Notion 架構文檔,或者拉著資深工程師講述半天。

本範例將展示 James 如何利用 Codebase Memory 知識圖譜,在不需要任何人指導的情況下,自主摸清整個訂單流水線的類別調用鏈,定位並理清問題。


規格定義

本次變更計畫的 ID 為 onboard-explore-order-flow。我們在 OpenSpec 增量規格中定義了以下驗收標準:

# 實戰範例 003:openspec/changes/onboard-explore-order-flow/specs/onboarding-doc/spec.md

## ADDED Requirements

### Requirement: Documented order creation execution path
James 必須(SHALL)生成一份精確的「訂單創建至扣款執行期呼叫鏈說明書」。
- 說明書必須(SHALL)包含從 `OrderController` 到底層 `PaymentGateway` 的完整多層級類別呼叫路徑與行號。

#### Scenario: Verify path accuracy
- **WHEN** 資深工程師審查該呼叫鏈說明書
- **THEN** 所有列出的類別名稱與調用關係必須(SHALL)與 codebase 最新實作 100% 脗合。

端到端 8 輪 AI 協作紀錄

以下是 James (You) 與 AI 助手在 Onboarding 調查時的完整對話歷史。

── 第 1 輪:高階專案目錄探索 ──

  • You:
    /opsx:explore "我剛入職,完全不熟悉這個 Monorepo 專案。請幫我呼叫 get_architecture 看看這個代碼庫的整體結構。"
  • AI:

    歡迎加入團隊!我將呼叫 codebase-memory 的 get_architecture 工具,為您呈現這個 Monorepo 的模組劃分。

    CM 呼叫: get_architecture()
    CM 回傳:

    {
      "project": "CommerceMonorepo",
      "modules": [
        { "name": "Commerce.API", "path": "/apps/api", "desc": "面向前端的 API 入口網關" },
        { "name": "Commerce.Core", "path": "/libs/core", "desc": "包含訂單與庫存的核心業務領域邏輯" },
        { "name": "Commerce.Billing", "path": "/libs/billing", "desc": "對接第三方支付與發票開立" },
        { "name": "Commerce.Jobs", "path": "/apps/jobs", "desc": "背景排程與訊息佇列消費端" }
      ]
    }

    分析
    這是一個清晰的 Monorepo 架構:

    1. apps/api:負責接收前端請求。
    2. libs/core:負責處理訂單流程的核心領域邏輯。
    3. libs/billing:負責真正的信用卡支付與扣款。

    接下來,我們將聚焦於您的任務:「訂單重複扣款」。我將在 apps/api 中尋找訂單相關的 API 控制器。


── 第 2 輪:尋找訂單控制器入口 ──

  • You:
    使用 search_graph 搜尋與 "Order" 相關的 API 控制器。
  • AI:

    好的,我將在 apps/api 專案中檢索名為 “Order” 的 Class 節點。

    CM 呼叫: search_graph(query: "OrderController", type: "class")
    CM 回傳:

    [
      {
        "qualifiedName": "Commerce.API.Controllers.OrderController",
        "type": "class",
        "filePath": "/apps/api/Controllers/OrderController.cs",
        "methodsCount": 5
      }
    ]

    找到了 OrderController!我將進一步查看它內部定義的建立訂單(Create)的方法。


── 第 3 輪:查看建立訂單 Action 定義 ──

  • You:
    使用 get_code_snippet 查看 OrderController 內的 CreateOrder 方法原始碼。
  • AI:

    我將呼叫 get_code_snippet 工具,獲取 CreateOrder 方法的實作內容。

    CM 呼叫: get_code_snippet(qualifiedName: "Commerce.API.Controllers.OrderController.CreateOrder")
    CM 回傳:

    • 檔案路徑: /apps/api/Controllers/OrderController.cs (第 25 行到第 34 行)
    • 原始碼:
      [HttpPost]
      public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDto dto)
      {
          var order = _orderService.Initialize(dto);
          var result = await _orderProcessor.ProcessAsync(order);
          return Ok(result);
      }

    分析
    訂單的建立是先呼叫 _orderService.Initialize 進行初始化,然後將 Order 物件傳送給 _orderProcessor.ProcessAsync 進行處理。

    為了找出它是如何流向 Billing(支付)模組的,我將使用 trace_pathProcessAsync 進行出站(outbound)路徑追蹤。


── 第 4 輪:追蹤訂單處理由此去向何方 ──

  • You:
    對 OrderProcessor.ProcessAsync 進行 outbound 追蹤,看它調用了哪些下游方法。
  • AI:

    好的,我將呼叫 trace_path 進行出站(outbound)依賴分析。

    CM 呼叫: trace_path(symbolName: "Commerce.Core.OrderProcessor.ProcessAsync", direction: "outbound")
    CM 回傳:

    [
      {
        "callee": "Commerce.Core.OrderProcessor.ProcessAsync",
        "calls": [
          {
            "callee": "Commerce.Core.OrderValidator.Validate",
            "location": "/libs/core/OrderProcessor.cs#L42"
          },
          {
            "callee": "Commerce.Billing.BillingService.ChargePayment",
            "location": "/libs/core/OrderProcessor.cs#L48"
          }
        ]
      }
    ]

    分析
    OrderProcessor.ProcessAsync 內部調用了兩個下游方法:

    1. OrderValidator.Validate:驗證訂單資料(第 42 行)。
    2. BillingService.ChargePayment:進行支付扣款(第 48 行,位於 Billing 模組)。

    這說明我們需要去檢視 libs/billing 中的 BillingService.ChargePayment 方法,因為那裡是扣款的核心。


── 第 5 輪:探索 BillingService.ChargePayment 原始碼 ──

  • You:
    使用 get_code_snippet 讀取 BillingService.ChargePayment 方法的原始碼。
  • AI:

    好的,我拉取 ChargePayment 方法的原始碼。

    CM 呼叫: get_code_snippet(qualifiedName: "Commerce.Billing.BillingService.ChargePayment")
    CM 回傳:

    • 檔案路徑: /libs/billing/BillingService.cs (第 50 行到第 65 行)
    • 原始碼:
      public async Task<PaymentResult> ChargePayment(Order order)
      {
          // 警告:此處缺少冪等性(Idempotency) Key 的校驗!
          // 如果同一個訂單被重複提交,這裡會直接向外部第三方網關發送多次扣款請求。
          var receipt = await _gateway.ExecuteCharge(order.Id, order.TotalAmount);
          return new PaymentResult { Success = receipt.Ok, TxId = receipt.TransactionId };
      }

    重大發現
    ChargePayment 方法中,直接將 order.Idorder.TotalAmount 發送給了第三方網關 _gateway.ExecuteCharge完全沒有任何防重提交或冪等鎖定(Idempotency Lock)!如果網路發生延遲,前端重複點擊,或者排程任務重試,就會立刻造成重複扣款。

    調查已非常清晰。我退出探索模式,並為您建立變更計畫。


── 第 6 輪:建立提案 ──

  • You:
    /opsx:propose "在 BillingService 中引入 Redis 羅塞塔鎖(Idempotency Lock)以解決訂單重複扣款 Bug"
  • AI:

    好的,提案 onboard-explore-order-flow 已在 OpenSpec 正式建立:

    • proposal.md: 說明重複扣款 Bug 的根因與使用 Redis 鎖定進行防重提交的方案。
    • specs/onboarding-doc/spec.md: 定義重複扣款防禦 Scenario。
    • tasks.md: 任務拆解如下:
      • 1.1 在 BillingService.cs 中,呼叫 IDistributedCacheorder.Id 作為 Redis 冪等鍵進行鎖定。
      • 1.2 測試模擬重複請求是否會被攔截。
      • 1.3 歸檔。

    請輸入 /opsx:apply 開始實作。


── 第 7 輪:實作 Redis 鎖 (Task 1.1) ──

  • You:
    /opsx:apply
  • AI:

    Implementing: onboard-explore-order-flow (schema: spec-driven)

    正在處理任務 1.1:實作防重鎖定。

    我已更新了 /libs/billing/BillingService.cs 的代碼,加入 Redis 冪等校驗:

    public async Task<PaymentResult> ChargePayment(Order order)
    {
        var lockKey = $"payment:lock:{order.Id}";
        // 嘗試獲取分散式鎖,過期時間設定為 5 分鐘
        var isLocked = await _cache.SetStringAsync(lockKey, "processing", new DistributedCacheEntryOptions {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        });
        
        if (isLocked == null) {
            return new PaymentResult { Success = false, ErrorMessage = "請勿重複提交訂單支付請求" };
        }
        
        var receipt = await _gateway.ExecuteCharge(order.Id, order.TotalAmount);
        return new PaymentResult { Success = receipt.Ok, TxId = receipt.TransactionId };
    }

    ✓ 任務 1.1 完成!已勾選 tasks.md


── 第 8 輪:驗證測試與歸檔 ──

  • You:
    執行測試,確認無誤後歸檔,並輸出我這次 Onboarding 整理出來的呼叫鏈說明書。
  • AI:

    Implementing: onboard-explore-order-flow

    任務 1.2 & 1.3:測試與歸檔。

    1. 測試:模擬在 1 秒內同時發送兩個相同 order.Id 的扣款請求。第一個請求正常執行,第二個請求在 Redis 鎖的作用下,直接被攔截並返回「請勿重複提交」錯誤,成功防堵重複扣款!
    2. 建置pnpm run build 通過。
    3. 歸檔:將 spec 同步並清理變更目錄。

    📖 James 的 Onboarding 訂單呼叫鏈說明書

    [前端請求] -> POST /apps/api/Controllers/OrderController.cs#CreateOrder (第 25 行)
    
    
            [業務處理] -> /libs/core/OrderProcessor.cs#ProcessAsync (第 42-48 行)
    
                    ├─ 呼叫: OrderValidator.Validate (驗證)
                    └─ 呼叫: BillingService.ChargePayment (第 48 行,扣款端點)
    
    
                       [Redis 冪等鎖定] -> Check key: "payment:lock:{order.Id}"
    
    
                       [第三方支付 SDK] -> _gateway.ExecuteCharge (實際發送扣款)

    Archive Complete

    變更計畫已成功歸檔!祝賀您完成了您的第一個 Bug 修復任務!