کاربر فروشگاه ایرانی روی «پرداخت» میزند؛ درخواست به درگاه میرود، اما اینترنت موبایل او درست پیش از دریافت پاسخ قطع میشود. اپ دوباره همان درخواست را میفرستد. آیا دو سفارش ساخته میشود؟ آیا مبلغ دو بار رزرو میشود؟ آیا تیم پشتیبانی میتواند بفهمد کدام مرحله شکست خورده است؟ پاسخ این پرسشها را واژه جادویی «API» نمیدهد؛ کیفیت قرارداد، امنیت و رفتار API در شکست تعیین میکند.
در این راهنما، API را نه «پل نامرئی» و نه یک محصول آماده، بلکه یک قرارداد قابلآزمایش میان مصرفکننده و ارائهدهنده میبینیم. از معناشناسی HTTP و تفاوت REST، GraphQL و gRPC شروع میکنیم؛ سپس به طراحی Resource، احراز هویت و مجوز، Idempotency، خطا و Retry، تست، Observability و چرخه عمر میرسیم. مثالها برای فروشگاه، SaaS و سرویسهای ایرانی نوشته شدهاند و در استفاده از سرویس خارجی، مسیر دورزدن محدودیتها پیشنهاد نمیشود.
API چیست؟ تعریف عملی، نه استعاره
API یا Application Programming Interface رابطی تعریفشده است که به یک نرمافزار اجازه میدهد قابلیت یا داده نرمافزار دیگری را طبق قواعد مشخص درخواست کند. این قواعد باید روشن کنند چه عملیاتی وجود دارد، ورودی و خروجی چه شکلی است، هویت و دسترسی چگونه کنترل میشود، خطا چه معنایی دارد و تغییرات آینده چگونه اعلام میشود.
API الزاماً تحت وب نیست. یک کتابخانه برنامهنویسی، سیستمعامل و مرورگر نیز API دارند. موضوع این مقاله Web API است؛ یعنی قراردادی که معمولاً روی شبکه و با HTTP در دسترس است. مصرفکننده میتواند وبسایت، اپ موبایل، پنل شریک تجاری، یک سرویس داخلی یا سامانه زمانبندیشده باشد.
| مفهوم | پرسشی که باید جواب دهد | نمونه |
|---|---|---|
| قابلیت | مصرفکننده چه کاری میتواند انجام دهد؟ | ساخت سفارش یا مشاهده موجودی |
| قرارداد داده | فیلد، نوع، واحد و محدودیت چیست؟ | amount_rial عدد صحیح مثبت |
| معناشناسی | تکرار درخواست و هر وضعیت چه معنایی دارد؟ | تکرار امن ساخت پرداخت با کلید Idempotency |
| اعتماد | چه کسی درخواست میدهد و چه اجازهای دارد؟ | کاربر احرازشده فقط سفارش خودش را میبیند |
| عملیات | چگونه خطا، ظرفیت و تغییر را مدیریت میکنیم؟ | Timeout، Retry، SLO و اعلام Deprecation |
یک درخواست از چه مسیرهایی میگذرد؟
درخواست فقط از «کلاینت به سرور» نمیپرد. در یک معماری واقعی ممکن است از DNS، اتصال TLS، CDN یا WAF، Load Balancer و API Gateway عبور کند؛ سپس احراز هویت، مجوز، اعتبارسنجی ورودی، منطق کسبوکار، پایگاه داده و یک سرویس ثالث را طی کند. پاسخ نیز باید کد وضعیت، Header و Body قابلفهم داشته باشد و در تمام این مسیر، شناسه همبستگی و Telemetry امکان عیبیابی را فراهم کند.
هر مرحله Failure mode خودش را دارد: DNS حل نمیشود، TLS منقضی است، Gateway نرخ را محدود میکند، Token معتبر ولی فاقد Scope است، رکورد همزمان تغییر کرده، درگاه پاسخ نامعلوم داده یا پاسخ سالم در مسیر برگشت گم شده است. طراحی API یعنی این شکستها را پیش از رخدادن نامگذاری و مدیریت کنیم.
API چه چیزی را تضمین نمیکند؟
- Microservice بودن، مقیاسپذیری یا نگهداشتپذیری را خودکار نمیکند؛ توزیع سامانه هزینه شبکه و عملیات را هم اضافه میکند.
- جداشدن Frontend و Backend لزوماً UX سریعتر نمیسازد؛ Payload بزرگ، Waterfall درخواستها و JavaScript سنگین میتواند نتیجه را بدتر کند.
- API Gateway جای مجوز در سطح Object و Business rule را نمیگیرد.
- HTTPS محتوای مسیر را رمز میکند، اما ورودی مخرب، دسترسی اضافه یا Secret افشاشده را درمان نمیکند.
- API-first بدون مالک، تست قرارداد و سیاست تغییر فقط یک شعار است.
قرارداد HTTP: متد، Header، وضعیت و Body
برای HTTP API، نخست باید معناشناسی خود HTTP را بفهمیم. RFC 9110 معنی متدها، کدهای وضعیت و دادههای کنترلی را تعریف میکند. Framework میتواند Route بسازد، اما معنای درست درخواست را تیم محصول و مهندسی تعیین میکنند.
Safe و Idempotent با «فقط یکبار اجرا» فرق دارند
| متد | کاربرد معمول | Safe؟ | Idempotent در معناشناسی HTTP؟ | نکته طراحی |
|---|---|---|---|---|
| GET | خواندن نمایش منبع | بله | بله | نباید اثر تجاری مانند ثبت سفارش ایجاد کند |
| HEAD | Headerهای GET بدون Body | بله | بله | برای متادیتا و بررسی سبک مفید است |
| POST | ساخت یا فرمان پردازشی | خیر | بهطور پیشفرض خیر | برای عملیات مالی کلید Idempotency طراحی کنید |
| PUT | جایگزینی وضعیت منبع | خیر | بله | قرارداد باید معنای فیلدهای حذفشده را روشن کند |
| PATCH | تغییر بخشی از منبع | خیر | وابسته به قالب و عملیات | افزایش موجودی با «+۱» با Set کردن مقدار برابر نیست |
| DELETE | حذف منبع | خیر | بله | اثر پاسخهای تکراری و Soft delete را مستند کنید |
Idempotent یعنی چند درخواست یکسان، از منظر اثر موردنظر سرور، نتیجه نهایی یکسانی داشته باشد؛ نه اینکه شبکه فقط یک بار بسته را بفرستد یا همه پاسخها دقیقاً یکسان باشند. یک DELETE ممکن است بار اول ۲۰۴ و بار دوم ۴۰۴ بدهد، ولی منبع در هر دو حالت حذفشده باقی بماند.
کد وضعیت را به بخشی از قرارداد تبدیل کنید
| کد | معنای پیشنهادی در API | خطای رایج |
|---|---|---|
| 200 / 201 / 202 / 204 | موفق، ساختهشده، پذیرفتهشده برای پردازش، موفق بدون Body | استفاده از ۲۰۰ برای Jobی که هنوز تمام نشده |
| 400 | درخواست از نظر نحو یا ساختار نامعتبر | قرار دادن همه خطاهای کسبوکار زیر ۴۰۰ |
| 401 | اعتبار احراز هویت موجود نیست یا پذیرفته نشد | معادلگرفتن با «اجازه نداری» |
| 403 | هویت شناخته شده ولی مجوز این عمل را ندارد | افشای بیش از حد وجود یک منبع حساس |
| 404 | منبع یافت نشد یا بنا بر سیاست نباید آشکار شود | برگرداندن ۲۰۰ و success:false |
| 409 | تعارض با وضعیت فعلی، نسخه یا قاعده یکتایی | نادیدهگرفتن Concurrent update |
| 422 | ساختار فهمیده شده اما مقدار از نظر دامنه پذیرفتنی نیست | وابستهکردن کلاینت به متن فارسی خطا |
| 429 | محدودیت مصرف؛ ترجیحاً همراه Retry-After | Retry فوری و همزمان همه کلاینتها |
| 500 / 502 / 503 / 504 | خطای داخلی، Gateway، عدم دسترسپذیری موقت، Timeout بالادستی | ارسال Stack trace و اطلاعات حساس |
برای خطاهای ماشینخوان، یک ساختار پایدار تعریف کنید. RFC 9457 قالب application/problem+json را با فیلدهایی مانند type، title، status، detail و instance ارائه میدهد. متن detail میتواند برای انسان ترجمه شود، اما کلاینت نباید منطقش را از روی جمله فارسی استخراج کند؛ یک type یا کد دامنه پایدار لازم است.
{
"type": "https://api.example.ir/problems/inventory-conflict",
"title": "موجودی کافی نیست",
"status": 409,
"detail": "از کالای درخواستی فقط ۲ عدد باقی مانده است.",
"instance": "https://api.example.ir/problem-instances/01J...",
"code": "INVENTORY_CONFLICT",
"trace_id": "8b9f..."
}REST، GraphQL، gRPC، SOAP و Event: چه چیزی را مقایسه میکنیم؟
REST یک سبک معماری است، GraphQL زبان Query و Runtime است، gRPC چارچوب RPC است و Webhook الگوی اعلان رویداد به مصرفکننده محسوب میشود. پس جدول انتخاب باید مسئله، مصرفکننده و محدودیت عملیاتی را مقایسه کند، نه اینکه یک برنده همیشگی اعلام کند.
| گزینه | تناسب معمول | مزیت | هزینه یا ریسک |
|---|---|---|---|
| HTTP Resource-oriented (RESTful) | API عمومی، CRUD دامنه، Cache و ابزارهای رایج وب | معناشناسی آشنا و اکوسیستم وسیع | بدون طراحی درست ممکن است Chatty یا ناسازگار شود |
| GraphQL | UIهای متنوع با نیاز انتخاب Field و Graph پیچیده | درخواست داده متناسب با View و Schema تایپشده | مجوز Field/Object، پیچیدگی Query، Cache و هزینه اجرا |
| gRPC | ارتباط داخلی سرویسها، Streaming و کلاینتهای کنترلشده | IDL، تولید Client و Streaming کارآمد | مرورگر و Debug دستی محدودتر، نیاز به Gateway در برخی مرزها |
| SOAP | یکپارچهسازی Legacy یا اکوسیستم متکی به WS-* و قرارداد XML | استانداردهای بالغ برای برخی محیطهای سازمانی | پیچیدگی و Payload بیشتر برای بسیاری از کاربردهای جدید |
| WebSocket / SSE | بهروزرسانی پیوسته یا Push از سرور | ارتباط بلادرنگ یا جریان یکطرفه | اتصال طولانی، Backpressure، Resume و عملیات پیچیدهتر |
| Webhook / Event | اطلاع از پرداخت، ارسال یا تغییر وضعیت بدون Polling | Coupling زمانی کمتر | امضا، تکرار، ترتیب، Replay و تحویل حداقل یکبار |
برای جزئیات Trade-offهای Query و Cache، راهنمای مقایسه GraphQL و REST را ببینید. تعریف رسمی GraphQL آن را زبان Query و Execution engine برای مدل داده کلاینت ـ سرور میداند؛ نسخه منتشرشده فعلی در مشخصات GraphQL سپتامبر ۲۰۲۵ قابل بررسی است. مستندات رسمی gRPC نیز آن را چارچوب RPC با Protocol Buffers و قابلیتهای Lifecycle/Streaming معرفی میکند.
ماتریس تصمیم کوتاه
- API عمومی برای توسعهدهنده ثالث: HTTP/JSON و OpenAPI معمولاً نقطه شروع قابلدسترستری است.
- چند UI با نیازهای داده متفاوت: GraphQL میتواند مفید باشد، اگر Budget پیچیدگی Query و مجوز دقیق دارید.
- ارتباط داخلی کمتأخیر و تیمهای کنترلشده: gRPC را کنار Service discovery، Deadline و Observability ارزیابی کنید.
- اعلان تغییر وضعیت: Webhook یا Event را مکمل API خواندن وضعیت بدانید؛ اعلان بهتنهایی Source of truth نیست.
- شریک Legacy: هزینه جایگزینی SOAP شاید از ادامه قرارداد موجود بیشتر باشد.
در معماری محصول نیز انتخاب API از انتخاب تجربه کاربر جدا نیست. اگر هدف نصبپذیری، Offline یا Push است، راهنمای تصمیم و معماری PWA مرزهای Client، Cache و Backend را روشنتر میکند.
طراحی قرارداد: Contract-first، Code-first یا API-first؟
API-first یک جهت سازمانی است: API را محصولی با مصرفکننده، مالک و چرخه عمر میبینید. Contract-first یعنی پیش از Implementation، قرارداد قابلبررسی نوشته و با مصرفکنندگان همراستا شود. Code-first یعنی قرارداد از کد استخراج شود. هیچکدام کیفیت را تضمین نمیکنند؛ کیفیت از Review، تست سازگاری و بازخورد مصرفکننده میآید.
OpenAPI Specification یک توصیف استاندارد و مستقل از زبان برای HTTP API فراهم میکند تا انسان و ابزار، قابلیتهای سرویس را بدون خواندن Source بفهمند. در زمان نگارش، شاخه منتشرشده جدید ۳.۲.۰ است؛ اما نسخه سند را بر اساس پشتیبانی Toolchain خود انتخاب و Pin کنید، نه صرفاً عدد جدیدتر.
قرارداد حداقلی چه چیزهایی دارد؟
- Path و Operation با شناسه پایدار؛ Parameterهای Path، Query، Header و Cookie؛
- Schema ورودی و خروجی، Required/Nullable، محدودیت طول، Enum و Pattern؛
- نمونه معتبر و نامعتبر، همه وضعیتهای موفق و خطا، Headerهای مهم؛
- روش احراز هویت و Scope لازم برای هر Operation؛
- قواعد Pagination، Rate limit، Idempotency و Cache؛
- مالک، کانال پشتیبانی، SLO، سیاست نسخه و تغییر ناسازگار.
Specification را Lint کنید، Mock server بسازید و در CI اختلاف Implementation با قرارداد را بشکنید. Generated client مفید است، اما اگر قرارداد مبهم یا بیشازحد بزرگ باشد، فقط ابهام را به کد بیشتری تبدیل میکند.
Resource و Action را از زبان دامنه استخراج کنید
نامها باید برای تیم کسبوکار و توسعه یک معنا داشته باشند: /orders/{order_id} بهتر از /doOrderAction است. با این حال همه رفتارها CRUD نیستند. تأیید سفارش یا Refund یک Transition با قواعد تجاری است؛ Endpoint عملی مانند POST /payments/{id}/refunds میتواند از PATCH مبهم روشنتر باشد.
در طراحی هر Operation پاسخ دهید:
- Precondition چیست و چه کسی مجاز است؟
- اثر تجاری و مرز تراکنش کجاست؟
- تکرار درخواست چه نتیجهای دارد؟
- اگر Dependency پاسخ نداد، وضعیت نهایی معلوم است یا نامعلوم؟
- کاربر و اپراتور چگونه نتیجه را Reconcile میکنند؟
داده ایرانی: ریال، تومان، تقویم و متن فارسی
| موضوع | قرارداد پیشنهادی | چرا؟ |
|---|---|---|
| پول | عدد صحیح + کد Currency صریح؛ مثلاً amount: 1250000 و currency: IRR | «۱۲۵ هزار تومان» در UI نباید به حدس واحد در API تبدیل شود |
| زمان | Timestamp استاندارد با Offset/UTC؛ منطقه زمانی نمایش جدا | مرز روز، DST سیستم خارجی و Jobها مبهم نمیشود |
| تقویم | تاریخ Canonical در قرارداد؛ تبدیل جلالی در لایه ارائه مگر دامنه خلافش را بخواهد | محاسبه و یکپارچهسازی پایدارتر میماند |
| شناسه | رشته Opaque، نه شماره موبایل یا کدملی در URL | کاهش Coupling و افشای داده شخصی |
| متن | UTF-۸، قواعد Normalization و محدودیت طول روشن | ی/ک فارسی و عربی، نیمفاصله و جستوجو قابلکنترل میشود |
| شماره تلفن | قالب Canonical مانند E.۱۶۴ با سیاست اعتبارسنجی مشخص | ورودی ۰۹ و +۹۸ به دو هویت تبدیل نمیشود |
Pagination، Filter و ترتیب پایدار
برگرداندن «همه سفارشها» API را دیر یا زود میشکند. برای داده متغیر، Cursor pagination معمولاً از Offset در صفحات عمیق پایدارتر است؛ ولی Cursor باید Opaque و ترتیب یکتا باشد، مثلاً ترکیب زمان و ID. اگر Offset مناسب است، سقف limit، ترتیب پیشفرض و معنای Total count را مستند کنید. Filter و Sort فقط روی Fieldهای مجاز و Indexشده اجرا شوند تا Query دلخواه کاربر به حمله مصرف منابع تبدیل نشود.
امنیت API: هویت کافی نیست؛ مجوز هر عمل و هر Object مهم است
Authentication میپرسد «چه هویت یا Clientی این درخواست را فرستاده؟» و Authorization میپرسد «این هویت روی این Object و این Action چه اجازهای دارد؟». API key معمولاً پروژه یا Application را شناسایی میکند؛ بهتنهایی اثبات هویت کاربر و مجوز مشاهده یک سفارش نیست.
| مکانیزم | کاربرد مناسب | ملاحظه |
|---|---|---|
| Session cookie | وباپ First-party | Secure/HttpOnly/SameSite و دفاع CSRF لازم است |
| API key | شناسایی Client، Quota یا دسترسی ساده Server-to-server | به کاربر نچسبانید؛ Rotate، Scope و محدود کنید |
| OAuth 2.0 | اعطای دسترسی محدود به Client برای Resource | Authorization framework است، نه Login protocol |
| OpenID Connect | احراز هویت Federated روی OAuth | ID token را جای Access token به Resource API نفرستید |
| mTLS / Private-key JWT | Client authentication پرریسک یا Server-to-server | چرخه عمر Certificate/Key و عملیات پیچیدهتر است |
RFC 9700 راهنمای امنیتی بهروز OAuth ۲.۰ است؛ از Authorization Code، تطبیق دقیق Redirect URI و محدودکردن امتیاز Token دفاع میکند و برخی الگوهای ناامن قدیمی را کنار میگذارد. Flow را از روی نوع Client و Threat model انتخاب کنید؛ عبارت «از OAuth استفاده میکنیم» بهتنهایی Evidence امنیت نیست.
مجوز را در سه سطح بررسی کنید
- Function: آیا نقش Support حق Export یا Refund دارد؟
- Object: آیا کاربر A میتواند با تغییر
order_idسفارش B را بخواند؟ - Property: آیا کاربر مجاز است فیلد
roleیاcredit_limitرا در Body بنویسد؟
این کنترلها باید در مسیر دسترسی داده اجرا شوند، نه فقط با مخفیکردن دکمه در UI. فهرست OWASP API Security Top 10 2023 علاوه بر Broken authorization، مصرف بیرویه منابع، SSRF، مدیریت نادرست Inventory و اعتماد ناامن به APIهای ثالث را برجسته میکند. برای Threat model و کنترل اجرایی، راهنمای داخلی امنیت API بر پایه OWASP، OAuth و JWT را کنار این مقاله بخوانید.
CORS دیوار امنیتی API نیست
CORS به مرورگر میگوید کدام Origin اجازه دارد پاسخ Cross-origin را در JavaScript بخواند. ابزار خط فرمان، Bot و Backend مهاجم به CORS متکی نیستند. بنابراین CORS جای Authentication، Authorization، CSRF defense یا اعتبارسنجی ورودی نیست. Originهای مجاز را محدود کنید و Credential را با Wildcard ترکیب نکنید.
Secret، Log و داده شخصی
- Secret را در Frontend، Mobile bundle، URL، Git، Ticket یا نمونه مستندات قرار ندهید.
- کلیدها را در Secret manager نگه دارید؛ مالک، تاریخ ایجاد، Scope، Rotation و مسیر ابطال داشته باشند.
- Token، Cookie، شماره کارت، کدملی و متن حساس را پیش از Log ماسک یا حذف کنید.
- Response فقط داده لازم برای Use case را برگرداند؛ Serializing کامل مدل داخلی همان Excessive data exposure است.
- TLS را برای همه Hopهای لازم فعال و اعتبار Certificate را بررسی کنید؛ خاموشکردن Verification راهحل عملیاتی نیست.
Webhook را مثل ورودی اینترنتی نامطمئن ببینید
برای Callback پرداخت یا حمل، امضای پیام را روی Raw body و Secret اختصاصی بررسی کنید؛ Timestamp و پنجره زمانی جلوی Replay قدیمی را بگیرد؛ Event ID را Deduplicate کنید؛ IP allowlist را فقط کنترل کمکی بدانید؛ و پس از دریافت سریع، رویداد را در Queue پردازش کنید. هیچگاه صرف دریافت Callback، مبلغ و وضعیت سفارش را بدون تطبیق با داده محلی و در صورت لزوم استعلام مستقل نپذیرید. راهنمای اتصال امن درگاه پرداخت چرخه Initiate، Callback، Verify و Reconciliation را کاملتر توضیح میدهد.
سرویس خارجی برای کسبوکار ایرانی: فنی و حقوقی را با هم بسنجید
پیش از انتخاب نقشه، پیامک، ایمیل، هوش مصنوعی یا Cloud API، شرایط استفاده و Eligibility کشور/کسبوکار، روش پرداخت مجاز، محل و مالکیت داده، Subprocessorها، محدودیت نرخ، SLA، مسیر Support، Export و حذف داده و برنامه خروج را بررسی کنید. ممکن است قابلیت فنی امروز در دسترس و فردا محدود شود. این مقاله هیچ روش دورزدن محدودیت، هویت یا پرداخت ارائه نمیکند؛ گزینهای انتخاب کنید که استفاده شما را صریحاً مجاز بداند و جایگزین عملی داشته باشد.
Idempotency و وضعیت نامعلوم: قلب API پرداخت و سفارش
در شبکه، «پاسخ نگرفتم» مساوی «عمل انجام نشد» نیست. شاید سرور پرداخت را ثبت کرده و پاسخ در راه برگشت گم شده باشد. اگر Client همان POST را کورکورانه تکرار کند، Duplicate محتمل است. Idempotency key به سرور اجازه میدهد تلاشهای یک عملیات منطقی را تشخیص دهد.
الگوی عملی Idempotency key
- Client برای یک Intent تجاری، کلید تصادفی با Entropy کافی میسازد و در همه Retryهای همان Intent ثابت نگه میدارد.
- سرور کلید را در Scope مشخص—مثلاً Merchant + Operation—بهصورت اتمیک ثبت میکند.
- Hash پارامترهای مهم کنار کلید ذخیره میشود؛ استفاده دوباره همان کلید با مبلغ یا سفارش متفاوت باید Conflict بدهد.
- نتیجه نهایی یا شناسه عملیات ذخیره و در تکرار برگردانده میشود. درخواست همزمان دوم باید منتظر یا با وضعیت قابلفهم پاسخ داده شود.
- TTL بر اساس طول Retry، Reconciliation و ریسک دامنه تعیین و مستند میشود؛ حذف زودهنگام رکورد، Duplicate دیرهنگام میسازد.
POST /payment-attempts
Idempotency-Key: 01JAZ...
{
"order_id": "ord_01J...",
"amount": 1250000,
"currency": "IRR"
}Idempotency بهتنهایی Exactly-once delivery ایجاد نمیکند. تراکنش پایگاه داده، Unique constraint، Outbox/Inbox، Deduplication مصرفکننده و Job تطبیق همچنان لازماند. برای پرداخت نامعلوم، Endpoint خواندن وضعیت و Job Reconciliation با شناسه مرجع ارائهدهنده طراحی کنید؛ پشتیبان نباید وضعیت مالی را صرفاً با حدس دستی تغییر دهد.
Timeout، Deadline و Retry budget
هر فراخوانی شبکه Timeout میخواهد. Connect timeout را از Read/overall deadline جدا کنید. در زنجیره سرویسها، سرویس پاییندستی نباید Deadline طولانیتر از بودجه باقیمانده درخواست بگیرد. Retry فقط برای خطاهای موقت و عملیات امن/Idempotent انجام شود و محدود باشد.
| وضعیت | Retry خودکار؟ | رفتار پیشنهادی |
|---|---|---|
| Timeout قبل از دانستن نتیجه POST مالی | فقط با Idempotency و سیاست ارائهدهنده | همان کلید، سپس Query/Reconcile |
| 429 | معمولاً بله | احترام به Retry-After، Backoff و Jitter |
| ۵۰۲/۵۰۳/۵۰۴ موقت | محدود و مشروط | Retry budget، Jitter و Circuit breaker |
| 400/401/403/422 | خیر، بدون تغییر درخواست/اعتبار | اصلاح ورودی، Permission یا Token |
| Conflict 409 | وابسته به دامنه | خواندن نسخه جدید یا نمایش تعارض به کاربر |
Backoff نمایی بدون Jitter میتواند همه Instanceها را دوباره همزمان بیدار کند. Retry در چند لایه نیز تعداد فراخوانی را انفجاری میکند؛ یک لایه مالک Retry شود و Budget کل داشته باشد. Circuit breaker، Bulkhead، محدودیت Concurrency و Queue ابزارند، نه نسخه ثابت. ابتدا Failure mode و SLO را مشخص کنید.
Queue و Event: تحویل دوباره را عادی فرض کنید
اغلب Queueها تحویل At-least-once دارند؛ Consumer باید Duplicate را تحمل کند. Event ID، Inbox، پردازش اتمیک و Dead-letter queue تعریف کنید. ترتیب جهانی را فرض نکنید؛ اگر ترتیب برای یک سفارش مهم است، Partition key و Version رویداد را طراحی کنید. Schema رویداد نیز قرارداد است و تغییر ناسازگار آن میتواند مصرفکننده خاموش را بشکند.
Cache: سرعت با معنای درست
Cache برای Response عمومی و تکرارشونده مفید است، ولی داده حساب کاربر یا موجودی لحظهای سیاست جدا میخواهد. Cache-Control، Vary، ETag و درخواست شرطی را آگاهانه انتخاب کنید. پاسخ شخصی را در Cache اشتراکی نریزید و Invalidation را بخشی از طراحی بدانید. برای لایهها و Headerها، راهنمای کش سایت و معماری Cache را ببینید.
تست API: از Schema تا شکست عمدی
تست Happy path کافی نیست. API قابلاعتماد باید ثابت کند قرارداد را رعایت میکند، دسترسی افقی و عمودی را میبندد، زیر بار رفتار کنترلشده دارد و هنگام قطع Dependency نتیجه نامعلوم را درست مدیریت میکند.
| لایه تست | چه چیزی را کشف میکند؟ | نمونه Gate |
|---|---|---|
| Unit / Domain | قواعد کسبوکار و Edge case سریع | Refund بیش از مبلغ پرداختشده رد شود |
| Schema / Lint | نقص و ناسازگاری سند | Response مستندنشده یا Required مبهم ممنوع |
| Contract | توافق Provider و Consumer | تغییر حذف Field CI را Fail کند |
| Integration | پایگاه داده، Queue و Sandbox ثالث | Callback تکراری فقط یک اثر داشته باشد |
| Authorization / Security | BOLA/BFLA، Injection، SSRF و Secret leak | کاربر A به Order B دسترسی نگیرد |
| Property/Fuzz | ورودیهای پیشبینینشده و Boundary | Payload بزرگ یا Unicode غیرعادی سرویس را نشکند |
| Load / Soak | ظرفیت، Leak و Tail latency | SLO در بار هدف و مدت طولانی حفظ شود |
| Failure / Chaos | Timeout، قطع Dependency و Recovery | قطعی درگاه سفارش را دو بار نسازد |
Sandbox ارائهدهنده ثالث رفتار Production را کامل شبیهسازی نمیکند. Test double برای سناریوهای قطعی و تست محدود End-to-end برای Integration واقعی داشته باشید. Deploy را با Canary یا Feature flag کوچک آغاز کنید و Rollback قرارداد/داده را پیش از انتشار تمرین کنید.
Observability: آیا میتوان یک درخواست را تا نتیجه تجاری دنبال کرد؟
Log زیاد مساوی Observability نیست. برای هر API حداقل Rate، Errors و Duration را با بُعدهای کنترلشده بسنجید؛ Trace توزیعشده و Correlation ID مسیر را بین Gateway، سرویس و Queue وصل کند؛ و Metric تجاری مانند نرخ موفق Initiate تا Verify پرداخت کنار Metric فنی دیده شود.
- Latency: Median کافی نیست؛ p95/p99 و تفکیک Operation مهم است.
- Error: خطای Client را از Server/Dependency و Error code دامنه جدا کنید.
- Traffic و saturation: نرخ درخواست، Concurrency، Queue lag، Pool و ظرفیت Dependency را ببینید.
- Trace: Spanها Deadline، Retry count و Dependency را نشان دهند، اما Payload حساس را ثبت نکنند.
- SLO: شاخصی انتخاب کنید که موفقیت مورد انتظار مصرفکننده را بسنجد، نه صرفاً Up بودن Process.
برای معماری Telemetry و طراحی SLO، راهنمای Observability وبسایت را بخوانید. پایش بیرونی نیز باید مسیر حیاتی را از دید کاربر بررسی کند؛ راهنمای مانیتورینگ Uptime تفاوت Check سطحی با Synthetic journey را توضیح میدهد.
Cardinality و حریم خصوصی را کنترل کنید
قرار دادن user_id، URL کامل یا order_id در Label متریک هزینه و Cardinality را منفجر میکند؛ این شناسهها در Trace/Log کنترلشده مناسبترند. Retention، دسترسی و Redaction را بر اساس طبقهبندی داده تنظیم کنید. Observability نباید به انبار دائمی داده شخصی تبدیل شود.
نسخه، سازگاری و پایان عمر API
هر تغییر Contract شکستن نیست. افزودن Field اختیاری معمولاً سازگار است، اما مصرفکنندهای که Parser سختگیر دارد شاید باز هم بشکند. حذف یا تغییر نوع Field، تنگکردن Enum، تغییر معنای پیشفرض و الزام Scope جدید معمولاً ناسازگارند. سازگاری را با Consumer واقعی و تست قرارداد بسنجید.
Version فقط عدد در URL نیست
Version میتواند در Path، Header یا Media type باشد، اما مهمتر از محل آن سیاست تغییر است: چه چیزی Major محسوب میشود؟ چند نسخه پشتیبانی میشود؟ Migration guide و محیط تست چیست؟ مصرف کدام Endpointها پایش میشود؟ چه کسی با مصرفکنندگان تماس میگیرد؟
RFC 9745 هدر Deprecation را برای اعلام زمان منسوخشدن منبع تعریف میکند و برای اعلام زمان توقف، استفاده از Sunset طبق RFC 8594 را توضیح میدهد. هدر بهتنهایی کافی نیست؛ Link به راهنمای مهاجرت، اعلان مستقیم، داشبورد مصرف و دوره همپوشانی لازم است.
مستندات و تجربه توسعهدهنده
مستند خوب باید زمان اولین درخواست موفق را کم کند و هنگام شکست، مسیر حل بدهد. Quickstart، Authentication، محیطها، مثالهای قابلاجرا، Error catalog، Rate limit، Idempotency، Webhook verification، Changelog و Status page اجزای پایهاند. نمونهها باید Secret جعلی و داده غیرواقعی داشته باشند.
SDK رسمی زمانی ارزش دارد که Versioning، Retry و Error mapping را استاندارد کند؛ SDK رهاشده بدهی مضاعف است. برای هر زبان، حداقل نسخه Runtime، سیاست Release و End-of-support را روشن کنید. API Explorer را به محیط امن و داده آزمایشی محدود کنید.
حاکمیت API: موجودی، مالک و مرز مسئولیت
Shadow API و Endpoint فراموششده از نبود Inventory میآیند. برای هر API، مالک فنی و محصول، مصرفکنندگان، سطح Exposure، طبقه داده، روش Auth، SLO، نسخه، Dependencyها و تاریخ بازبینی ثبت شود. Gateway discovery کمک میکند، اما Source of truth باید با Repository و Deploy pipeline همراستا باشد.
| دارایی | مالک | Evidence لازم | تناوب بازبینی |
|---|---|---|---|
| Contract | Product + Tech lead | OpenAPI/IDL، Review و Compatibility test | هر تغییر |
| دسترسی | Service owner + Security | Threat model، Scope matrix و تست BOLA | فصلی و هر قابلیت حساس |
| قابلیت اطمینان | Service owner/SRE | SLO، Runbook، Alert و بازیابی آزمایششده | ماهانه/پس از Incident |
| سرویس ثالث | Business + Legal + Tech | Eligibility، DPA/Terms، SLA و Exit test | حداقل سالانه |
| چرخه عمر | API product owner | Usage، Changelog، Deprecation و Migration | هر Release |
سناریوی کامل: سفارش فروشگاه ایرانی
فرض کنید کاربر سبدی به مبلغ ۱۲۵ هزار تومان دارد. Frontend مبلغ را منبع حقیقت نمیداند؛ Backend قیمت را از Snapshot معتبر سبد محاسبه و در قرارداد پرداخت به 1,250,000 IRR تبدیل میکند. Client برای ساخت Payment attempt یک Idempotency key میفرستد. Backend رکورد را اتمیک میسازد و شناسه داخلی را پیش از تماس با درگاه نگه میدارد.
- اگر درگاه پاسخ موفق دهد، Reference در همان Workflow ثبت و وضعیت به
pending_user_actionمیرود. - اگر Timeout شود، وضعیت
unknownاست؛ همان Intent دوباره با کلید ثابت یا طبق قرارداد ارائهدهنده بررسی میشود، نه با ساخت کورکورانه Intent تازه. - Callback امضاشده دریافت و Deduplicate میشود؛ مبلغ، Merchant و شناسه سفارش تطبیق میخورد.
- Backend مستقل Verify میکند؛ فقط نتیجه تأییدشده، سفارش را Paid میکند.
- Outbox رویداد
PaymentVerifiedرا منتشر میکند؛ موجودی و ارسال Consumerهای Idempotent دارند. - اگر سرویس حمل قطع باشد، سفارش Paid باقی میماند و Fulfillment با Queue/Retry و هشدار جدا ادامه مییابد.
- Job تطبیق، Paymentهای Unknown/Pending قدیمی را بررسی میکند و داشبورد اختلاف مالی دارد.
این طراحی اثر خطای شبکه را از اثر تجاری جدا میکند. همچنین دادههای API میتوانند به سنجش قیف و تشخیص Drop-off کمک کنند، به شرط آنکه Eventها تعریف و کنترل کیفیت شوند؛ برای لایه تحلیلی، راهنمای تحلیل داده بازاریابی و Warehouse مفید است.
برنامه ۳۰، ۶۰ و ۹۰ روزه بهبود API موجود
روز ۱ تا ۳۰: موجودی و ریسکهای حیاتی
- APIها، Endpointها، مالک، مصرفکننده، داده حساس و Dependency ثالث را فهرست کنید.
- عملیات مالی/حساس را برای Idempotency، Timeout و مجوز سطح Object بررسی کنید.
- Secretهای Hard-coded، Logهای حساس و Endpointهای بدون مالک را اصلاح یا مهار کنید.
- یک Error format، Correlation ID و داشبورد Rate/Error/Duration پایه بسازید.
روز ۳۱ تا ۶۰: قرارداد و تست
- OpenAPI/IDL را با Implementation همراستا و در CI Lint/Validate کنید.
- ماتریس Role × Action × Object و تستهای منفی Authorization بسازید.
- Contract test برای مصرفکنندگان حیاتی و Failure test برای درگاه/پیامک اجرا کنید.
- Retry budget، Deadline و Runbook وضعیت نامعلوم را مستند کنید.
روز ۶۱ تا ۹۰: چرخه عمر و SLO
- SLI/SLO مصرفکنندهمحور، Alert قابلاقدام و Error budget تعریف کنید.
- سیاست Version، Deprecation، Sunset و Migration را اجرا و یک سناریو را تمرین کنید.
- ارزیابی سرویس ثالث، برنامه خروج و بازیابی/Reconciliation را آزمایش کنید.
- مرور معماری و امنیت را به Definition of Done قابلیتهای API تبدیل کنید.
چکلیست طراحی و ممیزی API
- □ Use case، مصرفکننده، مالک و سطح Exposure مشخص است.
- □ قرارداد ماشینخوان Schema، مثال، وضعیتها و Headerها را پوشش میدهد.
- □ واحد پول، زمان، Nullability، Enum و Encoding مبهم نیست.
- □ عملیات حساس Idempotency، قید یکتا و Reconciliation دارد.
- □ Authentication از Authorization سطح Function/Object/Property جداست.
- □ Token و Secret کماختیار، قابل Rotation و خارج از Log/URL هستند.
- □ CORS جای کنترل دسترسی یا CSRF defense فرض نشده است.
- □ Timeout، Deadline، Retry budget، Backoff/Jitter و Circuit policy تعریف شدهاند.
- □ Webhook امضا، Timestamp، Replay defense و Deduplication دارد.
- □ Pagination، Filter، Rate limit و سقف Payload جلوی مصرف بیرویه را میگیرند.
- □ Cache خصوصی/عمومی، ETag و Invalidation آگاهانه طراحی شده است.
- □ Contract، Integration، Authorization، Load و Failure test در Pipeline وجود دارد.
- □ RED metrics، Trace، Correlation ID، SLO و Runbook قابل استفادهاند.
- □ سرویس خارجی از نظر Eligibility، داده، SLA، هزینه و Exit بررسی شده است.
- □ Changelog، Version، Deprecation/Sunset و راهنمای Migration دارید.
پرسشهای متداول
API چیست و چه فرقی با Web Service دارد؟
API قرارداد استفاده از قابلیت نرمافزار است و میتواند محلی یا شبکهای باشد. Web Service نوعی API شبکهای است؛ در کاربرد امروز، Web API معمولاً با HTTP ارائه میشود. پس هر Web Service یک API است، اما هر API الزاماً Web Service نیست.
REST بهتر است یا GraphQL؟
برنده عمومی وجود ندارد. HTTP Resource-oriented برای API عمومی، Cache و ابزارهای متداول نقطه شروع خوبی است؛ GraphQL برای UIهای متنوع و انتخاب Fieldها ارزش دارد، اما مجوز، پیچیدگی Query، Cache و عملیات دقیقتری میخواهد. تصمیم را با مصرفکننده، مدل داده و توان عملیاتی بگیرید.
آیا API key برای احراز هویت کاربر کافی است؟
معمولاً خیر. API key بیشتر Client یا پروژه را معرفی میکند. هویت کاربر و مجوز دسترسی به Object باید با سازوکار متناسب مانند Session یا OAuth/OIDC و کنترل Authorization سمت سرور اجرا شود. کلید نیز باید Scope، Rotation و محدودیت مصرف داشته باشد.
چرا برای پرداخت به Idempotency key نیاز داریم؟
چون Timeout معلوم نمیکند عملیات انجام شده یا نه. کلید Idempotency تلاشهای یک Intent را به هم وصل میکند تا Retry همان اثر تجاری را دوباره نسازد. ذخیره اتمیک، Hash پارامتر، TTL، Constraint و Reconciliation مکمل آن هستند.
حداقل مستندات یک API حرفهای چیست؟
قرارداد ماشینخوان OpenAPI/IDL، Quickstart، Authentication و Scope، مثالهای موفق و خطا، Error catalog، Pagination و Rate limit، Idempotency/Retry، Webhook verification، Changelog، SLO/Status و سیاست نسخه و پایان عمر حداقلهای عملیاند.
جمعبندی
API خوب صرفاً Endpointی نیست که JSON برگرداند. قرارداد آن باید برای انسان و ماشین روشن، در برابر تغییر قابلآزمایش، در سطح Object مجاز، در شکست قابلبازیابی و در عملیات قابلمشاهده باشد. برای API بعدی، از انتخاب Framework شروع نکنید: Intent تجاری، مالک، داده، اثر تکرار، مرز اعتماد و Failure mode را بنویسید؛ سپس سبک و ابزار را انتخاب کنید.
اقدام بعدی: یک Endpoint مالی یا حساس را انتخاب کنید و پنج Evidence جمع کنید: Contract، ماتریس مجوز، تست Retry/Idempotency، Trace یک درخواست و Runbook وضعیت نامعلوم. هر موردی که وجود ندارد، ریسک واقعی است—نه صرفاً بدهی مستندات.






