Serverless Functions بخشی از اجرای Backend را روی زیرساخت مدیریتشده قرار میدهند. توسعهدهنده معمولاً ماشین و فرایند Server را به همان شکل سنتی اداره نمیکند، اما هنوز API، مجوز، داده، خطا و هزینه را طراحی میکند. عبارت «بدون سرور و بدون مدیریت» این مسئولیتها را پنهان میکند. سرور وجود دارد؛ مرز مدیریت زیرساخت تغییر کرده است. ساخت یک Endpoint از فرانتاند به معنی آماده شدن همه مهارتها و عملیات یک Backend قابل نگهداری نیست.
برای مثال فرضی، یک سایت به پردازش فرم و ارتباط با سرویس داخلی نیاز دارد. Function میتواند ورودی را دریافت، اعتبارسنجی و با Credential سمت Server عملیات لازم را انجام دهد. مرورگر نباید همان Credential را داشته باشد. اما ارسال فرم، ذخیره درخواست و ارسال ایمیل سه اقدام متفاوتاند. اگر ذخیره موفق و ایمیل ناموفق شد، نتیجه و روش پیگیری باید مشخص باشد. Serverless فقط محل اجرای این تصمیمها را فراهم میکند و قرارداد محصول را به جای تیم نمینویسد.
Runtimeها و مدل اجرا یکسان نیستند
معرفی AWS Lambda اجرای کد در زیرساخت مدیریتشده و ارتباط با رویدادها را توضیح میدهد. مستندات Vercel Functions مدل ارائه Function در آن پلتفرم را معرفی میکند. راهنمای Workers نیز مسیر Runtime و توسعه خود را دارد. این نامها را به عنوان سه جایگزین کاملاً یکسان برای هر کد Node.js معرفی نکنید؛ APIها، محدودیتها و روش استقرار تفاوت دارند.
پیش از انتخاب، وابستگیها را فهرست کنید. Libraryای که به فایلسیستم محلی پایدار، Process خاص یا API ناسازگار تکیه دارد ممکن است بدون تغییر اجرا نشود. سازگاری Node نیز همیشه به معنی حضور همه رفتارهای یک ماشین عمومی نیست. محل اجرای Function و محل پایگاه داده روی تأخیر اثر دارند؛ نزدیک بودن به کاربر، نزدیک بودن به داده را تضمین نمیکند. مقاله Hono و Edge درباره هماهنگی Framework و Runtime زمینه دارد.
State پایدار را در حافظه Function فرض نکنید
متغیر ماژول میتواند در بعضی اجراها باقی بماند، اما منبع معتبر ثبت سفارش یا شمارنده مشترک همه درخواستها نیست. چند Instance میتوانند همزمان کار کنند و محیط اجرا نیز ممکن است جایگزین شود. Cache موقت با داده پایدار فرق دارد. اگر داده باید پس از اجرای بعدی یا تغییر منطقه باقی بماند، از منبع مناسب با قرارداد مشخص استفاده کنید. حفظ State در حافظه برای راحتی نمونه نباید به معماری ثبت داده واقعی تبدیل شود.
پایگاه داده به Connection، تراکنش و حدود همزمانی نیاز دارد. افزایش Instanceها میتواند فشار بیشتری روی منبع داده ایجاد کند. Pooling یا سرویس واسط ممکن است لازم باشد و انتخاب آن به Runtime و Driver مربوط است. Serverless مقیاسپذیر بودن Endpoint را با ظرفیت نامحدود Database یکسان نکنید. مقاله Turso و داده توزیعشده برای بعضی تصمیمهای محل و همگامسازی زمینه میدهد، اما انتخاب پایگاه داده همچنان با نیاز واقعی انجام میشود.
ورودی، Secret و مجوز را جدا کنترل کنید
Browser Client میتواند داده نامعتبر یا درخواست دستکاریشده بفرستد. اعتبارسنجی فرم در Client، بررسی Server را حذف نمیکند. نوع محتوا، حد اندازه و شکل پیام مشخص باشد و عملیات فقط با مجوز مناسب انجام شود. مستند Secrets در Workers روش نگهداری مقدار حساس سمت پلتفرم را توضیح میدهد. Secret نباید در Bundle مرورگر، خروجی پاسخ یا Log عمومی قرار گیرد. مخفی بودن نام مسیر نیز کنترل دسترسی نیست.
CORS درباره دسترسی مرورگر به پاسخ است و به تنهایی Authentication یا جلوگیری از همه درخواستها نیست. Webhook هم باید با سازوکار معتبر ارائهدهنده بررسی شود؛ داشتن URL خاص کافی نیست. اگر امضای Webhook به Body خام وابسته است، تبدیل زودهنگام داده میتواند بررسی را خراب کند. مقاله قرارداد API درباره معنی خطا و Idempotency توضیح دارد. این قواعد به معماری Serverless هم تعلق دارند و با انتخاب Host خودکار حذف نمیشوند.
نمونه کامل پیشنمایش فرم در Worker محلی
نمونه زیر فقط ورودی را بررسی و پیشنمایش پاسخ میسازد؛ داده را ذخیره نمیکند و ایمیل نمیفرستد. Node.js پشتیبانیشده Wrangler، در این مسیر یک نسخه جاری شاخه 22، و npm لازماند. در پوشه آموزشی npm init -y و npm install --save-dev wrangler@4 را اجرا کنید. فایل تنظیمات با نام wrangler.jsonc و کد با نام worker.js در همان ریشه قرار میگیرند. سپس npx wrangler dev --local را اجرا و نشانی محلی اعلامشده ابزار را باز کنید. نصب اولیه به دریافت Package نیاز دارد.
{"name":"form-preview-demo","main":"worker.js","compatibility_date":"2026-10-13"}
در تنظیمات، main به همان فایل واقعی اشاره میکند و compatibility_date رفتار Runtime مورد استفاده را مشخص میکند. تغییر تاریخ باید با بررسی رفتار برنامه انجام شود و صرف تازهتر بودن آن مزیت محصولی تضمین نمیکند. این مسیر برای توسعه محلی است و فرمان انتشار عمومی در آن نیامده است. اگر به منبع واقعی پلتفرم Binding اضافه شود، محدوده محلی و Remote آن باید جدا مشخص باشد؛ نمونه حاضر هیچ Binding بیرونی ندارد.
const page=`<!doctype html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>پیشنمایش Worker</title><style>body{font:1rem/1.8 Tahoma,sans-serif;max-width:40rem;margin:2rem auto;padding:1rem}label{display:block}input,textarea,button{font:inherit;padding:.5rem;margin:.3rem}input:focus-visible,textarea:focus-visible,button:focus-visible{outline:3px solid #111;outline-offset:3px}</style></head><body><h1>پیشنمایش فرم</h1><p>فقط ورودی بررسی میشود؛ ذخیره یا ایمیل وجود ندارد. داده واقعی خصوصی وارد نکنید.</p><form id="form"><label for="name">نام آموزشی</label><input id="name" required maxlength="80"><label for="message">پیام آموزشی</label><textarea id="message" required maxlength="400"></textarea><button id="submit">پیشنمایش</button></form><p id="status" role="status"></p><script>const form=document.getElementById('form'),button=document.getElementById('submit'),status=document.getElementById('status');form.onsubmit=async event=>{event.preventDefault();button.disabled=true;status.textContent='در حال بررسی';try{const response=await fetch('/api/preview',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:document.getElementById('name').value,message:document.getElementById('message').value})});const result=await response.json();if(!response.ok)throw new Error(result.error||'بررسی ممکن نشد.');status.textContent='پیشنمایش برای '+result.name+': '+result.length+' کاراکتر. ذخیره نشده است.'}catch(error){status.textContent=error.message}finally{button.disabled=false}};</script></body></html>`;
function json(value,status=200){return Response.json(value,{status,headers:{'Cache-Control':'no-store'}})}
export default{async fetch(request){const path=new URL(request.url).pathname;if(path==='/'&&request.method==='GET')return new Response(page,{headers:{'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-store'}});if(path!=='/api/preview')return json({error:'مسیر موجود نیست.'},404);if(request.method!=='POST')return json({error:'POST لازم است.'},405);if(!request.headers.get('content-type')?.startsWith('application/json'))return json({error:'JSON لازم است.'},415);const raw=await request.text();if(raw.length>2048)return json({error:'متن درخواست بیش از حد است.'},413);let data;try{data=JSON.parse(raw)}catch{return json({error:'JSON معتبر نیست.'},400)}if(!data||typeof data.name!=='string'||typeof data.message!=='string')return json({error:'نام و پیام رشتهای لازم است.'},400);const name=data.name.trim(),message=data.message.trim();if(!name||name.length>80||!message||message.length>400)return json({error:'نام تا ۸۰ و پیام تا ۴۰۰ کاراکتر غیرخالی لازم است.'},400);return json({name,length:message.length,stored:false})}};
GET ریشه صفحه فرم را میدهد و POST به /api/preview داده JSON را بررسی میکند. نام و پیام باید رشته غیرخالی با حد مشخص باشند. پاسخ فقط نام و طول متن را برای پیشنمایش برمیگرداند. در UI، نتیجه با textContent نمایش داده میشود و وضعیت انتظار و خطا جدا است. کنترل حد متن پس از خواندن Body در این نمونه، جای کنترل حجم ورودی در لایه زیرساخت یک سرویس عمومی را نمیگیرد؛ نمونه برای آموزش قرارداد کوچک نوشته شده است.
فرم از همان Origin درخواست میدهد و به CORS باز نیاز ندارد. هیچ API Key داخل HTML قرار نگرفته است. بعد از پاسخ، پیام «ذخیره شد» نمایش داده نمیشود چون ذخیرهای انجام نشده. برای تبدیل این مسیر به دریافت درخواست واقعی باید منبع پایدار، سیاست حریم خصوصی، مجوز لازم و روش رسیدگی به ارسال تکراری اضافه شود. افزودن یک فراخوانی ایمیل بدون این قراردادها، نسخه کامل فرم محصولی نمیسازد.
زمان، هزینه و محدودیت را با سناریو بخوانید
حدود Workers برای منابع و مدل استفاده مشخصاند و ممکن است با طرح یا نوع قابلیت فرق کنند. در ارائهدهندگان دیگر نیز Duration، Memory، CPU، اندازه پیام و Connection محدودیت دارند. CPU Time با زمان انتظار شبکه یک معیار نیست. قیمت Pay-per-use هم کل هزینه نیست؛ Storage، خروجی داده، Log و سرویسهای وابسته میتوانند اضافه شوند. بدون سناریوی مصرف، ادعای همیشه ارزانتر بودن قابل دفاع نیست.
Cold Start، گرم بودن محیط و شکل Concurrency به پلتفرم و پیکربندی مربوطاند. یک زمان ثابت برای همه Functionها اعلام نکنید. دریافت Runtime، وابستگیها و منبع داده میتواند در تأخیر نقش داشته باشد. برای تجربه کاربر، Timeout و راه تلاش دوباره مهماند. مقاله طراحی فرم درباره حفظ متن و وضعیت ارسال توضیح دارد. اجرای محلی موفق نیز به تنهایی رفتار شبکه و بار محصولی را تضمین نمیکند.
Retry و پردازش پسزمینه قرارداد میخواهند
رویداد یا درخواست ممکن است دوباره برسد. راهنمای Lambda به Idempotency و طراحی متناسب با مدل اجرا میپردازد. برای تغییر داده، شناسه عملیات و سیاست تکرار روشن باشد. اگر Client پس از Timeout دوباره ارسال کرد، Server باید بداند عملیات قبلی انجام شده یا نه. ثبت دوباره سفارش نباید فقط به دلیل قطع پاسخ رخ دهد. پاسخ موفق HTTP هم باید با تعهد واقعی ذخیره یا صفبندی هماهنگ باشد.
کار طولانی شاید به صف یا Workflow نیاز داشته باشد. ادامه یک Promise پس از پایان پاسخ بدون توجه به مدل پلتفرم، تضمین انجام کار نیست. اگر پاسخ میگوید «در صف قرار گرفت»، شناسه و راه پیگیری لازم است؛ اگر میگوید «انجام شد»، کار باید واقعاً به همان تعهد رسیده باشد. Observability شامل زمان، خطا و شناسه درخواست کمک میکند، اما متن حساس همه درخواستها نباید صرفاً برای راحتی Debug نگهداری شود.
تحویل را با مرز مسئولیت تمام کنید
در تحویل، Runtime، Dependencyها، Secretها، منبع پایدار و روش خطا را ثبت کنید. مشخص باشد کدام داده در Client، کدام در Function و کدام در Storage معتبر است. مقاله TypeScript با React برای مدل State رابط مفید است. شروع مناسب یک Endpoint کوچک با قرارداد روشن است: ورودی را محدود، نتیجه را صادقانه و مسیر تکرار را قابل پیگیری کنید. Serverless هزینه اداره بعضی زیرساختها را تغییر میدهد و مسئولیت طراحی Backend همچنان با تیم میماند.
هنوز دیدگاهی ثبت نشده
اولین نفری باشید که نظر میدهد!