你的服務用 Go 寫,呼叫一個 Python 的 ML 服務,兩個服務之間要怎麼定義 API?

你在看一個 Go 的 codebase,想讓 IDE 給你 go-to-definition 和型別資訊,需要什麼工具?

你的 CI 要跑 Go、Python、Node.js 三個語言的測試,怎麼管理這些環境?

這篇是這些場景的工具地圖。


跨語言服務通訊

Protocol Buffers(protobuf)+ gRPC

當兩個不同語言的服務要溝通,你需要一個雙方都能理解的 API 定義。

protobuf 是一個語言無關的介面描述語言(IDL):

// user.proto
syntax = "proto3";
 
service UserService {
    rpc GetUser (GetUserRequest) returns (User);
    rpc ListUsers (ListUsersRequest) returns (stream User);
}
 
message GetUserRequest {
    int64 user_id = 1;
}
 
message User {
    int64 id = 1;
    string name = 2;
    string email = 3;
}

protoc(protobuf compiler)從這個 .proto 檔生成各語言的 code:

protoc --go_out=. --go-grpc_out=. user.proto      # 生成 Go code
protoc --python_out=. --grpc_python_out=. user.proto  # 生成 Python code

Go 的 server 和 Python 的 client 各自用生成的 code,型別定義一致,不用手動維護兩邊。

gRPC 是建在 protobuf 上的 RPC 框架,支援:

  • Unary(一個請求一個回應)
  • Server streaming(一個請求,多個回應)
  • Client streaming
  • Bidirectional streaming

相較於 REST + JSON,gRPC 的優點:binary 格式比 JSON 小、速度快、型別強制。代價是 debug 不如 JSON 直觀(需要工具解碼)。

OpenAPI / Swagger(REST 的 IDL)

如果你用 REST,OpenAPI 規格讓你也能從定義生成多語言 client:

# openapi.yaml
paths:
  /users/{id}:
    get:
      parameters:
        - name: id
          in: path
          schema:
            type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

openapi-generator 能從這個生成 Go、Python、TypeScript 等語言的 client SDK。


語言環境管理

asdf / mise:多語言版本管理

如果你的 repo 有多個語言,每個語言有自己的版本需求:

# .tool-versions(asdf 格式)
python 3.11.5
golang 1.21.0
nodejs 20.9.0

asdf install 一次安裝所有版本,asdf local 設定目錄的語言版本,切換目錄自動切版本。

mise(前身是 rtx)是更快的替代品,相容 .tool-versions 格式。

Docker:最極端的環境隔離

當「確保環境一致」比「快速安裝」更重要,用 Docker:

# Dockerfile(多語言服務)
FROM golang:1.21 AS go-builder
COPY go/ /app/
RUN cd /app && go build -o /server .
 
FROM python:3.11-slim
COPY --from=go-builder /server /server
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt

Multi-stage build 讓你在同一個 Dockerfile 裡混合不同語言的建構步驟。


IDE 工具:Language Server Protocol(LSP)

你用 VS Code 或其他 editor,為什麼 go-to-definition 在 Go 能跳到正確位置、在 TypeScript 也能?

因為大多數現代語言都實作了 LSP(Language Server Protocol)——一個語言和 editor 之間的標準協議,定義了 textDocument/definitiontextDocument/hovertextDocument/completion 等操作的格式。

Editor 發送請求(「游標在 line 23,col 10,這是什麼?」),Language Server 回應(「這是 User.Name,定義在 models.go:45」)。

主要的 Language Server:

  • Gogopls(Go 官方)
  • Pythonpylsppyright(Microsoft)
  • TypeScript/JavaScripttsserver(內建於 TypeScript compiler)
  • Rustrust-analyzer

如果你在一個新語言的 codebase 裡 navigate,第一件事是確認對應的 Language Server 有安裝——這讓讀不熟悉語言的程式碼的效率提升幾倍。


跨語言測試工具

合約測試(Contract Testing)

當你有 A(Go)和 B(Python)兩個服務,A 呼叫 B 的 API。你怎麼確保 A 和 B 的 API 定義沒有衝突?

Pactpact.io)是主流的合約測試框架,支援 Go、Python、Java、Node.js:

  • B(Provider)定義它能提供什麼
  • A(Consumer)定義它預期的 response 格式
  • Pact 驗證 Provider 的實際回應滿足 Consumer 的期望

這讓 API 相容性問題在 CI 就被抓到,不是在 production。

k6:多語言的負載測試

k6 用 JavaScript 寫測試腳本,但可以對任何 HTTP/gRPC 服務做壓測——不管後端是 Go 還是 Python:

// k6 腳本
import http from 'k6/http';
 
export let options = {
    vus: 100,         // 100 個虛擬使用者
    duration: '30s',
};
 
export default function() {
    http.get('http://api.example.com/users/1');
}

序列化格式的跨語言相容

JSON:最通用,但有細節

JSON 在所有語言都支援,但有幾個跨語言的細節:

  • 整數精度:JavaScript 的 number 型別是 64-bit float,大整數(> 2^53)會失去精度——Go 的 int64 序列化成 JSON 傳給 JS,建議用 string
  • 時間格式:用 ISO 8601(2024-01-15T10:30:00Z),不要用 Unix timestamp(語意不清),也不要用 locale-specific 格式
  • null vs missing field:Python 的 None、Go 的 nil、JavaScript 的 null/undefined 在 JSON 序列化行為不完全一樣,需要明確處理

MessagePack / CBOR:binary JSON

如果 JSON 的 overhead(text 格式、parsing 成本)是問題,MessagePack 和 CBOR 是 binary 格式的替代品,大多數語言都有 library,和 JSON 的資料模型相容但更緊湊快速。


這些工具的選擇,取決於你的跨語言場景:

  • 微服務通訊 → protobuf + gRPC 或 OpenAPI
  • 多語言本地開發環境 → asdf / mise 或 Docker
  • IDE navigate 多語言 codebase → 確認各語言的 LSP 有安裝
  • 驗證跨服務 API 相容 → Pact 合約測試

Elixir 並行模型特別