Native RPC & Service Bindings
Speakeasy では全てのサービス間通信に Cloudflare Workers Native RPC を使用しています。HTTP リクエストを介さず、Service Binding 経由で TypeScript メソッドを直接呼び出します。
アーキテクチャ概要
Section titled “アーキテクチャ概要”REST API との違い
Section titled “REST API との違い”| 項目 | REST API (HTTP) | Native RPC |
|---|---|---|
| 通信方式 | HTTP リクエスト | 直接メソッド呼び出し |
| レイテンシ | ネットワークラウンドトリップ | ゼロレイテンシ(同一データセンター内) |
| 型安全性 | OpenAPI / 手動型定義 | TypeScript 型がそのまま適用 |
| シリアライズ | JSON 変換が必要 | 自動(V8 構造化クローン) |
| エラーハンドリング | HTTP ステータスコード | TypeScript 例外 |
Service Bindings マップ
Section titled “Service Bindings マップ”Apps → Services
Section titled “Apps → Services”| 呼び出し元 | バインディング | 呼び出し先 |
|---|---|---|
| Counter | PROOF | Proof(認証) |
| Counter | VINTAGE | Vintage(連絡先) |
| Counter | FIZZ | Fizz(チャット・配信) |
| Counter | BASE | Base(組織・店舗) |
| Counter | BELL | Bell(通知) |
| Penthouse | PROOF | Proof(認証) |
| Penthouse | VINTAGE | Vintage(連絡先) |
| Penthouse | BASE | Base(組織・店舗) |
Gateways → Services
Section titled “Gateways → Services”| 呼び出し元 | バインディング | 呼び出し先 |
|---|---|---|
| Door | PROOF | Proof(認証) |
| Tap | PROOF | Proof(認証) |
| Tap | FIZZ | Fizz(チャット) |
| Tap | BASE | Base(組織) |
| Tap | VINTAGE | Vintage(連絡先) |
Service → Service
Section titled “Service → Service”| 呼び出し元 | バインディング | 呼び出し先 | 主な用途 |
|---|---|---|---|
| Vintage | FIZZ | Fizz | WebSocket 通知(notify, broadcast) |
| Fizz | VINTAGE | Vintage | グループ情報取得 |
| Base | PROOF | Proof | ユーザー・セッション管理 |
| Base | BELL | Bell | 通知送信 |
実装パターン
Section titled “実装パターン”1. サービス側: WorkerEntrypoint
Section titled “1. サービス側: WorkerEntrypoint”各サービスは WorkerEntrypoint クラスを継承し、public メソッドを RPC インターフェースとして公開します。
import { WorkerEntrypoint } from 'cloudflare:workers';
export default class FizzWorker extends WorkerEntrypoint<Env> { // HTTP フォールバック(Direct HTTP アクセス用) override async fetch(req: Request): Promise<Response> { return app.fetch(req, this.env, this.ctx); }
// Native RPC メソッド async listChatRooms(params: { readonly shopId: string; readonly userId: string; readonly userType: 'shop' | 'customer'; readonly limit?: number; readonly cursor?: string; }) { // 実装... }
async sendChatMessage(params: { readonly roomId: string; readonly senderId: string; readonly senderType: 'shop' | 'customer'; readonly content: string; }) { // 実装... }}2. クライアント側: RPC クライアント型
Section titled “2. クライアント側: RPC クライアント型”アプリ側では Service Binding を型安全に利用するためのクライアント型を定義します。
import { getRequestEvent } from '$app/server';
/** Fizz サービスの RPC クライアント型 */export type FizzClient = { listChatRooms(params: { readonly shopId: string; readonly userId: string; readonly userType: 'shop' | 'customer'; readonly limit?: number; readonly cursor?: string; }): Promise<ChatRoomsResult>;
sendChatMessage(params: { readonly roomId: string; readonly senderId: string; readonly senderType: 'shop' | 'customer'; readonly content: string; }): Promise<ChatMessage>; // ...};
/** Service Binding から Fizz クライアントを取得 */export const getFizzClient = (): FizzClient => { const { platform } = getRequestEvent(); if (!platform?.env?.FIZZ) { throw new Error('FIZZ service binding not available'); } return platform.env.FIZZ as unknown as FizzClient;};3. Remote Functions からの呼び出し
Section titled “3. Remote Functions からの呼び出し”SvelteKit Remote Functions がサーバーサイドで RPC クライアントを使用します。
import { query, command } from '$app/server';import { z } from 'zod';import { getFizzClient } from './fizz-rpc';
export const getChatRooms = query( z.object({ limit: z.number().optional(), cursor: z.string().optional() }), async ({ limit, cursor }) => { const client = getFizzClient();
// Native RPC 呼び出し(HTTP なし) const res = await client.listChatRooms({ shopId: getShopId(), userId: getUserId(), userType: 'shop', limit, cursor });
return res; });wrangler.jsonc 設定例
Section titled “wrangler.jsonc 設定例”{ "services": [ { "binding": "PROOF", "service": "proof" }, { "binding": "VINTAGE", "service": "vintage" }, { "binding": "FIZZ", "service": "fizz" }, { "binding": "BASE", "service": "base" }, { "binding": "BELL", "service": "bell" } ],
"env": { "dev": { "services": [ { "binding": "PROOF", "service": "proof-dev", "remote": true }, { "binding": "VINTAGE", "service": "vintage-dev", "remote": true }, { "binding": "FIZZ", "service": "fizz-dev", "remote": true }, { "binding": "BASE", "service": "base-dev", "remote": true }, { "binding": "BELL", "service": "bell-dev", "remote": true } ] } }}- 本番環境では同一アカウント内の Workers 間で直接バインド
- 開発環境では
"remote": trueを指定してリモート Workers に接続
データフローの全体像
Section titled “データフローの全体像”ブラウザ (Svelte コンポーネント) ↓ Remote Function 呼び出しSvelteKit サーバー (Remote Function) ↓ getFizzClient() / getVintageClient() 等Service Binding (platform.env.FIZZ) ↓ Native RPC (メソッド直接呼び出し)WorkerEntrypoint (FizzWorker) ↓ Drizzle ORM / Durable Objects / QueuesPostgreSQL (Hyperdrive) / KV / R2この構成により、ブラウザからデータベースまで HTTP リクエストを一切介さず、全てが Cloudflare のインフラ内で完結します。