選好編碼格式、談完前後向相容之後,還有一個問題:編碼過的資料到底怎麼從一個程序流到另一個? DDIA 把它收斂成三條路——透過資料庫、透過服務呼叫 (REST/RPC)、透過非同步訊息。三條路各自對「相容性」提出不同的要求。
🧠 Intuition
TIP
相容性不是抽象潔癖,而是滾動升級 (rolling upgrade) 逼出來的現實:程式碼無法一次全換,新舊版本必然並存一段時間。於是每條資料流都得同時扛住向後相容(新碼讀舊資料)與向前相容(舊碼讀新資料)——向後通常容易,向前才是難點(舊碼得學會忽略它看不懂的新內容)。
- 透過資料庫:一方寫入(編碼)、另一方之後讀取(解碼)——連「未來的自己」都算對象。
- 透過服務:client 編碼請求、server 解碼後再編碼回應(REST / RPC)。
- 透過非同步訊息:經由 broker 或 actor 中轉,sender 與 recipient 徹底解耦。
⚖️ Tradeoffs
透過資料庫:資料比程式碼活得久
- 寫入方編碼、讀取方解碼;同一個資料庫可能被新舊版程式同時讀寫,所以要同時撐向後與向前相容。
- 最容易被忽略的坑——保留未知欄位 (preserving unknown fields):舊版程式讀到含新欄位的記錄、更新後寫回時可能把新欄位弄丟。這是應用層要主動處理的責任。
- 為什麼向前相容非有不可?因為資料比程式碼活得久 (data outlives code):程式幾分鐘就能全數替換,但資料庫裡同時躺著五毫秒前和五年前寫入的資料,除非主動遷移。多數關聯庫允許「加一個帶 null 預設的欄位」而不必重寫既有資料,schema 演化讓整庫看起來像單一 schema,底層其實混著各歷史版本。
透過服務:REST vs RPC
- 最常見是客戶端-伺服器 (client-server);伺服器又可以是別的服務的客戶端,串成 SOA / 微服務 (microservices)——核心目標是各服務能獨立部署與演化,因此服務間編碼必須跨版本相容。
- REST:建立在 HTTP 原則上的設計哲學,用 URL 識別資源 (resource)、善用 HTTP 特性;常搭 OpenAPI (Swagger) 產文件。相對地 SOAP 基於 XML、用 WSDL 描述,因複雜與互通問題在小公司式微。
- 選型:REST 適合公開 API(好實驗、好除錯、生態豐富);RPC 框架(gRPC / Finagle / Rest.li)主要用於同組織內部服務,可用二進位編碼換更好效能。
RPC 的原罪:把網路呼叫偽裝成本地呼叫
- RPC 想營造位置透明性 (location transparency)——讓遠端呼叫看起來像本地函數。但這個抽象本質有缺陷,因為兩者根本不同:
- 網路請求會不可預測地失敗;可能逾時而不知是否已被處理。
- 重試可能造成重複執行,除非有冪等性 (idempotence)。
- 延遲劇烈變動;參數得編碼成 bytes(大物件尤難);client / server 可能用不同語言,型別轉換出錯。
- 新一代框架乾脆明確區分遠端與本地,用 futures / promises 封裝非同步(gRPC 還支援串流)。相容性上可假設伺服器先升級:請求端維持向後相容、回應端維持向前相容;跨組織時無法逼客戶端升級,只能長期維護多個 API 版本(URL 帶版本號或用 Accept header)。
透過非同步訊息:broker 與 actor
- 非同步訊息傳遞介於 RPC 與資料庫之間:像 RPC 一樣低延遲送達,又像資料庫一樣經訊息代理 (message broker) 暫存。
- broker(RabbitMQ / ActiveMQ / NATS / Kafka)的好處:收件者掛了能緩衝、崩潰重啟能重送、發送者不需知道對方 IP/port(位置透明)、一則訊息可多播給多個 consumer、sender 與 recipient 邏輯解耦。訊息通常單向,要回覆得走另一條 channel。
- broker 不強制資料模型——訊息只是帶 metadata 的 bytes,任何編碼皆可;只要格式前後向相容,publisher 與 consumer 就能各自獨立升級(但若 consumer 轉發到別的 topic,一樣要小心保留未知欄位)。
- Actor 模型:把邏輯封進 actor,各有局部狀態、只靠非同步訊息溝通。分散式 actor 框架(Akka / Orleans / Erlang OTP)讓同節點或跨節點都用同一套訊息機制——位置透明在這裡比 RPC 自然,因為 actor 模型本就假設訊息可能遺失。但滾動升級下各家編碼策略是坑:Akka 預設 Java 序列化(不相容,需換 Protobuf)、Orleans 預設格式不支援滾動升級、Erlang OTP 改 record schema 出乎意料地難。
🔑 Takeaways
- 三種資料流——資料庫 / 服務 (REST·RPC) / 非同步訊息——都被滾動升級逼著同時扛向後與向前相容;向前相容(舊碼讀新資料)永遠是難的一半。
- 資料庫這條路的關鍵心法是 data outlives code 與保留未知欄位:別讓舊碼在寫回時吃掉它看不懂的新欄位。
- RPC 的原罪是假裝網路呼叫等於本地呼叫;認清失敗、逾時、重試(冪等)、延遲的本質差異,才用得對。
- 非同步訊息用 broker / actor 換來緩衝、重送、解耦與多播,代價是單向、最終一致的心智模型——與序列化格式的前後向相容一起,才構成安全的獨立部署。
No notes yet — jot your takeaways or Q&A here.