在前面兩篇的文章說明「絞殺榕模式」與「抽象分支模式」都可以讓我們在正式環境中使用相同功能的新舊實作,並且可以在兩者間切換。但是,我們如何驗證抽取出來的功能是「正確的」?
平常,如果為一個既有系統進行重寫,在正式上線前會做什麼活動?我想可能很多人猜到了,就是「平行測試」。當我們將既有系統中的某個功能抽取出來、重寫為獨立的微服務時,雖然我們可能已經在開發與測試環境中進行了單元測試與整合測試,但這仍無法保證新服務在實際運行時的資料一致性與行為一致性。尤其是在高可靠性要求的系統中,一次錯誤就可能造成巨大的損失。
因此,在正式上線前,我們需要進行一段時間的平行測試:
在我剛入行的年代,所謂的平行測試大多是人工操作:
這樣的方式雖然直觀,但效率極低,也容易出錯。若要維持營運效能,往往需要兩倍以上的人力投入,更別提維護資料一致性與日誌記錄的難度。
為了解決這個議題,我們希望「平行測試」這個作業可以更自動化一些,於是乎我們希望設計一個架構能協助我們進行「平行測試」的作業,與前兩天不同的是先前我們是找方法來切換新舊系統的呼叫,現在我們期望新舊系統可以收到相同的「請求」訊息。
為了解決效率與準確性的問題,我們傾向於設計自動化平行測試架構。其核心概念是:
設計一套中介機制,讓新舊系統同時收到相同的請求,主要回應仍由既有系統提供,但會額外記錄新系統的執行結果,並進行比對分析。
具體實作流程如下:
在原本的 HTTP Proxy(如 Nginx、API Gateway)背後加入一個 平行測試處理模組(Parallel Test Handler),具備以下職責:
每次請求除了觸發新舊系統處理外,還需將以下資訊記錄:
上述流程看似完整,但實際應用時還會遇到一個極大的挑戰:下游依賴(Downstream Dependencies)。
舉例來說,如果你所抽取出來的功能還需要呼叫其他內部服務(例如:發送通知、更新帳戶餘額、查詢客戶狀態),那麼在平行測試時就可能產生兩次呼叫,導致:
因此,針對這類依賴模組,我們需要進一步設計:「抽象分支模式(Branch by Abstraction)」。
在依賴服務之上建立一層抽象,例如 NotificationService 介面,在平行測試階段注入兩種實作:
這樣就可以避免副作用,同時仍能記錄下新系統是否正確觸發該依賴邏輯,並納入比對項目中。
一個完整的自動化平行測試架構,應包含下列元件:
可搭配下列技術:
由於建置與維護平行測試的成本極高,因此建議應用在以下情境:
反之,如果是低風險、無副作用的查詢型功能(如使用者個人頁面資料),可考慮以 Shadow Traffic 或 A/B 測試替代。
精力應該花在刀口上,才能製造出槓桿
平行測試的設計與實作是一門深奧的工程藝術,它既是一種保險措施,也是一種架構成熟度的體現。它可以讓我們在正式導入新架構前,用數據與結果證明「我們做對了」。但同時也必須正視它的投入成本與維運複雜度,並根據實際業務特性做出平衡取捨。
或許,在某些小型系統遷移中,「讓使用者手動敲兩次」反而是最省力又有效的平行測試方法。但在大型、關鍵的系統轉換中,唯有投入更多的設計與自動化機制,才能讓我們的微服務轉型真正站穩腳步。