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 のメタデータを永続化しません。次の手順を示します。
RpcTargetクラスを作成するRpcTargetクラスに 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) {
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>;