Reuse Patterns#

在單體架構中,程式碼重用只是匯入或注入共用類別檔案的問題。然而在分散式架構中,如何處理共用功能變得更加複雜。本章介紹四種管理程式碼重用的技術:Code ReplicationShared LibraryShared Service,以及 Sidecar / Service Mesh,並分析各自的優缺點與適用場景。

Figure 8.1: Code reuse is a hard part of distributed architecture

Code Replication#

  • 概念:將共用程式碼複製到每個服務的原始碼倉庫中,完全避免程式碼共享
  • 在微服務早期盛行,源自 bounded context 概念與「share nothing architecture」的理念
  • 適用場景:程式碼是靜態的、不太會變動的簡單工具類別(如 annotations、attributes、簡單的 utility)
  • 從單體遷移至分散式架構時,可用於複製通用靜態工具類別(如 Utility.cs),讓各服務可依需求客製化

Figure 8.2: With replication, shared functionality is copied into each service

若共用程式碼中發現 bug 或需要修改,必須逐一更新所有複製該程式碼的服務,非常困難且耗時。

Trade-Offs:

優勢劣勢
保留 bounded context難以套用程式碼變更
無程式碼共享依賴服務間程式碼不一致
無版本控制能力

Shared Library#

  • 概念:將共用程式碼打包為外部製品(如 JAR、DLL),在編譯時期綁定到各服務
  • 看似簡單直覺,但涉及共用函式庫粒度(granularity)與版本控制(versioning)兩大挑戰

Figure 8.3: With the shared library technique, common code is consolidated and shared

Dependency Management and Change Control#

共用函式庫的粒度涉及兩股對立力量:

  • 粗粒度(Coarse-grained):單一大型函式庫,依賴管理簡單,但任何變更都會影響所有服務,強制不必要的重新測試與部署
  • 細粒度(Fine-grained):多個小型函式庫(如 security、formatters、calculators),變更影響範圍小,但依賴關係矩陣變得極度複雜,形成所謂的 distributed monolith

Figure 8.4: Changes to coarse-grained shared libraries impact multiple services

Figure 8.5: Changes to fine-grained shared libraries impact fewer services

建議避免大型粗粒度的共用函式庫,盡量使用較小的、按功能分區的函式庫,優先考慮 change control 而非 dependency management。

Versioning Strategies#

  • 務必使用版本控制(always use versioning),提供向後相容性與敏捷性
  • 避免使用 LATEST 版本,以防止熱部署時引入不相容的變更
  • 版本溝通困難:在多團隊的大型分散式架構中,版本變更資訊難以有效傳遞
  • 版本棄用策略(Deprecation strategies):
    • Custom:每個函式庫依其變更頻率設定不同的版本數量(如穩定的 Security.jar 只保留 2-3 版,頻繁變更的 Calculators.jar 保留 10 版)
    • Global:所有函式庫統一最大向後相容版本數,管理簡單但可能導致大量不必要的 churn

Trade-Offs:

優勢劣勢
可進行版本變更依賴管理困難
編譯時期綁定,降低執行時期錯誤異質代碼庫中程式碼重複
共用程式碼變更的敏捷性好版本棄用困難
版本溝通困難

When To Use#

  • 適用於同質環境(homogeneous),共用程式碼變更頻率低到中等
  • 由於在編譯時期綁定,不影響 performance、scalability、fault tolerance 等營運特性

Shared Service#

  • 概念:將共用功能放在獨立部署的服務中,透過組合(composition)而非繼承(inheritance)實現重用
  • 變更共用功能無需重新部署使用該功能的服務

Figure 8.6: With the shared service technique, common functionality is made available

Change Risk#

  • 優勢:變更隔離在共用服務中,其他服務無需重新部署
  • 風險:共用服務的變更是執行時期(runtime)變更,不當修改可能導致所有依賴服務同時故障
  • API endpoint 版本控制比 shared library 的版本控制更複雜且主觀,特別是涉及多種通訊協議(REST、gRPC、messaging)時

Figure 8.7: Shared functionality changes are isolated to only the shared service

Figure 8.8: Changes to a shared service can break other services at runtime

Performance#

  • 存取共用服務需透過網路呼叫,引入 network latency 與 security latency
  • 可使用 gRPC 降低延遲,或使用非同步訊息(messaging)來緩解效能問題

Figure 8.9: Shared service introduces network and security latency

Scalability#

  • 共用服務必須隨著使用它的服務一起擴展,管理上更複雜

Figure 8.10: Shared services must scale as dependent services scale

Fault Tolerance#

  • 若共用服務不可用,所有依賴服務也將無法運作
  • Shared library 不存在此問題,因為共用功能在編譯時期已嵌入服務

Figure 8.11: Shared services introduce fault-tolerance issues

Trade-Offs:

優勢劣勢
適合高度變動的程式碼版本控制困難
異質代碼庫中無程式碼重複因延遲影響效能
保留 bounded context容錯與可用性問題(服務依賴)
無靜態程式碼共享擴展性與吞吐量問題(服務依賴)
執行時期變更風險較高

When to Use#

  • 適合高度多語言環境(polyglot)且共用功能經常變動的場景
  • 需注意執行時期副作用與風險

Sidecars and Service Mesh#

  • Sidecar pattern 源自 hexagonal architecture(Ports and Adaptors Pattern),將領域邏輯與技術基礎設施邏輯解耦

Figure 8.12: The Hexagonal pattern separated domain logic from technical coupling

  • 將 operational concerns(如 logging、monitoring、circuit breaker、service discovery、authentication、authorization)抽離到 sidecar 元件中

Figure 8.13: Two microservices that share the same operational capabilities

Figure 8.14: When each microservice includes a common component, architects can extract into sidecar

  • 當每個服務都包含 sidecar 元件時,形成 service mesh,提供一致的 operational interface 與 service plane

Figure 8.15: A service mesh is an operational link among services

  • 屬於 orthogonal reuse pattern,解決 domain coupling 與 operational coupling 正交交叉的問題

Sidecar 僅用於 operational coupling(logging、monitoring、service discovery 等),不應放入 domain 元件(如 AddressCustomer class),以避免不適當的耦合。

Trade-Offs:

優勢劣勢
提供一致的方式建立隔離的耦合每個平台需實作各自的 sidecar
允許一致的基礎設施協調sidecar 元件可能變得龐大複雜
可由團隊、集中或混合方式擁有

When to Use#

  • 適合在分散式架構中傳播 cross-cutting concern
  • 提供類似 Gang of Four Decorator Design Pattern 的架構等效物,獨立於正常連接性之外「裝飾」行為

Sysops Squad Saga: Common Infrastructure Logic#

團隊決定使用 sidecar 與 service mesh 來統一 operational coupling:

  • 由共用基礎設施團隊擁有並維護 sidecar
  • 提供的服務包括:Monitoring、Logging、Service discovery、Authentication、Authorization
  • 判斷規則:若超過一半的團隊需要某個共用工具程式碼,就放入 sidecar;否則暫不放入

Code Reuse: When Does It Add Value?#

  • Reuse 是最容易被濫用的抽象之一,過度重用會導致架構脆弱性(brittleness)
  • 早期 SOA 追求最大化重用的教訓:將所有 domain 的 Customer 資訊統一到單一服務,反而造成架構災難

Figure 8.16: Each domain within a large insurance company has a view of the customer

Figure 8.17: Unifying on a centralized Customer service

  • 重用有兩個面向:
    • 抽象(abstraction):識別重用候選者
    • 變更速率(rate of change):決定重用是否真正有價值

Reuse is derived via abstraction but operationalized by slow rate of change. 我們從作業系統、開源框架等變更緩慢的資產中受益於技術耦合,但內部 domain 能力或快速變動的技術框架則是糟糕的耦合目標。

Reuse via Platforms#

  • Platform 是組織內重用的新目標:為每個可區分的 domain capability 建構一個具有良好定義 API 的平台
  • 緩慢的變更速率驅動此推理:API 可設計為與呼叫者鬆散耦合,允許內部積極的實作變更而不破壞 API

Sysops Squad Saga: Shared Domain Functionality#

團隊面臨 ticketing 領域的三個服務(Ticket Creation、Ticket Assignment、Ticket Completion)共用資料庫邏輯的問題:

  • Shared data service 方案:提供資料抽象與集中連線池,但帶來效能、容錯與 runtime 變更風險

Figure 8.18: Option using a shared Ticket Data service for common database logic

  • Shared library (DLL) 方案:消除服務依賴和 HTTP 流量,但變更需重新測試部署

Figure 8.19: Option using a shared library for common database logic

最終團隊選擇 shared library,因為分析歷史程式碼變更記錄後發現共用資料庫邏輯相當穩定,不常變更。透過版本控制提供向後相容性,降低變更風險。