🎯 什麼情境該想到我

當你「業務邏輯被資料庫/框架/第三方 SDK 綁死,換掉就要重寫」的時候,
或「想理解為什麼 Clean Architecture 要畫那些同心圓」的時候。

⚙️ 怎麼用

兩條原則,一體兩面

① 依賴反轉原則(DIP) —— 關於依賴方向

高層模組不應依賴低層模組,兩者都應依賴抽象。抽象不應依賴細節,細節應依賴抽象。

② 好萊塢原則 —— 關於控制流方向

“Don’t call us, we’ll call you.” 由高層決定何時呼叫低層,低層只實作被回呼的介面。

★ 「反轉」到底反轉了什麼

❌ 傳統:控制流與依賴方向「一致」
   OrderService ──呼叫且 import──▶ PostgresRepo
   ★ 業務邏輯依賴資料庫實作 → 換資料庫要改業務邏輯

✅ 反轉後:控制流不變,★ 依賴方向被反轉
   OrderService ──呼叫──▶ OrderRepository(介面,★ 定義在業務層)
                                 ▲
                                 │ 實作(依賴方向往上)
                          PostgresRepo(基礎設施層)

★ 關鍵:介面「屬於」高層,不屬於低層。
OrderRepository 這個介面和 OrderService 放在同一個套件——
它表達的是「業務邏輯需要什麼」,而不是「資料庫能提供什麼」。

這樣一來,基礎設施依賴業務邏輯,而不是反過來——依賴方向被「反轉」了。
這正是 Clean Architecture / 六角架構那些同心圓的全部內容。

★ Go 的長相

// ── 業務層(不 import 任何基礎設施)──
package order
 
// ★ 介面定義在這裡,而且只有業務邏輯真正需要的方法
type Repository interface {
    Save(ctx context.Context, o *Order) error
    FindByID(ctx context.Context, id string) (*Order, error)
}
 
type Service struct{ repo Repository }        // ★ 依賴抽象
 
func (s *Service) Place(ctx context.Context, req Request) (*Order, error) {
    o := newOrder(req)                         // 業務規則
    if err := o.Validate(); err != nil { return nil, err }
    return o, s.repo.Save(ctx, o)
}
// ── 基礎設施層(import 業務層)──
package postgres
 
type OrderRepo struct{ db *sql.DB }
func (r *OrderRepo) Save(ctx context.Context, o *order.Order) error { ... }
// ★ 不需要寫 "implements order.Repository" —— Go 的隱式滿足
// ── main:唯一知道所有具體型別的地方 ──
func main() {
    db, _ := sql.Open("postgres", dsn)
    svc := order.NewService(&postgres.OrderRepo{DB: db})   // ★ 組裝
}

★ order 套件完全不 import postgres——
這可以用 go list -deps 或架構測試(如 go-arch-lint)自動驗證,
讓「架構規則」從文件變成 CI 檢查。

★ 依賴反轉 vs 依賴注入(常被混淆)

依賴反轉(DIP)依賴注入(DI)
是什麼★ 原則:依賴方向該指向抽象★ 手法:從外部把相依傳進來
回答「該依賴什麼」「怎麼拿到它」
沒有對方行嗎可以只用 DI 不做 DIP(注入一個具體型別)可以只做 DIP 不用 DI(用 service locator,但不建議)

★ 一句話:DIP 決定「依賴誰」,DI 決定「怎麼拿到」。 兩者常一起出現,但不是同一件事。
注入一個具體的 *postgres.OrderRepo 是 DI 但不是 DIP。

★ 好萊塢原則:函式庫 vs 框架

函式庫(library):★ 你呼叫它     → 你控制流程
   result := json.Marshal(v)

框架(framework):★ 它呼叫你     → 它控制流程(控制反轉,IoC)
   http.HandleFunc("/x", myHandler)    // 你只是註冊,何時執行由框架決定

「控制反轉」的其他常見形態:

形態例子
回呼/事件處理器http.Handler、EventEmitter
範本方法模式父類控制骨架,子類填空
觀察者模式Subject 決定何時通知
middleware框架決定何時呼叫 next
生命週期 hookinit()、React 的 useEffect

🧪 我實際套用的紀錄

  • 2026-07-14:(待填)

⚠️ 注意 / 什麼時候不適用

  • ★ 介面放錯邊是最常見的錯誤。把 Repository 介面放在 postgres 套件裡 →
    業務邏輯還是要 import 基礎設施,完全沒有反轉。 介面必須屬於使用它的那一方
    (見 工具-針對介面編程)。
  • ★ 介面不要照著資料庫設計。FindByUserIDAndStatusOrderByCreatedAt 這種方法名
    代表你在用 ORM 的語言描述業務需求——那是依賴沒有真正反轉的訊號。
  • ★ 不是每一層都要反轉。小專案、CRUD、一次性腳本直接用具體型別更快更清楚。
    DIP 的成本(多一個介面、多一層間接、追程式要跳)在簡單系統裡不划算。
  • ★ 抽象洩漏:介面上出現 sql.Tx、*gorm.DB、context.Context 裡塞資料庫交易 →
    抽象已經洩漏了。事務邊界的處理是這裡最難的部分(常見解法是 Unit of Work,見 工具-領域邏輯組織模式)。
  • DI 框架不是必需品。Go 社群多半在 main() 裡手動組裝就好;
    相依真的很多時才考慮 wire(編譯期產生程式碼,比反射式的 DI 容器好除錯)。
  • 好萊塢原則讓控制流難追。「誰呼叫了我」在框架裡不明顯——這是控制反轉的固有代價。

🔗 相關工具