Hono Service bindings入門
はじめに
Cloudflare Workers にはパブリックにアクセス可能なURLを経由せず別のWorkersを呼び出すことができます。
今回はCloudflare Network 内で通信できる Service Bindings の機能を使って、複数のWorkersを連携したいと思います。
内容としてはクライアントからのエンドポイントを想定したWebhookという名前のエンドポイントをHonoで作成し、エンドポイントごとに別のWorkersを呼び出す構成です。
記事の作成にはこちらの投稿を参考にさせていただきました。
この記事の対象者
Honoで別のWorkersを連携したい人
成果物
Monorepo環境を構築
参考にさせていただいた記事と同じ構成のほうが、誤ったときに原因をつきとめやすいので同じ構成にさせていただきます。
複数の Cloudflare Workers を 1 つのリポジトリで管理するために、Monorepo を作成し、turborepoで環境構築します。
パッケージ共通のツールはプロジェクトルートにインストールしておきます。
{
"devDependencies": {
"turbo": "^1.10.9",
"wrangler": "^3.1.2"
},
"scripts": {
"dev": "turbo dev"
}
}
pnpm run dev
でパッケージを平行に起動するために、turborepo を設定します。
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"dev": {
"dependsOn": ["^dev"]
}
}
}
Workers のコードは、workers
ディレクトリにパッケージとして配置します。
packages:
- "workers/*"
node_modules
.wrangler
.turbo
webhook アプリを作成
クライアントからのリクエストを想定したwebhookアプリを作成します。
他の内部プライベートサービスと通信する役割を担っています。
cd workers/
create hono@latest
Service Bindingの設定を行う
webhookのService Binding設定
Service Binding を設定するために、webhook の wrangler.toml
に services
フィールドを追加します。
名前はなんでもいいですが、分かりやすくHTTP_SERVICE
とRPC_SERVICE
に設定します。
compatibility_dateは2024-07-01に設定します。
name = "webhook"
compatibility_date = "2024-07-01"
services = [
{ binding = "HTTP_SERVICE", service = "http-service" },
{ binding = "RPC_SERVICE", service = "rpc-service" },
]
rpc-serviceのService Binding設定
Cloudflare WorkersのService BindingにはRPCとHTTPの2通りあります。
RPCの場合、別のworkerをJavaScriptの関数のように使用することができます。
workersディレクトリに到達したら、pnpm create cloudflare@latest rpc-service
でrpcで使用するworkerを作成していきます。
rpc-service
が作成できたら、wrangler.toml
の内容を編集していきます。
name = "rpc-service"
main = "src/index.ts"
compatibility_date = "2024-07-01"
[dev]
port = 8888
ポート番号は初期設定の8787以外であれば被らなそうなものにしましょう
http-serviceのService Binding設定
http-serviceはHonoで作成します。pnpm create hono@latest
で作成後、wrangler.toml
の内容を編集していきます。
name = "http-service"
compatibility_date = "2024-07-01"
[dev]
port = 9999
ロジックの作成
各workerのロジックを書いていきます。どのworkerが動いているか分かれば良いので、内容は適当です。
http-serviceではテキストをレスポンスします。
import { Hono } from "hono";
const app = new Hono().basePath("/http");
app.get("/", (c) => c.text("Hello Http Service Worker!"));
export default app;
rpc-serviceではService Bindingを介して、同じ Cloudflare アカウント上の他のworkerから呼び出すことができるmethodを実装します。
WorkerEntrypoint を継承したRpcServiceをExportします。import { WorkerEntrypoint } from "cloudflare:workers";
export class RpcService extends WorkerEntrypoint {
async fetch(request: Request): Promise<Response> {
return new Response("Hello Rpc Service Worker!");
}
add(a: number, b: number): number {
return a + b;
}
}
export default RpcService;
webhook/src/index.tsでExportした Service Binding を型安全に使用します。
Bindings という型を定義して、Hono の Generics に渡します。このとき、Bindings
のキーはwrangler.toml
のservices
フィールドに設定したbinding
の値と同じにしておきます。
import { Hono } from "hono";
import type { RpcService } from "../../rpc-service/src";
type Bindings = {
// RPCの場合、Service<T>で使用する必要があります。
RPC_SERVICE: Service<RpcService>;
HTTP_SERVICE: Fetcher;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get("/", (c) => {
return c.text("Hello Hono!");
});
app.get("/rpc", async (c) => {
// 呼び出し元が非同期関数ではなくても非同期扱いになる。
const res = await c.env.RPC_SERVICE.add(1, 2);
return c.text(`add result: ${res}`);
});
app.get("/http", async (c) => {
const res = await c.env.HTTP_SERVICE.fetch(c.req.raw);
const text = await res.text();
return c.text(text);
});
export default app;
注意点を上げるとすれば、以下2つです。
- RPCで呼び出すメソッドが非同期関数ではなくても、非同期扱いになります。
- type BindingsでRPCを型安全に使用するときは、
Service<T>
で使用する必要があります。
デプロイする
各workerをpnpm run deploy
します。
デプロイ後、https://webhook.ドメイン名.workers.dev/http
にアクセスすれば「Hello Http Service Worker!」と表示されます。
https://webhook.ドメイン名.workers.dev/rpc
にアクセスすれば、「add result: 3」と表示され、正常に呼び出されていることが確認できると思います。
実運用のするときは参考にさせていただいた記事のようなトークンの検証などを設けたほうが良いですね。
おわりに
いかがでしたでしょうか。
既にこのテーマで記事を書いている人や、公式ドキュメントが丁寧に書かれているのもあり開発体験がとても良かったです。
次作成するアプリの規模によりますが取り入れてみたいですね!
以上になります!
参考
Discussion