INTEGRITY Документация

Используйте класс RpcTarget для обработки метаданных Durable Object

При работе с Durable Objects вам потребуется получить доступ к имени, использованному при создании Durable Object, через idFromName(). Обычно это осмысленный идентификатор, отражающий назначение Durable Object, например ID пользователя, название комнаты или идентификатор ресурса.

Однако у текущей реализации есть ограничение: хотя вы можете создать Durable Object с .idFromName(name), вы не можете напрямую получить доступ к этому имени внутри Durable Object через this.ctx.id.name.

RpcTarget паттерн, показанный ниже, предлагает решение: он создаёт слой связи, который автоматически передаёт имя при каждом вызове метода. Это позволяет сохранить чистый API и при этом гарантирует, что Durable Object имеет доступ к собственному имени.

В зависимости от ваших потребностей метаданные можно временно хранить в RpcTarget класса, либо используйте хранилище Durable Object, чтобы сохранить метаданные на весь срок жизни объекта.

В этом примере метаданные Durable Object не сохраняются. В нём показано, как:

  1. Создайте RpcTarget класс
  2. Задайте метаданные Durable Object (в данном примере это идентификатор) в RpcTarget класс
  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>;