藥不藥來當工程師一個不務正業的藥師,誤打誤撞來當了前端工程師。斜槓工程師的人生!

使用 Observer Pattern 重構複雜的資料上傳流程 - 以 PrimaryAPI 與 BackupService 整合為例

發佈於 最後更新於
8 min read

在後端開發中,我們常遇到需求不斷疊加,導致原本單純的類別(Class)變成了「上帝類別(God Class)」,職責混雜且難以測試。最近我在處理一個將資料上傳到 PrimaryAPI(主要資料平台)與 BackupService(備份服務)的功能時,就遇到了「堆疊式複雜度」的問題。本文將分享如何利用 Observer Pattern(觀察者模式)1Mediator Pattern(中介者模式)2 來優化架構,達成高內聚低耦合的設計。

問題背景

原本的 StreamDataDriver 類別最初只負責「將資料分批上傳到 PrimaryAPI」。隨著業務需求增加,加入了以下邏輯:

  1. BackupService 資料備份:上傳 PrimaryAPI 的同時,要判斷資料是否符合 BackupService 格式。
  2. 寫入暫存檔:符合 BackupService 格式的資料要寫入暫存檔。
  3. 上傳 BackupService:PrimaryAPI 上傳完成後,要把暫存檔上傳到 BackupService 平台。
  4. 強順序性要求:如果 PrimaryAPI 上傳失敗,BackupService 流程必須全部中止,避免資料不一致。

這導致 StreamDataDriver 裡充滿了與 PrimaryAPI 無關的程式碼(寫檔、判斷 ID 格式、呼叫其他上傳服務),違反了 單一職責原則 (Single Responsibility Principle, SRP)3

typescript
// 重構前的 God Class 示意
class StreamDataDriver {
  execute() {
    // 1. 讀取資料
    stream.on('data', row => {
       // 2. 處理 PrimaryAPI 上傳邏輯
       buffer.push(row);
       if (buffer.full) uploadToPrimary(buffer);

       // 3. 處理 BackupService 邏輯 (混雜在一起!)
       if (isBackupFormat(row)) {
          writeToTempFile(row);
       }
    });

    // 4. 等待所有流程結束,處理 BackupService 上傳
    await Promise.all([primaryPromise, backupPromise]);
  }
}

這樣的架構有幾個痛點:

  • 測試困難:要測試 PrimaryAPI 上傳邏輯,還得 Mock BackupService 的相關依賴。
  • 錯誤處理脆弱:並行執行下,很難精準控制「PrimaryAPI 失敗則 BackupService 不上傳」。
  • 擴充性差:如果明天要多傳一個 S3,程式碼會變得更亂。

解決方案:Observer + Mediator

我們決定將架構拆解,引入設計模式來分離職責。

1. Observer Pattern (觀察者模式)

首先,我們將 StreamDataDriver 降級為單純的 Stream Driver(資料流驅動者)與 PrimaryAPI Uploader。它不再關心「誰要用這些資料」,只負責廣播「我處理到一筆資料囉」。

我們定義了一個明確的介面:

typescript
// 定義監聽器的合約
export interface IDataListener {
    onSchemaDetermined(schemaInfo: SchemaInfo): void
    onRowProcessed(row: Record<string, any>, transformedItem: IDataItem): void
}

接著實作 BackupService 的專屬邏輯 BackupFileBuilder

typescript
// 專門負責 BackupService 邏輯的觀察者
export class BackupFileBuilder implements IDataListener {
    onRowProcessed(row: any, item: any) {
        // 判斷是否符合 BackupService 規格
        if (this.canWrite(row)) {
            this.writeBackupFile(row, item);
        }
    }
    // ... 其他寫檔邏輯
}

StreamDataDriver 中,只需要一行程式碼通知大家:

typescript
class StreamDataDriver {
    private listeners: IDataListener[] = [];

    addListener(listener: IDataListener) {
        this.listeners.push(listener);
    }

    private handleRow(row) {
        // ... PrimaryAPI 處理邏輯 ...
        
        // 通知所有聽眾,完全不管他們要做什麼
        this.listeners.forEach(l => l.onRowProcessed(row, item));
    }
}

2. Mediator Pattern (中介者模式)

雖然解耦了,但業務需求中的「強順序性(PrimaryAPI 成功 -> 才做 BackupService)」該由誰負責?這時候就需要一個 Mediator(中介者)。在我們的案例中,由最外層的 Action Class (TaskOrchestrator) 擔任。

typescript
class TaskOrchestrator {
    async execute() {
        // 1. 建立元件
        const uploader = new StreamDataDriver();
        const backupBuilder = new BackupFileBuilder();

        // 2. 組裝 (註冊觀察者)
        uploader.addListener(backupBuilder);

        try {
            // 3. 執行主流程 (PrimaryAPI 上傳)
            await uploader.execute(); 

            // 4. 控制流程:只有 PrimaryAPI 成功,才繼續 BackupService
            // 從 Builder 取得準備好的暫存檔路徑
            const result = await backupBuilder.finish();
            
            if (result.count > 0) {
                 await backupUploader.execute(result.filePath);
            }

        } catch (err) {
            // 5. 統一錯誤處理與資源清理
            backupBuilder.cleanup();
            throw err;
        }
    }
}

新舊架構比較

比較項目舊架構 (Coupled / Parallel)新架構 (Decoupled / Sequential)
責任歸屬混亂。Class 同時負責上傳、邏輯判斷、寫檔。單一職責。Uploader 管上傳;Builder 管資料準備;Action 管流程。
錯誤處理脆弱。PrimaryAPI 失敗時難以中斷 BackupService 流程。強健。由 Mediator 統一控制,失敗直接跳過後續步驟。
擴充性。新增功能需修改核心程式碼。。只需新增 Listener 即可。
測試難易度。依賴過多,Mock 困難。。各個元件可獨立單元測試。

技術反思:前端 vs 後端思維

身為前端開發者轉戰後端,可能會覺得這種設計「參數傳來傳去很麻煩」或是「為什麼不直接寫在裡面就好」。但這正是後端開發與前端元件化思維的異曲同工之妙:

  • Listener 其實就像 React 的 props.onEvent 或 Vue 的 $emit。讓元件只負責發出訊號,而不負責處理後續。
  • Dependency Injection (DI) 就像是把 loggerconfig 當作 props 傳進元件,讓元件保持純粹,方便測試。

總結

這次重構展示了即使在業務邏輯強相依(Sequential constraint)的情況下,依然可以透過 Observer Pattern 實現程式碼層級的解耦。我們保留了 StreamDataDriver 作為資料流驅動者,利用 listeners 陣列保留擴充性,最後透過外層的 Mediator 來確保業務邏輯的執行順序。

這樣的架構不僅讓目前的程式碼更乾淨,也為未來可能新增的「S3 備份」、「Log 監控」等功能預留了極佳的擴充空間。

延伸閱讀:

Reference

  1. Observer Pattern - Game Programming Patterns - Observer Pattern 是 Gang of Four 定義的 23 種設計模式之一,屬於行為型模式,定義物件間的一對多依賴關係。
  2. Mediator Pattern - Spring Framework Guru - Mediator Pattern 透過中介者物件封裝物件間的互動,促進鬆耦合設計。
  3. Single-responsibility principle - Wikipedia - Robert C. Martin 提出的 SOLID 原則之一,主張一個類別應該只有一個改變的理由。
Copyright 2020-2026 - AzureBlue ALL RIGHTS RESERVED.