ポートフォリオサイトのロゴ
Icon for Hono Service bindings入門

Hono Service bindings入門

Cloudflare WorkersのService Bindings機能を使って、パブリックにアクセス可能なURLを経由せずに別のWorkersを呼び出す方法を解説します。Honoを使った複数Workersの連携実装例を紹介します。

はじめに

Cloudflare Workers にはパブリックにアクセス可能な URL を経由せず別の Workers を呼び出すことができます。

今回は Cloudflare Network 内で通信できる Service Bindings の機能を使って、複数の Workers を連携したいと思います。

内容としてはクライアントからのエンドポイントを想定した Webhook という名前のエンドポイントを Hono で作成し、エンドポイントごとに別の Workers を呼び出す構成です。

記事の作成にはこちらの投稿を参考にさせていただきました。

zenn.dev

Honoを使ってCloudflare Multi Workers環境を作る

この記事の対象者

Hono で別の Workers を連携したい人

成果物

github.com

GitHub - Suntory-Y-Water/hono-service-binding: Hono にCloudflare WorkersのService Bindingsを使った練習用リポジトリ

Hono にCloudflare WorkersのService Bindingsを使った練習用リポジトリ - Suntory-Y-Water/hono-service-binding

Monorepo環境を構築

参考にさせていただいた記事と同じ構成のほうが、誤ったときに原因をつきとめやすいので同じ構成にさせていただきます。
複数の Cloudflare Workers を 1 つのリポジトリで管理するために、Monorepo を作成し、turborepo で環境構築します。
パッケージ共通のツールはプロジェクトルートにインストールしておきます。

package.json
{
  "devDependencies": {
    "turbo": "^1.10.9",
    "wrangler": "^3.1.2"
  },
  "scripts": {
    "dev": "turbo dev"
  }
}

pnpm run dev でパッケージを平行に起動するために、turborepo を設定します。

turbo.json
{
  "$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_SERVICERPC_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 ではテキストをレスポンスします。

http-service/src/index.ts
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 を実装します。

developers.cloudflare.com

Remote-procedure call (RPC)

The built-in, JavaScript-native RPC system built into Workers and Durable Objects.

WorkerEntrypoint を継承した RpcService を Export します。

rpc-service/src/index.ts
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 を型安全に使用します。

hono.dev

Cloudflare Workers - Hono

Web framework built on Web Standards for Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Node.js, and others. Fast, but not only fast.

Bindings という型を定義して、Hono の Generics に渡します。

このとき、Bindings のキーは wrangler.tomlservices フィールドに設定した binding の値と同じにしておきます。

webhook/src/index.ts
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 つです。

  1. RPC で呼び出すメソッドが非同期関数ではなくても、非同期扱いになります。
  2. type Bindings で RPC を型安全に使用するときは、Service<T> で使用する必要があります。
developers.cloudflare.com

Remote-procedure call (RPC)

The built-in, JavaScript-native RPC system built into Workers and Durable Objects.

デプロイする

各 worker を pnpm run deploy します。

デプロイ後、https://webhook.ドメイン名.workers.dev/http にアクセスすれば「Hello Http Service Worker!」と表示されます。

https://webhook.ドメイン名.workers.dev/rpc にアクセスすれば、「add result: 3」と表示され、正常に呼び出されていることが確認できると思います。

実運用のするときは参考にさせていただいた記事のようなトークンの検証などを設けたほうが良いですね。

おわりに

いかがでしたでしょうか。
既にこのテーマで記事を書いている人や、公式ドキュメントが丁寧に書かれているのもあり開発体験がとても良かったです。
次作成するアプリの規模によりますが取り入れてみたいですね!
以上になります!

参考

zenn.dev

Honoを使ってCloudflare Multi Workers環境を作る

zenn.dev

LINE Botの開発でCloudflareとHonoを使う理由

developers.cloudflare.com

Service bindings

Facilitate Worker-to-Worker communication.

github.com

GitHub - cloudflare/js-rpc-and-entrypoints-demo

Contribute to cloudflare/js-rpc-and-entrypoints-demo development by creating an account on GitHub.