{
  "title": "استراتژی مدیریت خطا",
  "slug": "team/backend/error-handling-strategy",
  "url": "/docs/team/backend/error-handling-strategy",
  "frontmatter": {
    "layout": "doc",
    "title": "استراتژی مدیریت خطا",
    "description": "استراتژی مدیریت خطا در سرویس‌ها — Retry، DLQ، Idempotency و Recovery",
    "version": "1.0.0",
    "status": "PRIVATE",
    "author": "xoxxel",
    "owner": "xoxxel",
    "created_at": "2026-06-07",
    "updated_at": "2026-06-07",
    "tags": "",
    "reviewers": ""
  },
  "sections": [
    {
      "level": 1,
      "heading": "استراتژی مدیریت خطا",
      "content": "> Error Handling Strategy\n\n> کدهای خطای پلتفرم در [nons-api/contracts/errors.proto](/docs/team/platform/package/contract_catalog) و کدهای خطای دامنه در هر سرویس تعریف شده‌اند — این سند فقط استراتژی Retry، DLQ و Idempotency را پوشش می‌دهد"
    },
    {
      "level": 2,
      "heading": "اصول کلی",
      "content": "- **تلاش مجدد (Retry):** برای خطاهای موقتی (مانند قطعی شبکه، timeout، در دسترس نبودن سرویس‌های وابسته).\n- **Dead Letter Queue (DLQ):** برای خطاهای دائمی (مانند خطای اعتبارسنجی شِما، خطای منطق کسب‌وکار، داده‌های ناقص).\n- **قابلیت توانمندی (Idempotency):** همه مصرف‌کنندگان رویداد باید توانمند (idempotent) باشند؛ زیرا تحویل (delivery) رویدادها ممکن است بیش از یک بار انجام شود.\n- **جداسازی (Isolation):** خطا در یک مصرف‌کننده نباید سایر مصرف‌کنندگان همان رویداد یا رویدادهای دیگر را تحت تأثیر قرار دهد."
    },
    {
      "level": 2,
      "heading": "خطاهای موقتی (Transient Errors)",
      "content": "خطاهای موقتی شامل موارد زیر می‌شوند:\n\n- اتصال به پایگاه داده قطع است.\n- سرویس وابسته در دسترس نیست (HTTP 5xx، timeout).\n- محدودیت نرخ (Rate limit) موقتی.\n- قفل (Lock) موقتی در دیتابیس.\n- **currency-service در دسترس نیست** — استفاده از آخرین نرخ cache شده در Redis.\n- **TigerBeetle در دسترس نیست** — تلاش مجدد برای ثبت تراکنش‌های settlement.\n\n**عمل مورد نیاز:** تلاش مجدد با پشتوانه (Backoff)."
    },
    {
      "level": 3,
      "heading": "خط مشی تلاش مجدد",
      "content": "| تلاش | تأخیر |\n|---|---|\n| ۱ | ۵ ثانیه |\n| ۲ | ۳۰ ثانیه |\n| ۳ | ۲ دقیقه |\n| پس از ۳ تلاش | انتقال به DLQ |"
    },
    {
      "level": 2,
      "heading": "خطاهای دائمی (Permanent Errors)",
      "content": "خطاهای دائمی شامل موارد زیر می‌شوند:\n\n- اعتبارسنجی شِما رویداد ناموفق بود.\n- فیلد اجباری در payload وجود ندارد.\n- نوع داده فیلد اشتباه است.\n- موجودیت ارجاع‌شده وجود ندارد (مثلاً `userId` نامعتبر) و با بازیابی (reconciliation) قابل حل نیست.\n- نقض محدودیت کسب‌وکار.\n- **نرخ ارز invalid یا قدیمی** — نرخ‌های ارز باید در بازه زمانی معتبر باشند؛ اگر نرخ cache شده منقضی شده و currency-service در دسترس نباشد، پرداخت متوقف و رویداد به DLQ منتقل می‌شود.\n- **خطای تبدیل ارز غیرمجاز** — جفت ارز پشتیبانی‌نشده (مثلاً IRR/TRY مستقیم) باعث خطای دائمی می‌شود.\n\n**عمل مورد نیاز:** انتقال فوری رویداد به DLQ (بدون تلاش مجدد)."
    },
    {
      "level": 2,
      "heading": "خطای مصرف‌کننده (Consumer Error)",
      "content": "اگر یک مصرف‌کننده در پردازش یک رویداد با خطای غیرمنتظره‌ای مواجه شود (مثلاً برنامه از کار بیفتد یا استثنای مدیریت‌نشده)، نادر (NATS) پس از timeout پیام را دوباره تحویل می‌دهد. مصرف‌کننده باید از توانمند بودن خود اطمینان حاصل کند."
    },
    {
      "level": 2,
      "heading": "نظارت و هشدار (Alerting)",
      "content": "| معیار | آستانه هشدار |\n|---|---|\n| شمارش پیام‌های DLQ | بیش از ۱۰ پیام در ۵ دقیقه |\n| سن پیام در DLQ | بیش از ۱ ساعت |\n| نرخ تلاش مجدد | بیش از ۵۰ تلاش مجدد در دقیقه به ازای هر سوژه |"
    },
    {
      "level": 2,
      "heading": "بازیابی (Recovery) از DLQ",
      "content": "- پیام‌های DLQ باید به صورت دستی یا با یک فرایند خودکار بررسی شوند.\n- علت خطا باید مستند شود.\n- پس از رفع علت، پیام می‌تواند دوباره منتشر شود یا به صورت دستی پردازش گردد."
    },
    {
      "level": 2,
      "heading": "تضمین‌های سرویس",
      "content": "- رویدادها حداقل یک بار (At-Least-Once) تحویل داده می‌شوند.\n- مصرف‌کنندگان باید توانمند باشند.\n- ترتیب رویدادها در یک سوژه واحد، به ازای هر منتشرکننده، حفظ می‌شود.\n- هیچ تضمینی برای ترتیب رویدادها در سوژه‌های مختلف وجود ندارد.\n\n---"
    },
    {
      "level": 2,
      "heading": "سناریوهای خطای سرویس‌های مالی",
      "content": ""
    },
    {
      "level": 3,
      "heading": "Currency Service — Fallback و خطاها",
      "content": "| سناریو | رفتار | نوع خطا |\n|--------|-------|---------|\n| currency-service در دسترس نیست، cache Redis موجود است | استفاده از آخرین نرخ cache شده | موقتی — لاگ هشدار |\n| currency-service در دسترس نیست، cache Redis هم موجود نیست | **توقف پرداخت** — رویداد به DLQ منتقل می‌شود | دائمی |\n| نرخ cache شده قدیمی است (TTL منقضی) | تلاش مجدد برای دریافت نرخ جدید؛ اگر ناموفق → DLQ | دائمی |\n| جفت ارز درخواستی پشتیبانی نمی‌شود (مثلاً IRR/USDT) | خطای `UNSUPPORTED_PAIR` برگردانده می‌شود | دائمی |\n| مقدار ورودی برای تبدیل نامعتبر است (منفی، صفر) | خطای `INVALID_AMOUNT` برگردانده می‌شود | دائمی |\n\n**خط مشی تلاش مجدد برای currency-service:**\n\n| تلاش | تأخیر | اقدام |\n|------|-------|-------|\n| ۱ | ۱ ثانیه | تلاش مجدد درخواست به currency-service |\n| ۲ | ۵ ثانیه | تلاش مجدد + استفاده از cache به عنوان fallback |\n| ۳ | ۳۰ ثانیه | استفاده از cache (اجباری) |\n| پس از ۳ تلاش | — | اگر cache موجود نباشد → DLQ |"
    },
    {
      "level": 3,
      "heading": "TigerBeetle — خطاهای Ledger و Retry Settlement",
      "content": "| سناریو | رفتار | نوع خطا |\n|--------|-------|---------|\n| TigerBeetle در دسترس نیست (connection refused) | Retry با backoff | موقتی |\n| TigerBeetle timeout | Retry با backoff | موقتی |\n| تراکنش تکراری (duplicate) | شناسایی توسط `id` یکتا و نادیده گرفتن (idempotency) | موقتی — بی‌خطر |\n| موجودی ناکافی در حساب (insufficient funds) | خطای کسب‌وکار — بررسی دستی | دائمی |\n| عدم تطابق جمع debit/credit | خطای حیاتی — لاگ فوری + هشدار تیم | دائمی |\n\n**خط مشی تلاش مجدد برای TigerBeetle (settlement-service):**\n\n| تلاش | تأخیر | اقدام |\n|------|-------|-------|\n| ۱ | ۲ ثانیه | تلاش مجدد ثبت تراکنش در TigerBeetle |\n| ۲ | ۱۰ ثانیه | تلاش مجدد |\n| ۳ | ۶۰ ثانیه | تلاش مجدد نهایی |\n| پس از ۳ تلاش | — | رویداد به DLQ + هشدار تیم Devops |\n\n**نکات مهم:**\n- settlement-service باید idempotent باشد — هر `settlement_id` فقط یک بار پردازش شود.\n- `rate_at_payment` در هر تراکنش TigerBeetle ثبت می‌شود — برای audit و dispute حیاتی است.\n- اگر settlement با شکست مواجه شود، order در حالت `RELEASED` باقی می‌ماند و تسویه در صف بعدی دوباره تلاش می‌شود."
    }
  ]
}