Pular para o conteúdo

Build a counter

Build a counter using Durable Objects and Workers with RPC methods.

Atualizado em Ver como Markdown

This example shows how to build a counter using Durable Objects and Workers with RPC methods that can print, increment, and decrement a name provided by the URL query string parameter, for example, ?name=A.

import { DurableObject } from "cloudflare:workers";

// Worker
export default {
	async fetch(request, env) {
		let url = new URL(request.url);
		let name = url.searchParams.get("name");
		if (!name) {
			return new Response(
				"Select a Durable Object to contact by using" +
					" the `name` URL query string parameter, for example, ?name=A",
			);
		}

		// A stub is a client Object used to send messages to the Durable Object.
		let stub = env.COUNTERS.getByName(name);

		// Send a request to the Durable Object using RPC methods, then await its response.
		let count = null;
		switch (url.pathname) {
			case "/increment":
				count = await stub.increment();
				break;
			case "/decrement":
				count = await stub.decrement();
				break;
			case "/":
				// Serves the current value.
				count = await stub.getCounterValue();
				break;
			default:
				return new Response("Not found", { status: 404 });
		}

		return new Response(`Durable Object '${name}' count: ${count}`);
	},
};

// Durable Object
export class Counter extends DurableObject {
	async getCounterValue() {
		let value = (await this.ctx.storage.get("value")) || 0;
		return value;
	}

	async increment(amount = 1) {
		let value = (await this.ctx.storage.get("value")) || 0;
		value += amount;
		// You do not have to worry about a concurrent request having modified the value in storage.
		// "input gates" will automatically protect against unwanted concurrency.
		// Read-modify-write is safe.
		await this.ctx.storage.put("value", value);
		return value;
	}

	async decrement(amount = 1) {
		let value = (await this.ctx.storage.get("value")) || 0;
		value -= amount;
		await this.ctx.storage.put("value", value);
		return value;
	}
}
import { DurableObject } from "cloudflare:workers";

export interface Env {
	COUNTERS: DurableObjectNamespace<Counter>;
}

// Worker
export default {
	async fetch(request, env) {
		let url = new URL(request.url);
		let name = url.searchParams.get("name");
		if (!name) {
			return new Response(
				"Select a Durable Object to contact by using" +
					" the `name` URL query string parameter, for example, ?name=A",
			);
		}

		// A stub is a client Object used to send messages to the Durable Object.
		let stub = env.COUNTERS.get(name);

		let count = null;
		switch (url.pathname) {
			case "/increment":
				count = await stub.increment();
				break;
			case "/decrement":
				count = await stub.decrement();
				break;
			case "/":
				// Serves the current value.
				count = await stub.getCounterValue();
				break;
			default:
				return new Response("Not found", { status: 404 });
		}

		return new Response(`Durable Object '${name}' count: ${count}`);
	},
} satisfies ExportedHandler<Env>;

// Durable Object
export class Counter extends DurableObject {
	async getCounterValue() {
		let value = (await this.ctx.storage.get("value")) || 0;
		return value;
	}

	async increment(amount = 1) {
		let value: number = (await this.ctx.storage.get("value")) || 0;
		value += amount;
		// You do not have to worry about a concurrent request having modified the value in storage.
		// "input gates" will automatically protect against unwanted concurrency.
		// Read-modify-write is safe.
		await this.ctx.storage.put("value", value);
		return value;
	}

	async decrement(amount = 1) {
		let value: number = (await this.ctx.storage.get("value")) || 0;
		value -= amount;
		await this.ctx.storage.put("value", value);
		return value;
	}
}
from workers import DurableObject, Response, WorkerEntrypoint
from urllib.parse import urlparse, parse_qs

# Worker
class Default(WorkerEntrypoint):
	async def fetch(self, request):
		parsed_url = urlparse(request.url)
		query_params = parse_qs(parsed_url.query)
		name = query_params.get('name', [None])[0]

		if not name:
			return Response(
				"Select a Durable Object to contact by using"
				+ " the `name` URL query string parameter, for example, ?name=A"
			)

		# A stub is a client Object used to send messages to the Durable Object.
		stub = self.env.COUNTERS.getByName(name)

		# Send a request to the Durable Object using RPC methods, then await its response.
		count = None

		if parsed_url.path == "/increment":
			count = await stub.increment()
		elif parsed_url.path == "/decrement":
			count = await stub.decrement()
		elif parsed_url.path == "" or parsed_url.path == "/":
			# Serves the current value.
			count = await stub.getCounterValue()
		else:
			return Response("Not found", status=404)

		return Response(f"Durable Object '{name}' count: {count}")

# Durable Object
class Counter(DurableObject):
	def __init__(self, ctx, env):
		super().__init__(ctx, env)

	async def getCounterValue(self):
		value = await self.ctx.storage.get("value")
		return value if value is not None else 0

	async def increment(self, amount=1):
		value = await self.ctx.storage.get("value")
		value = (value if value is not None else 0) + amount
		# You do not have to worry about a concurrent request having modified the value in storage.
		# "input gates" will automatically protect against unwanted concurrency.
		# Read-modify-write is safe.
		await self.ctx.storage.put("value", value)
		return value

	async def decrement(self, amount=1):
		value = await self.ctx.storage.get("value")
		value = (value if value is not None else 0) - amount
		await self.ctx.storage.put("value", value)
		return value

Finally, configure your Wrangler file to include a Durable Object binding and migration based on the namespace and class name chosen previously.

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "my-counter",
	"main": "src/index.ts",
	"durable_objects": {
		"bindings": [
			{
				"name": "COUNTERS",
				"class_name": "Counter"
			}
		]
	},
	"migrations": [
		{
			"tag": "v1",
			"new_sqlite_classes": [
				"Counter"
			]
		}
	]
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-counter"
main = "src/index.ts"

[[durable_objects.bindings]]
name = "COUNTERS"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Counter" ]