عنوان:

‫Cloudflare Workers برای توسعه‌دهندگان ASP.NET Core؛ بخش سوم: ذخیره‌سازی و مدیریت حالت در Edge


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۵/۲۳ ۰۸:۱۰
آدرس: www.dntips.ir
تا به امروز، اپلیکیشن‌های ASP.NET Core ما برای مدیریت حالت و ذخیره‌سازی داده به شدت به سرور اصلی (Origin) متکی بوده‌اند. ما از SQL Server برای داده‌های رابطه‌ای، از Redis یا IMemoryCache برای کش، و از سرویس‌هایی مانند Azure Blob Storage یا Amazon S3 برای ذخیره‌سازی فایل‌ها استفاده می‌کردیم. در این بخش، با سه سرویس قدرتمند Cloudflare آشنا می‌شویم که به ما اجازه می‌دهند این وظایف را به لبه شبکه (Edge) منتقل کنیم و به عملکردی بی‌نظیر دست یابیم.

درس ۱: آشنایی با Cloudflare KV (Key-Value Store)

۱.۱- یک مقایسه آشنا: KV در مقابل Redis و Dictionary
تصور کنید یک Dictionary در #C یا یک نمونه Redis دارید، با این تفاوت که این دیکشنری به صورت خودکار در صدها دیتاسنتر در سراسر جهان توزیع شده است. این دقیقاً همان کاری است که Cloudflare KV انجام می‌دهد.
KV یک پایگاه داده Key-Value با کارایی بسیار بالا برای خواندن (Read) است. داده‌ای که شما در KV می‌نویسید، در تمام شبکه جهانی Cloudflare تکثیر می‌شود تا کاربران از نزدیک‌ترین نقطه به آن دسترسی داشته باشند.

۱.۲- ویژگی کلیدی: سازگاری نهایی (Eventual Consistency)
این مهم‌ترین نکته در مورد KV است.
  • خواندن (Read) بسیار سریع: خواندن داده از KV تقریباً آنی است، زیرا از نزدیک‌ترین کش Edge به کاربر خوانده می‌شود.
  • نوشتن (Write) با تأخیر: وقتی شما داده‌ای را در KV می‌نویسید، ممکن است تا ۶۰ ثانیه طول بکشد تا این تغییر در تمام نقاط جهان منتشر شود.
نتیجه: KV یک جایگزین برای پایگاه‌داده‌های تراکنشی (Transactional) مانند SQL Server نیست. اما برای موارد زیر فوق‌العاده است:
  • داده‌های پیکربندی و Feature Flags: مقادیری که به ندرت تغییر می‌کنند ولی به کرات خوانده می‌شوند.
  • نقشه‌های ریدایرکت (Redirect Maps): نگهداری آدرس‌های کوتاه و بلند.
  • اطلاعات پروفایل کاربری: ذخیره تنظیمات شخصی‌سازی کاربر.

۱.۳- پروژه عملی: ساخت یک سرویس کوتاه‌کننده لینک (URL Shortener)
این پروژه کلاسیک، بهترین مثال برای نمایش قدرت KV است.
ساخت یک Namespace در KV:
  • Namespace در KV مانند یک جدول یا یک دیتابیس مجزاست.
wrangler kv:namespace create "URL_STORE"

اتصال (Binding) در wrangler.toml:
  • Wrangler خروجی دستور قبل را به شما می‌دهد. آن را در فایل wrangler.toml کپی کنید تا Worker شما به این Namespace دسترسی داشته باشد.
[[kv_namespaces]]
binding = "URL_STORE"
id = "..."
preview_id = "..."
حالا می‌توانید از طریق env.URL_STORE به آن دسترسی پیدا کنید.

کد Worker:
  • یک POST request یک URL بلند را می‌گیرد، یک کد کوتاه تصادفی تولید می‌کند و آن را در KV ذخیره می‌کند.
  • یک GET request با کد کوتاه، URL بلند را از KV می‌خواند و کاربر را به آن ریدایرکت می‌کند.
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (request.method === 'POST') {
      // ۱. دریافت URL بلند از بدنه درخواست
      const longUrl = await request.text();
      if (!longUrl) return new Response('Missing URL in body', { status: 400 });

      // ۲. تولید یک کد کوتاه تصادفی
      const shortCode = Math.random().toString(36).substring(2, 8);

      // ۳. ذخیره در KV
      await env.URL_STORE.put(shortCode, longUrl);

      return new Response(`Your short URL is: ${url.origin}/${shortCode}`);
    }

    if (request.method === 'GET') {
      // ۴. دریافت کد کوتاه از مسیر
      const shortCode = url.pathname.slice(1);
      if (!shortCode) return new Response('Homepage - POST a URL to shorten it.', { status: 404 });

      // ۵. خواندن از KV
      const longUrl = await env.URL_STORE.get(shortCode);

      if (!longUrl) {
        return new Response('Short URL not found', { status: 404 });
      }

      // ۶. ریدایرکت کاربر
      return Response.redirect(longUrl, 302);
    }

    return new Response('Method not allowed', { status: 405 });
  },
};

interface Env {
  URL_STORE: KVNamespace;
}

درس ۲: کار با Cloudflare R2 (Object Storage)

۲.۱- یک مقایسه آشنا: R2 در مقابل Azure Blob Storage / Amazon S3
R2 معادل مستقیم سرویس‌های ذخیره‌سازی آبجکت مانند S3 و Blob Storage است. شما از آن برای ذخیره فایل‌های حجیم و بدون ساختار (مانند تصاویر، ویدیوها، فایل‌های Zip و بکاپ‌ها) استفاده می‌کنید.

۲.۲- مزیت رقابتی اصلی: صفر هزینه خروج داده (Zero Egress Fees)
بزرگترین مزیت R2 که آن را متمایز می‌کند، مدل قیمت‌گذاری آن است. سرویس‌هایی مانند S3، هزینه‌ای برای خروج داده (Egress) دریافت می‌کنند؛ یعنی هر بار که کاربری فایلی را از سرور شما دانلود می‌کند، شما هزینه آن پهنای باند را پرداخت می‌کنید.
در R2، هزینه خروج داده صفر است! این یعنی شما می‌توانید بدون نگرانی از هزینه‌های گزاف پهنای باند، فایل‌های خود را در اختیار کاربران سراسر جهان قرار دهید.

۲.۳- پروژه عملی: ساخت یک API برای آپلود فایل
ساخت یک Bucket در R2:
  • Bucket در R2 معادل یک کانتینر در Blob Storage است.
wrangler r2 bucket create "USER_UPLOADS"
اتصال (Binding) در wrangler.toml:
[[r2_buckets]]
binding = "USER_UPLOADS"
bucket_name = "USER_UPLOADS"
preview_bucket_name = "USER_UPLOADS"

کد Worker:
  • یک PUT request فایل را در بدنه خود دریافت کرده و مستقیماً در R2 ذخیره می‌کند.
  • یک GET request فایل را از R2 می‌خواند و برای کاربر ارسال می‌کند.
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const objectKey = url.pathname.slice(1);

    if (request.method === 'PUT') {
      // ۱. آپلود مستقیم بدنه درخواست در R2
      await env.USER_UPLOADS.put(objectKey, request.body);
      return new Response(`Object ${objectKey} uploaded successfully!`);
    }

    if (request.method === 'GET') {
      // ۲. دریافت آبجکت از R2
      const object = await env.USER_UPLOADS.get(objectKey);

      if (object === null) {
        return new Response('Object Not Found', { status: 404 });
      }

      // ۳. ساخت پاسخ با هدرهای مناسب
      const headers = new Headers();
      object.writeHttpMetadata(headers);
      headers.set('etag', object.httpEtag);

      // ۴. ارسال بدنه آبجکت (فایل) به کاربر
      return new Response(object.body, { headers });
    }

    return new Response('Method Not Allowed', { status: 405 });
  },
};

interface Env {
  USER_UPLOADS: R2Bucket;
}

درس ۳: مدیریت Cache پیشرفته با Cache API

۳.۱- یک مقایسه آشنا: Cache API در مقابل Response Caching
شما با Middleware مربوط به Response Caching در ASP.NET Core آشنا هستید که هدرهای Cache-Control را به پاسخ‌ها اضافه می‌کند. کلادفلر به صورت پیش‌فرض نیز بسیاری از محتوا را بر اساس همین هدرها کش می‌کند.
Cache API به شما این امکان را می‌دهد که به صورت برنامه‌نویسی شده و دقیق، کش Edge کلادفلر را مدیریت کنید. شما می‌توانید تصمیم بگیرید چه چیزی، برای چه مدتی و چگونه کش شود. این ابزار برای کش کردن پاسخ‌های دینامیک API که به ندرت تغییر می‌کنند، فوق‌العاده است.

۳.۲- الگوی "Cache-First"
این یک الگوی رایج و بسیار کارآمد است:
  • ابتدا کش را برای درخواست بررسی کن.
  • اگر پاسخ در کش بود (Cache Hit)، همان را برگردان.
  • اگر نبود (Cache Miss)، درخواست را به سرور اصلی بفرست.
  • پاسخ را از سرور اصلی بگیر، آن را در کش قرار بده و سپس برای کاربر ارسال کن.

۳.۳- کد پیاده‌سازی:
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // ۱. دسترسی به کش پیش‌فرض Edge
    const cache = caches.default;
    
    // ۲. بررسی وجود درخواست در کش
    let response = await cache.match(request);
    
    if (response) {
      console.log('Cache HIT!');
      // ۳. اگر در کش بود، همان را برگردان
      return response;
    }
    console.log('Cache MISS!');

    // ۴. اگر در کش نبود، درخواست را به سرور اصلی بفرست
    response = await fetch(request);

    // ۵. یک کپی قابل تغییر از پاسخ بساز تا بتوانیم هدرها را تغییر دهیم
    response = new Response(response.body, response);
    
    // ۶. تنظیم هدر برای کش شدن در شبکه کلادفلر به مدت ۶۰ ثانیه
    response.headers.set('Cache-Control', 's-maxage=60');
    
    // ۷. پاسخ را در کش ذخیره کن. ctx.waitUntil باعث می‌شود این کار در پس‌زمینه انجام شود
    // و کاربر منتظر اتمام آن نماند.
    ctx.waitUntil(cache.put(request, response.clone()));
    
    // ۸. پاسخ را به کاربر برگردان
    return response;
  },
};

جمع‌بندی بخش سوم:
شما اکنون با سه ابزار کلیدی برای مدیریت داده در Edge آشنا شدید. با KV می‌توانید داده‌های پیکربندی و اطلاعات کم‌حجم را با سرعت نور در سراسر جهان بخوانید. با R2 می‌توانید فایل‌های خود را بدون نگرانی از هزینه‌های پهنای باند ذخیره و توزیع کنید. و با Cache API می‌توانید بار روی سرور اصلی خود را با کش کردن هوشمندانه پاسخ‌های داینامیک به شدت کاهش دهید. این ابزارها به شما قدرت ساخت اپلیکیشن‌هایی را می‌دهند که پیش از این ممکن نبود. در بخش نهایی، به سراغ مباحث پیشرفته‌تر خواهیم رفت.