Service bindings を使うと、公開 URL を経由せずに、ある Worker から別の Worker を呼び出せます。
Service bindings で、ほかの Worker に公開する内部 API を作れます。組み込みの WorkerEntrypoint クラスを拡張し、独自の公開メソッドを追加します。これらの公開メソッドは、この Worker への バインディング を宣言した、同じ Cloudflare アカウント上のほかの Worker から直接呼び出せます。
Workers の RPC システム は、同じ Worker 内の JavaScript 関数を呼ぶ感覚にできるだけ近づけて設計されています。多くの場合、すべてが 1 つの Worker にあるときと同じ書き方でコードを書けます。
たとえば、次の Worker は公開メソッド add(a, b) を実装します。
たとえば、Worker B が公開メソッド add(a, b) を実装している場合です。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "worker_b",
"main": "./src/workerB.js"
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "worker_b"
main = "./src/workerB.js"import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async fetch() {
return new Response("Hello from Worker B");
}
add(a, b) {
return a + b;
}
}import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async fetch() {
return new Response("Hello from Worker B");
}
add(a: number, b: number) {
return a + b;
}
}from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response("Hello from Worker B")
def add(self, a: int, b: int) -> int:
return a + bWorker A は、Worker B への バインディング を宣言できます。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "worker_a",
"main": "./src/workerA.js",
"services": [
{
"binding": "WORKER_B",
"service": "worker_b"
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "worker_a"
main = "./src/workerA.js"
[[services]]
binding = "WORKER_B"
service = "worker_b"これにより、Worker A から Worker B の add() メソッドを呼び出せます。
export default {
async fetch(request, env) {
const result = await env.WORKER_B.add(1, 2);
return new Response(result);
},
};export default {
async fetch(request, env) {
const result = await env.WORKER_B.add(1, 2);
return new Response(result);
},
};from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
result = await self.env.WORKER_B.add(1, 2)
return Response(f"Result: {result}")RPC システムを使うために、特別なプロトコルを学んだり実装したりする必要はありません。クライアント(この場合は Worker A)は Worker B を呼び、クライアントが渡した引数で特定の手続きを実行するよう伝えます。これは標準の JavaScript クラスで実現します。
Worker から RPC メソッドを提供するには、次の例のように WorkerEntrypoint クラスを拡張する必要があります。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
async add(a, b) { return a + b; }
}from workers import WorkerEntrypoint
class Default(WorkerEntrypoint):
async def add(self, a, b):
return a + bWorker が呼ばれるたびに、クラスの新しいインスタンスが作られます。Worker はクラスとして実装しますが、状態は持ちません。クラスインスタンスは呼び出しの間だけ存在します。Workers で状態を永続化したり調整したりする場合は、Durable Objects を使ってください。
env オブジェクトは、WorkerEntrypoint クラスのクラスプロパティとして公開されます。
たとえば、環境変数 GREETING へのバインディングを宣言する Worker では:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-worker",
"vars": {
"GREETING": "Hello"
}
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-worker"
[vars]
GREETING = "Hello"this.env.GREETING でアクセスできます。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
fetch() { return new Response("Hello from my-worker"); }
async greet(name) {
return this.env.GREETING + name;
}
}from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response("Hello from my-worker")
async def greet(self, name):
return self.env.GREETING + nameこの方法で、どの種類の バインディング でも使えます。
ctx オブジェクトは、WorkerEntrypoint クラスのクラスプロパティとして公開されます。
たとえば、waitUntil() メソッドを呼んで、呼び出しコンテキストの生存期間を延ばせます。
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint {
fetch() { return new Response("Hello from my-worker"); }
async signup(email, name) {
// sendEvent() will continue running, even after this method returns a value to the caller
this.ctx.waitUntil(this.#sendEvent("signup", email))
// Perform any other work
return "Success";
}
async #sendEvent(eventName, email) {
//...
}
}from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response("Hello from my-worker")
async def signup(self, email, name):
# _send_event() will continue running, even after this method returns a value to the caller
self.ctx.waitUntil(self._send_event("signup", email))
# Perform any other work
return "Success"
async def _send_event(self, event_name, email):
# ...
passWorker に 静的アセットバインディング がある場合、RPC メソッド内から this.env.ASSETS.fetch() を呼べます。RPC メソッドは request パラメータを受け取らないため、任意のホスト名で Request または URL を組み立てます。ホスト名はアセットバインディングでは無視され、パス名だけが使われます。
import { WorkerEntrypoint } from "cloudflare:workers";
export class ImageWorker extends WorkerEntrypoint {
async getImage(path) {
return this.env.ASSETS.fetch(new Request(`https://assets.local${path}`));
}
}import { WorkerEntrypoint } from "cloudflare:workers";
export class ImageWorker extends WorkerEntrypoint {
async getImage(path: string): Promise<Response> {
return this.env.ASSETS.fetch(
new Request(`https://assets.local${path}`)
);
}
}from workers import WorkerEntrypoint, Request
class ImageWorker(WorkerEntrypoint):
async def get_image(self, path: str):
return await self.env.ASSETS.fetch(
Request.new(f"https://assets.local{path}")
)呼び出し側は、このメソッドを RPC で呼び出せます。
const response = await env.IMAGE_SERVICE.getImage("/images/logo.png");const response = await env.IMAGE_SERVICE.getImage("/images/logo.png");response = await self.env.IMAGE_SERVICE.get_image("/images/logo.png")1 つの Worker から、デフォルトエクスポートに加えて、任意の数の名前付き WorkerEntrypoint クラスをエクスポートできます。そのうえで、特定の名前付きエントリポイントへの Service binding を宣言できます。
これを使って、複数の計算処理をまとめられます。たとえば、アプリケーションの権限ロールごとに別の WorkerEntrypoint を作り、ロール固有の RPC メソッドを提供できます。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "todo-app",
"d1_databases": [
{
"binding": "D1",
"database_name": "todo-app-db",
"database_id": "<unique-ID-for-your-database>"
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "todo-app"
[[d1_databases]]
binding = "D1"
database_name = "todo-app-db"
database_id = "<unique-ID-for-your-database>"import { WorkerEntrypoint } from "cloudflare:workers";
export class AdminEntrypoint extends WorkerEntrypoint {
async createUser(username) {
await this.env.D1.prepare("INSERT INTO users (username) VALUES (?)")
.bind(username)
.run();
}
async deleteUser(username) {
await this.env.D1.prepare("DELETE FROM users WHERE username = ?")
.bind(username)
.run();
}
}
export class UserEntrypoint extends WorkerEntrypoint {
async getTasks(userId) {
return await this.env.D1.prepare(
"SELECT title FROM tasks WHERE user_id = ?"
)
.bind(userId)
.run();
}
async createTask(userId, title) {
await this.env.D1.prepare(
"INSERT INTO tasks (user_id, title) VALUES (?, ?)"
)
.bind(userId, title)
.run();
}
}
export default class extends WorkerEntrypoint {
async fetch(request, env) {
return new Response("Hello from my to do app");
}
}from workers import WorkerEntrypoint, Response
class AdminEntrypoint(WorkerEntrypoint):
async def create_user(self, username):
await self.env.D1.prepare("INSERT INTO users (username) VALUES (?)").bind(username).run()
async def delete_user(self, username):
await self.env.D1.prepare("DELETE FROM users WHERE username = ?").bind(username).run()
class UserEntrypoint(WorkerEntrypoint):
async def get_tasks(self, user_id):
return await self.env.D1.prepare("SELECT title FROM tasks WHERE user_id = ?").bind(user_id).run()
async def create_task(self, user_id, title):
await self.env.D1.prepare("INSERT INTO tasks (user_id, title) VALUES (?, ?)").bind(user_id, title).run()
class Default(WorkerEntrypoint):
async def fetch(self, request):
return Response("Hello from my to do app")別の Worker で、AdminEntrypoint への Service binding を直接宣言できます。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "admin-app",
"services": [
{
"binding": "ADMIN",
"service": "todo-app",
"entrypoint": "AdminEntrypoint"
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "admin-app"
[[services]]
binding = "ADMIN"
service = "todo-app"
entrypoint = "AdminEntrypoint"export default {
async fetch(request, env) {
await env.ADMIN.createUser("aNewUser");
return new Response("Hello from admin app");
},
};from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
await self.env.ADMIN.create_user("aNewUser")
return Response("Hello from admin app")D1 の設定方法は、D1 ドキュメント を参照してください。
この To Do アプリの完全な例と、名前付きエントリポイントで作った Discord ボットは、GitHub の cloudflare/js-rpc-and-entrypoints-demo リポジトリ ↗ をクローンして試せます。