Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

RpcTarget クラスで Durable Object のメタデータを扱う

RpcTarget を使い、Durable Object 内から名前にアクセスします。

最終更新 Markdown で表示Agent セットアップ

Durable Objects を使うときは、idFromName() で Durable Object を作成したときに使った名前へアクセスする必要があります。この名前は通常、Durable Object が担当する対象を表す意味のある識別子です(ユーザー ID、ルーム名、リソース識別子など)。

ただし、現在の実装には制限があります。.idFromName(name) で Durable Object を作れても、Durable Object 内から this.ctx.id.name でその名前へ直接アクセスすることはできません。

次に示す RpcTarget パターンは、各メソッド呼び出しに名前を自動で載せる通信レイヤーを作ることで、この問題を解消します。API はすっきりしたまま、Durable Object が自分の名前へアクセスできます。

必要に応じて、メタデータを RpcTarget クラスに一時保存するか、Durable Object のストレージへ保存してオブジェクトの存続期間中保持するかを選べます。

この例では、Durable Object のメタデータを永続化しません。次の手順を示します。

  1. RpcTarget クラスを作成する
  2. RpcTarget クラスに Durable Object のメタデータ(この例では識別子)を設定する
  3. メタデータを Durable Object のメソッドへ渡す
  4. 使い終わったら RpcTarget クラスをクリーンアップする
import { DurableObject, RpcTarget } from "cloudflare:workers";

//  * Create an RpcDO class that extends RpcTarget
//  * Use this class to set the Durable Object metadata
//  * Pass the metadata in the Durable Object methods
//  * @param mainDo - The main Durable Object class
//  * @param doIdentifier - The identifier of the Durable Object

export class RpcDO extends RpcTarget {
	constructor(
		private mainDo: MyDurableObject,
		private doIdentifier: string,
	) {
		super();
	}

	//  * Pass the user's name to the Durable Object method
	//  * @param userName - The user's name to pass to the Durable Object method

	async computeMessage(userName: string): Promise<string> {
		// Call the Durable Object method and pass the user's name and the Durable Object identifier
		return this.mainDo.computeMessage(userName, this.doIdentifier);
	}

	//  * Call the Durable Object method without using the Durable Object identifier
	//  * @param userName - The user's name to pass to the Durable Object method

	async simpleGreeting(userName: string) {
		return this.mainDo.simpleGreeting(userName);
	}
}

//  * Create a Durable Object class
//  * You can use the RpcDO class to set the Durable Object metadata

export class MyDurableObject extends DurableObject<Env> {
	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
	}

	//  * Initialize the RpcDO class
	//  * You can set the Durable Object metadata here
	//  * It returns an instance of the RpcDO class
	//  * @param doIdentifier - The identifier of the Durable Object

	async setMetaData(doIdentifier: string) {
		return new RpcDO(this, doIdentifier);
	}

	//  * Function that computes a greeting message using the user's name and DO identifier
	//  * @param userName - The user's name to include in the greeting
	//  * @param doIdentifier - The identifier of the Durable Object

	async computeMessage(
		userName: string,
		doIdentifier: string,
	): Promise<string> {
		console.log({
			userName: userName,
			durableObjectIdentifier: doIdentifier,
		});
		return `Hello, ${userName}! The identifier of this DO is ${doIdentifier}`;
	}

	//  * Function that is not in the RpcTarget
	//  * Not every function has to be in the RpcTarget

	private async notInRpcTarget() {
		return "This is not in the RpcTarget";
	}

	//  * Function that takes the user's name and does not use the Durable Object identifier
	//  * @param userName - The user's name to include in the greeting

	async simpleGreeting(userName: string) {
		// Call the private function that is not in the RpcTarget
		console.log(this.notInRpcTarget());

		return `Hello, ${userName}! This doesn't use the DO identifier.`;
	}
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		let id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName(
			new URL(request.url).pathname,
		);
		let stub = env.MY_DURABLE_OBJECT.get(id);

		//  * Set the Durable Object metadata using the RpcTarget
		//  * Notice that no await is needed here

		const rpcTarget = stub.setMetaData(id.name ?? "default");

		// Call the Durable Object method using the RpcTarget.
		// The DO identifier is passed in the RpcTarget
		const greeting = await rpcTarget.computeMessage("world");

		// Call the Durable Object method that does not use the Durable Object identifier
		const simpleGreeting = await rpcTarget.simpleGreeting("world");

		// Clean up the RpcTarget.
		try {
			(await rpcTarget)[Symbol.dispose]?.();
			console.log("RpcTarget cleaned up.");
		} catch (e) {
			console.error({
				message: "RpcTarget could not be cleaned up.",
				error: String(e),
				errorProperties: e,
			});
		}

		return new Response(greeting, { status: 200 });
	},
} satisfies ExportedHandler<Env>;

この例では、Durable Object のメタデータを永続化します。前の例と同じ手順ですが、識別子を Durable Object のストレージへ保存するため、RpcTarget 経由で渡す必要はありません。

import { DurableObject, RpcTarget } from "cloudflare:workers";

//  * Create an RpcDO class that extends RpcTarget
//  * Use this class to set the Durable Object metadata
//  * Pass the metadata in the Durable Object methods
//  * @param mainDo - The main Durable Object class
//  * @param doIdentifier - The identifier of the Durable Object

export class RpcDO extends RpcTarget {
	constructor(
		private mainDo: MyDurableObject,
		private doIdentifier: string,
	) {
		super();
	}

	//  * Pass the user's name to the Durable Object method
	//  * @param userName - The user's name to pass to the Durable Object method

	async computeMessage(userName: string): Promise<string> {
		// Call the Durable Object method and pass the user's name and the Durable Object identifier
		return this.mainDo.computeMessage(userName, this.doIdentifier);
	}

	//  * Call the Durable Object method without using the Durable Object identifier
	//  * @param userName - The user's name to pass to the Durable Object method

	async simpleGreeting(userName: string) {
		return this.mainDo.simpleGreeting(userName);
	}
}

//  * Create a Durable Object class
//  * You can use the RpcDO class to set the Durable Object metadata

export class MyDurableObject extends DurableObject<Env> {
	constructor(ctx: DurableObjectState, env: Env) {
		super(ctx, env);
	}

	//  * Initialize the RpcDO class
	//  * You can set the Durable Object metadata here
	//  * It returns an instance of the RpcDO class
	//  * @param doIdentifier - The identifier of the Durable Object

	async setMetaData(doIdentifier: string) {
		// Use DO storage to store the Durable Object identifier
		await this.ctx.storage.put("doIdentifier", doIdentifier);
		return new RpcDO(this, doIdentifier);
	}

	//  * Function that computes a greeting message using the user's name and DO identifier
	//  * @param userName - The user's name to include in the greeting

	async computeMessage(userName: string): Promise<string> {
		// Get the DO identifier from storage
		const doIdentifier = await this.ctx.storage.get("doIdentifier");
		console.log({
			userName: userName,
			durableObjectIdentifier: doIdentifier,
		});
		return `Hello, ${userName}! The identifier of this DO is ${doIdentifier}`;
	}

	//  * Function that is not in the RpcTarget
	//  * Not every function has to be in the RpcTarget

	private async notInRpcTarget() {
		return "This is not in the RpcTarget";
	}

	//  * Function that takes the user's name and does not use the Durable Object identifier
	//  * @param userName - The user's name to include in the greeting

	async simpleGreeting(userName: string) {
		// Call the private function that is not in the RpcTarget
		console.log(this.notInRpcTarget());

		return `Hello, ${userName}! This doesn't use the DO identifier.`;
	}
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		let id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName(
			new URL(request.url).pathname,
		);
		let stub = env.MY_DURABLE_OBJECT.get(id);

		//  * Set the Durable Object metadata using the RpcTarget
		//  * Notice that no await is needed here

		const rpcTarget = stub.setMetaData(id.name ?? "default");

		// Call the Durable Object method using the RpcTarget.
		// The DO identifier is stored in the Durable Object's storage
		const greeting = await rpcTarget.computeMessage("world");

		// Call the Durable Object method that does not use the Durable Object identifier
		const simpleGreeting = await rpcTarget.simpleGreeting("world");

		// Clean up the RpcTarget.
		try {
			(await rpcTarget)[Symbol.dispose]?.();
			console.log("RpcTarget cleaned up.");
		} catch (e) {
			console.error({
				message: "RpcTarget could not be cleaned up.",
				error: String(e),
				errorProperties: e,
			});
		}

		return new Response(greeting, { status: 200 });
	},
} satisfies ExportedHandler<Env>;

役に立ちましたか?