جنگِ متدها: بررسی دقیق تفاوت PUT و PATCH در درخواستهای AJAX
اگرچه هر دو متد برای "ویرایش" اطلاعات استفاده میشوند، اما فلسفه، عملکرد و تاثیر آنها بر روی سرور و پهنای باند کاملاً متفاوت است. در این مقاله، ما به عمق این تفاوتها خواهیم رفت.
۱. تعریف بنیادین: این دو متد چه میکنند؟
قبل از ورود به بحثهای فنی، بیایید با یک مثال ساده غیر فنی شروع کنیم. فرض کنید شما یک پرونده فیزیکی از یک کارمند دارید که شامل نام، نام خانوادگی، آدرس و شماره تلفن است. حال این کارمند آدرس خانه خود را تغییر داده است.
-
رویکرد PUT (جایگزینی کامل): شما پرونده قدیمی را دور میاندازید. یک فرم کاملاً جدید برمیدارید، نام، نام خانوادگی (که تغییر نکرده)، شماره تلفن (که تغییر نکرده) و آدرس جدید را در آن مینویسید و جایگزین پرونده قبلی میکنید.
-
رویکرد PATCH (وصله کردن): شما پرونده قدیمی را نگه میدارید، لاک غلطگیر برمیدارید، فقط قسمت آدرس را پاک کرده و آدرس جدید را مینویسید. سایر اطلاعات دستنخورده باقی میمانند.
متد PUT: جایگزینی کامل (Full Replacement)
متد PUT به معنای "قراردادن" است. طبق استاندارد RFC 7231، این متد درخواستی است که در آن Payload (دادههای ارسالی) باید نمایانگر کامل منبع (Resource) جدید باشد.
-
منطق: "این شیء جدید را بگیر و جایگزین شیء قبلی در این آدرس کن."
-
نکته مهم: اگر شما در بدنه درخواست PUT فقط فیلدی که تغییر کرده را بفرستید و بقیه فیلدها را نفرستید، سرور (در یک پیادهسازی استاندارد) باید بقیه فیلدها را null یا حذف کند، زیرا فرض بر این است که آنچه فرستادهاید، کلِ موجودیت جدید است.
متد PATCH: بروزرسانی جزئی (Partial Modification)
متد PATCH به معنای "وصله کردن" است. این متد برای اعمال تغییرات جزئی به یک منبع استفاده میشود.
-
منطق: "این لیست تغییرات را روی شیء موجود اعمال کن."
-
نکته مهم: در اینجا شما فقط فیلدهایی را ارسال میکنید که نیاز به تغییر دارند.
۲. مقایسه فنی و تخصصی
در جدول زیر تفاوتهای کلیدی را به صورت خلاصه مشاهده میکنید و سپس به تشریح آنها میپردازیم:
| ویژگی | PUT | PATCH |
| نوع عملیات | جایگزینی کامل (Replace) | تغییر جزئی (Modify) |
| پهنای باند (Bandwidth) | بالا (ارسال کل دادهها) | پایین (ارسال فقط تغییرات) |
| تکرارپذیری (Idempotency) | بله (Idempotent) | لزوماً خیر (Non-Idempotent)* |
| امنیت داده | ریسک حذف دادههای ارسال نشده | ریسک کمتر، تمرکز بر تغییر |
| ایجاد منبع (Create) | بله (اگر ID مشخص باشد) | معمولاً خیر |
بحث عمیق: تکرارپذیری (Idempotency)
این مهمترین مفهوم فنی در تفاوت این دو است.
Idempotent بودن به این معناست که اگر شما یک درخواست را یک بار بفرستید یا ۱۰۰ بار بفرستید، نتیجه نهایی روی سرور باید یکسان باشد.
-
PUT تکرارپذیر است: فرض کنید ما یک PUT میفرستیم که میگوید Age: 20. اگر این درخواست را ده بار بفرستیم، سن کاربر همچنان ۲۰ خواهد بود. وضعیت نهایی پایدار است.
-
PATCH میتواند تکرارپذیر نباشد: اگرچه در اکثر پیادهسازیهای JSON (مثل فرستادن {age: 20})، پچ هم رفتار تکرارپذیر دارد، اما ذاتِ PATCH اینگونه نیست.
-
مثال: فرض کنید دستور PATCH شما این باشد: "به سن کاربر ۱ واحد اضافه کن" (Increment).
-
بار اول: سن میشود ۲۱.
-
بار دوم (اگر شبکه خطا دهد و دوباره ارسال شود): سن میشود ۲۲.
-
بنابراین، PATCH از نظر ریاضیاتی همیشه Idempotent نیست.
-
۳. پیادهسازی در AJAX (با مثال کد)
بیایید فرض کنیم یک منبع کاربر (User) با ساختار زیر در دیتابیس داریم:
{
"id": 1,
"username": "ali_reza",
"email": "ali@example.com",
"score": 100
}
حال میخواهیم ایمیل این کاربر را به new@example.com تغییر دهیم.
سناریوی ۱: استفاده از PUT (جاوا اسکریپت / Fetch API)
در اینجا ما مجبوریم کل آبجکت را بفرستیم، حتی username و score که تغییری نکردهاند.
// آدرس اندپوینت
const url = '/api/users/1';
// دادههای جدید (باید کامل باشد)
const updatedUser = {
username: "ali_reza", // تغییر نکرده اما باید ارسال شود
email: "new@example.com", // تغییر کرده
score: 100 // تغییر نکرده اما باید ارسال شود
};
fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedUser)
})
.then(response => response.json())
.then(data => console.log('PUT Success:', data));
خطر: اگر در کد بالا score را فراموش کنید و نفرستید، مقدار score در دیتابیس حذف شده یا صفر میشود (بسته به تنظیمات Backend).
سناریوی ۲: استفاده از PATCH (جاوا اسکریپت / Fetch API)
در اینجا فقط چیزی که تغییر کرده را میفرستیم.
const url = '/api/users/1';
// فقط فیلد تغییر یافته
const partialUpdate = {
email: "new@example.com"
};
fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(partialUpdate)
})
.then(response => response.json())
.then(data => console.log('PATCH Success:', data));
مزیت: حجم درخواست (Payload) بسیار کمتر است و نیازی نیست کلاینت از مقادیر فعلی username یا score خبر داشته باشد.
۴. چالشهای پیادهسازی در Backend
تفاوت فقط در سمت کلاینت (AJAX) نیست؛ بیشترین تفاوت در کدی است که شما در سمت سرور (Node.js, Python, PHP, ASP.NET) مینویسید.
چالش Null در PATCH
یکی از پیچیدگیهای PATCH زمانی است که میخواهید یک فیلد را واقعاً پاک کنید (Null کنید).
اگر کلاینت بفرستد:
{ "email": null }
آیا منظور این است که ایمیل را نادیده بگیر (تغییر نده) یا ایمیل را از دیتابیس پاک کن؟
-
در PUT، اگر فیلدی نباشد یعنی حذف شود.
-
در PATCH معمولاً قرارداد میشود که اگر کلید وجود داشت و مقدارش null بود، یعنی آن را در دیتابیس پاک کن. اما اگر کلید اصلاً در JSON نبود، یعنی دست نزن. برنامه نویس باید این منطق را با if (key in request) پیادهسازی کند، نه با if (request.key == null).
۵. کدام را انتخاب کنیم؟
این تصمیم به معماری سیستم و نیازهای شما بستگی دارد، اما راهنمای کلی زیر میتواند کمک کننده باشد:
چه زمانی از PUT استفاده کنیم؟
-
سادگی سمت سرور: پیادهسازی PUT در سمت سرور معمولاً سادهتر است (فقط جایگزین کن).
-
کلاینتِ آگاه: زمانی که کلاینت (مثلاً فرم ویرایش پروفایل) تمام اطلاعات فعلی کاربر را دارد و دکمه "ذخیره" را میزند.
-
عملیات Upsert: زمانی که میخواهید بگویید "این را در ID شماره ۵ قرار بده، اگر نبود بسازش".
چه زمانی از PATCH استفاده کنیم؟
-
بهینهسازی پهنای باند: وقتی آبجکتها بسیار بزرگ هستند (مثلاً دارای هزاران فیلد) و شما فقط میخواهید یک فیلد (مثلاً وضعیت فعال/غیرفعال) را تغییر دهید.
-
همزمانی (Concurrency): وقتی چند کاربر همزمان روی یک رکورد کار میکنند. PATCH امنتر است چون فقط فیلد خاصی را تغییر میدهد و احتمال اینکه تغییرات کاربر دیگر روی فیلدهای دیگر را بازنویسی کند کمتر است (به نسبت PUT که کل رکورد را بازنویسی میکند).
-
تغییرات ریز: مثل لایک کردن یک پست، تغییر وضعیت سفارش، یا آپدیت کردن آخرین زمان بازدید.
۶. استانداردهای پیشرفته: JSON Patch
برای استفاده حرفهای از PATCH، استانداردی به نام RFC 6902 (JSON Patch) وجود دارد. در این روش، به جای ارسال یک آبجکت ساده، آرایهای از "دستورات" ارسال میشود.
مثال درخواست استاندارد JSON Patch:
[
{ "op": "replace", "path": "/email", "value": "new@example.com" },
{ "op": "remove", "path": "/score" }
]
این روش بسیار دقیق است اما پیادهسازی آن در کلاینت و سرور پیچیدهتر است و معمولاً برای APIهای عمومی ساده (Simple JSON Merge Patch) استفاده نمیشود.
برخورد سرور با url ارسالی
کلمه users در آدرس چیست؟
در آدرس /api/users/1:
-
users: نام منبع (Resource) یا همان موجودیت است. این معمولاً نام Controller شما در سمت سرور است.
-
1: شناسه (ID) آن موجودیت خاص است.
در معماری REST، آدرس URL نباید شامل "فعل" باشد (مثل getUser یا updateUser). آدرس فقط باید بگوید "ما با چه چیزی کار داریم" (یک اسم).
پس چگونه ۴ عمل اصلی انجام میشود؟
شما یک آدرس ثابت دارید (/api/users/1)، اما رفتار سیستم بر اساس "نوع درخواست" (HTTP Method) تغییر میکند.
تصور کنید /api/users/1 مثل "شماره اتاق ۱ در هتل کاربران" است. شما جلوی درب این اتاق میروید. حالا رفتار شما (متد) تعیین میکند چه اتفاقی بیفتد:
-
GET (نگاه کردن): درب را باز میکنید و فقط داخل را میبینید (Read).
-
PUT (جایگزین کردن): دکوراسیون اتاق را کلاً بیرون میریزید و دکور جدید میچنید (Update All).
-
PATCH (تعمیر کردن): فقط لامپ سوخته را عوض میکنید (Update Partial).
-
DELETE (تخریب): اتاق را خالی/حذف میکنید (Delete).
آدرس (شماره اتاق) همیشه ثابت بود، اما ابزاری که با خود بردید متفاوت بود.
این در کد C# (ASP.NET Core) چگونه دیده میشود؟
"آیا ما یک سرویس داریم؟". بله، معمولاً ما یک کلاس (Controller) داریم که مدیریت users را بر عهده دارد، اما داخل آن کلاس، متدهای جداگانهای برای هر نوع درخواست نوشته شده است.
سیستم مسیریابی (Routing) فریمورک (مثلاً ASP.NET Core) هوشمند است. وقتی درخواستی میآید، نگاه میکند:
-
آدرس چیست؟ -> /api/users/1 (پس بفرستش به UsersController)
-
متد چیست؟ -> PUT (پس بفرستش به تابع UpdateUser)
[ApiController]
[Route("api/[controller]")] // اینجا مشخص میکنیم آدرس پایه api/users است
public class UsersController : ControllerBase
{
// 1. وقتی با متد GET صدا زده شود
// URL: /api/users/1
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
return Ok("این اطلاعات کاربر است");
}
// 2. وقتی با متد PUT صدا زده شود (همان آدرس بالا)
// URL: /api/users/1
[HttpPut("{id}")]
public IActionResult UpdateWholeUser(int id, [FromBody] UserDto user)
{
return Ok("کل کاربر جایگزین شد");
}
// 3. وقتی با متد PATCH صدا زده شود (همان آدرس بالا)
// URL: /api/users/1
[HttpPatch("{id}")]
public IActionResult UpdateEmailOnly(int id, [FromBody] PatchDto partialUpdate)
{
return Ok("فقط بخشی از کاربر تغییر کرد");
}
// 4. وقتی با متد DELETE صدا زده شود (همان آدرس بالا)
// URL: /api/users/1
[HttpDelete("{id}")]
public IActionResult DeleteUser(int id)
{
return Ok("کاربر حذف شد");
}
}
مقایسه با روش قدیمی (RPC)
شاید ذهنیتی در این میان برای شما پیش بیاید که در گذشته (و حتی الان در برخی سناریوها - بنده خودم بشدت علاقمند به این روش یعنی RPC هستم) استفاده میشد. در آن روش، "فعل" یا "متد" بخشی از آدرس URL بود:
-
/api/users/GetUserDetails?id=1
-
/api/users/UpdateUserEmail
-
/api/users/DeleteUser
در این روش قدیمی، همه درخواستها معمولاً با POST یا GET ارسال میشدند و آدرس URL مدام تغییر میکرد. اما در روش استاندارد REST، آدرس تمیز و ثابت میماند و HTTP Verb نقش متمایزکننده را بازی میکند.
بنابراین ...
-
Users چیست؟ نام منبع (Resource) و معمولاً نام کنترلر (UsersController) در سمت سرور.
-
آیا یک سرویس است؟ بله، یک کلاس کنترلر است که چندین تابع (Action) داخل خود دارد.
-
چطور تشخیص میدهد؟ فریمورک سرور (مثل ASP.NET) به صورت خودکار بر اساس HTTP Method (که آیا GET است یا PUT یا PATCH) درخواست را به تابع (متد) مربوطه در داخل آن کلاس هدایت میکند.
نتیجهگیری
درک تفاوت PUT و PATCH فراتر از یک بحث آکادمیک است؛ این موضوع مستقیماً بر عملکرد (Performance)، یکپارچگی دادهها (Data Integrity) و تجربه توسعهدهنده (DX) تاثیر میگذارد.
-
از PUT استفاده کنید اگر میخواهید منبع را کاملاً بازنویسی کنید و کلاینت تمام دادهها را در اختیار دارد.
-
از PATCH استفاده کنید اگر میخواهید تغییرات دقیق، کوچک و بهینه اعمال کنید.
رعایت این استانداردها باعث میشود API شما پیشبینیپذیر باشد و توسعهدهندگانی که از سرویسهای AJAX شما استفاده میکنند (فرانتاند کارها)، دقیقاً بدانند چه رفتاری از سیستم انتظار داشته باشند.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.