سلام دوستان؛ مستقیم میرم سر اصل مطلب. اگر بخوام صادقانه بگم، idempotency جزو اون مفاهیمیه که خیلیها فکر میکنن میدوننش، ولی وقتی پای پیادهسازی واقعی وسط میاد، تازه عمق ماجرا مشخص میشه. مخصوصاً وقتی سیستم بزرگ میشه، توزیعشده میشه، یا با پول و دادهی حساس سروکار داره. idempotency دقیقاً همون چیزیه که جلوی خیلی از فاجعههای نرمافزاری رو قبل از اتفاق گرفتن میگیره.
به زبان ساده، idempotency یعنی اجرای چندبارهی یک عملیات، سیستم رو به وضعیت جدیدی نبره و نتیجه نهایی همون نتیجهی اجرای یکباره باشه. نکتهی مهم اینجاست که این «نتیجه» فقط مقدار خروجی نیست؛ شامل side effectها هم میشه. یعنی نه دادهی تکراری ساخته بشه، نه پول دوبار کم بشه، نه state سیستم از کنترل خارج بشه.
مفهوم idempotency در اصل از ریاضیات میاد. تابعی idempotent محسوب میشه که اگر چند بار روی یک ورودی اعمال بشه، خروجی تغییر نکنه. علوم کامپیوتر این مفهوم رو گرفت و به یکی از پایههای تفکر در طراحی سیستمهای قابلاعتماد تبدیلش کرد.
در مهندسی نرمافزار، idempotency بیشتر از اینکه یک تعریف تئوریک باشه، یک استراتژی برای بقا در دنیای غیرقابلاعتماد شبکههاست. چون در عمل، هیچ تضمینی وجود نداره که یک درخواست فقط یک بار ارسال یا فقط یک بار پردازش بشه.
در دنیای وب، idempotency بهصورت رسمی در استانداردهای HTTP تعریف شده. طبق RFC 7231، بعضی متدهای HTTP ذاتاً idempotent در نظر گرفته میشن. این یعنی کلاینت حق داره اونها رو چند بار صدا بزنه، بدون اینکه نگران تغییر ناخواستهی وضعیت سرور باشه.
اما این فقط یک «انتظار» از سمت پروتکله، نه یک تضمین خودکار. اگر backend درست طراحی نشده باشه، حتی PUT هم میتونه غیر-idempotent بشه. اینجاست که مسئولیت کامل میافته روی دوش طراح سیستم.

فرض کن کاربر روی دکمهی «پرداخت» کلیک میکنه. درخواست به سرور میرسه، پول کم میشه، ولی پاسخ بهخاطر timeout به کاربر نمیرسه. کاربر دوباره روی دکمه کلیک میکنه. اگر سیستم idempotent نباشه، پول دوباره کم میشه. همین یک سناریو بهتنهایی کافیه تا بفهمیم idempotency شوخیبردار نیست.
راهحل رایج اینه که برای هر درخواست پرداخت، یک idempotency key یکتا تعریف بشه. سرور اگر دوباره همون key رو ببینه، نتیجهی قبلی رو برمیگردونه، نه اینکه عملیات رو تکرار کنه.
در ثبت سفارش، معمولاً POST استفاده میشه. اما اگر درخواست دوبار ارسال بشه، دو سفارش ساخته میشه. اینجاست که یا باید:
شناسهی سفارش از سمت کلاینت بیاد
یا server-side تشخیص بده که این درخواست قبلاً پردازش شده
در غیر این صورت، دیتابیس پر از سفارشهای تکراری میشه و کسی هم نمیفهمه مشکل از کجاست.
در Kafka یا RabbitMQ، دریافت دوبارهی پیام یک اتفاق کاملاً طبیعیه. مصرفکننده ممکنه crash کنه، یا commit offset انجام نشه. اگر پردازش پیام idempotent نباشه، نتیجه میتونه ثبت چندبارهی یک عملیات باشه.
به همین دلیله که در سیستمهای event-driven، idempotent consumer یک اصل طراحی محسوب میشه، نه یک بهینهسازی. (معمار ها میدونن چی دارم میگم!)
خیلیها فکر میکنن transaction داشتن یعنی idempotency. ولی این اشتباهه. transaction فقط atomic بودن رو تضمین میکنه، نه idempotent بودن رو. یک INSERT داخل transaction اگر دوبار اجرا بشه، دوبار داده میسازه.
برای رسیدن به idempotency در دیتابیس، معمولاً از این ابزارها استفاده میشه:
unique constraint
primary key معنادار
upsert
check روی state قبلی
یکی از رایجترین اشتباهات اینه که idempotency فقط در سطح API دیده میشه. در حالی که اگر منطق بیزینس یا دیتابیس idempotent نباشه، API هم عملاً idempotent نخواهد بود.
اشتباه رایج دیگه اینه که idempotency با retry اشتباه گرفته میشه. retry یک مکانیزمه، idempotency یک خاصیته. retry بدون idempotency فقط احتمال خرابکاری رو بیشتر میکنه.
و شاید خطرناکترین اشتباه اینه که idempotency رو فقط برای «سیستمهای بزرگ» ضروری بدونیم. واقعیت اینه که حتی یک سرویس کوچیک هم اگر قراره رشد کنه، باید از همون اول با این طرز فکر ساخته بشه.
idempotency یعنی پذیرش این واقعیت که دنیای واقعی پر از خطاست. یعنی طراحی سیستم بهگونهای که از تکرار نترسه. سیستمی که idempotent طراحی شده، نهتنها پایدارتره، بلکه قابلاعتمادتر، قابلدیباگتر و در نهایت حرفهایتره.
اگر بخوای یک خط قرمز بین سیستم آماتور و سیستم بالغ بکشی، idempotency یکی از واضحترین اونهاست.
در مقاله بعدی بیشتر در مورد پیاده سازی این موضوع صحبت خواهیم کرد ...