當你在開發一個需要支援多種程式語言(如 Python, TypeScript, Go, Java)的 SDK 時,最令工程師頭痛的通常不是單一語言的實作,而是如何確保所有語言版本的邏輯完全一致。如果核心邏輯複雜且龐大,你得在每種語言中重複寫一遍,這不僅極大地增加了開發成本,更會導致不同語言版本之間出現行為不一致的 Bug,嚴重損害使用者的信任。
這篇文章將分享 Temporal 團隊如何透過 Rust 建立一個共享的核心層(Shared Core),並在上方疊加語言特定層(Language-specific Layers),來實現高效且可靠的多語言 SDK 架構。
核心架構設計
為了避免重複造輪子,Temporal 採用的架構分為三個層級:
第一層是 Rust Core。這是整個 SDK 的大腦,所有複雜的狀態機、核心業務邏輯與 durability(持久化)保證都寫在這裡。這樣做能確保邏輯只有一份,修改一次即可同步到所有語言。
第二層是 Bridge Layer(橋接層)。這是一個關鍵的膠水層,負責將 Rust 的功能對接給上層語言。橋接層通常也用 Rust 撰寫,但會針對不同語言定義不同的對接介面。
第三層是 Language-specific SDK。這是使用者直接接觸的 API 層。這一層的目標是提供 Idiomatic Experience(慣用法體驗),讓 Python 工程師感覺在用 Python,TypeScript 工程師感覺在用 TS,而不是在用一個被強行包裝的 Rust 函式庫。
跨語言溝通的實務挑戰
在實作橋接層時,工程師會遇到三個核心難題:
類型轉換(Type Conversion) Rust 與其他語言的記憶體佈局不同,必須進行轉換。建議使用 IDL(Interface Definition Language,介面定義語言),例如 Protobuf,透過代碼生成(Codegen)來減少手寫轉換邏輯。此外,在橋接層應盡量使用簡單的類型,避免在 Rust 核心中使用過於複雜的泛型或枚舉,以免在跨越 C FFI(Foreign Function Interface,外部函式介面)時產生不可預期的錯誤。
非同步概念對接(Async Bridging) 不同語言處理非同步的方式迥異:Rust 有 Future,JS 有 Promise,Python 有 asyncio,Ruby 有 Fiber。 若使用 PyO3(Python)或 Neon(Node.js)等成熟的橋接庫,很多問題會被自動處理。但如果必須手寫,則需要考慮回呼函式(Callback)或事件迴圈(Event Loop)。特別是在有 GIL(Global Interpreter Lock,全域解釋器鎖)的語言(如 Ruby)中,必須確保回呼在該語言擁有的執行緒上執行,否則會導致崩潰。
記憶體管理(Memory Management) 這是最危險的部分。在跨語言邊界傳遞資料時,必須遵循一個金律:記憶體在哪裡創建,就必須在哪裡銷毀。 如果 Rust 分配了記憶體並傳給 JS,JS 使用完後必須將指針傳回 Rust 進行釋放。若處理不當,會導致記憶體洩漏(Memory Leak)或段錯誤(Segmentation Fault)。建議將橋接層盡量保持精簡(Thin Layer),邏輯越少,出錯機會越低。
架構的限制與未來優化方向
雖然 Rust Core 模式大幅提升了開發速度,但仍有其限制:
原生擴展的部署成本 分發原生二進位檔(Native Binaries)很麻煩。例如 npm 不像 PyPI 那樣能很好地處理多平台二進位檔,導致使用者下載包時體積過大。此外,Java 的 JNI 或 Go 的 cgo 會增加運維複雜度,有些使用者非常排斥在環境中執行原生擴展。
效能損耗 使用 Protobuf 等序列化 IDL 會帶來額外的 CPU 開銷。如果效能是極限瓶頸,可以考慮 FlatBuffers 或 Cap'n Proto,這類 IDL 採用相同的物理記憶體佈局,無需序列化步驟,讀取速度極快。
潛在的解決方案:WebAssembly (Wasm) WebAssembly 提供了一種極具吸引力的替代方案。它可以將 Rust 編譯成平台無關的字節碼(Bytecode),解決了分發原生二進位檔的痛苦。透過在 Java 或 Go 中運行 Wasm 虛擬機(如 wazero 或 Chicory),可以在不使用原生擴展的情況下執行 Rust 核心邏輯。
實務建議總結
對於想要嘗試此架構的團隊,有幾個核心建議:
優先選擇成熟的橋接庫。不要嘗試從零手寫 C FFI,除非別無選擇。PyO3 (Python) 和 Neon (Node.js) 能幫你省掉 80% 的麻煩。
不要犧牲使用者體驗。雖然核心是 Rust,但上層 API 必須符合該語言的習慣。不要把 Rust 的 API 原封不動地搬到 Python 中。
預留行為注入的鉤子(Hooks)。在設計核心層時,要預想到使用者可能想自定義日誌、指標或網路請求。建議將這些副作用(Side Effects)透過橋接層回傳給語言層執行,而不是在 Rust 核心中硬編碼。
來源:infoq.com - Rust at the Core - Accelerating Polyglot SDK Development
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。