قرارداد API و اتصال ضعیف
REST منبع حقیقت نسخه اول است. قراردادها برای Web و Mobile یکسان، نسخهدار و با OpenAPI 3.1 توصیف میشوند. طراحی باید روی شبکه کند، پرنوسان و دارای قطع موقت در ایران قابل استفاده بماند.
اصول قرارداد
- Base Path عمومی
/api/v1است و Version در URL فقط برای تغییر ناسازگار افزایش مییابد. - Resourceها اسم جمع دارند، رفتار با HTTP Method بیان میشود و Verb در URL فقط برای Command دامنهای واقعی مجاز است.
- JSON از
camelCaseو زمان از RFC 3339 با UTC استفاده میکند؛ مبلغ همیشه Integer و واحد آن در Schema «ریال» ثبت میشود. Content-TypeوAcceptValidate میشوند. Charset ضمنی UTF-8 است.- OpenAPI منبع قرارداد Client است و تغییر کد بدون تغییر همزمان Contract مجاز نیست.
- Contract و پیادهسازی دستی Server با Test قرارداد و بررسی Breaking Change در CI کنترل میشوند.
- Field جدید Optional است؛ حذف یا تغییر معنای Field در همان Version ممنوع است.
پیادهسازی دستی OpenAPI
رویکرد پروژه Contract-first با Transport دستی است. فایل OpenAPI قرارداد بیرونی را تعریف میکند، اما Route، DTO، Decode، Validation ساختاری، Handler و Encode پاسخ در Feature بهصورت دستی و خوانا نوشته میشوند.
- فایلهای
api/openapi/*.yamlمنبع حقیقت قرارداد هستند؛ تغییر Endpoint ابتدا در Contract و سپس در پیادهسازی دستی انجام میشود. - Code Generator برای Server، Client یا Glue مرز HTTP در Repository بکاند استفاده نمیشود.
- Routeها با Patternهای Method-aware در
http.ServeMuxو Handler استانداردnet/httpثبت میشوند؛ Router و Web Framework بیرونی استفاده نمیشود. - Request ID، Panic Recovery، Security Header، Body Limit، Timeout، Logging و پاسخ عمومی 404/405 یکبار در Platform پیادهسازی میشوند و Feature آنها را تکرار نمیکند.
- DTO حملونقل از Entity و Value Object دامنه جدا میماند و Mapping صریح در HTTP Adapter انجام میشود.
- Required بودن Field و Header، Length، Format، Media Type، رد Unknown Field، Status، Header پاسخ و Problem Details باید Test رفتاری متناظر داشته باشند.
- Domain، Use Case، Repository، Query و Migration همگی دستی، کوچک و مستقل از Transport هستند.
- Lint قرارداد و Breaking-change check پیش از Merge الزامی است و جایگزین Test رفتاری، Validation دامنه یا Review امنیتی نیست.
Semantics و Status
| وضعیت | کاربرد |
|---|---|
200 |
خواندن یا Command موفق با پاسخ |
201 |
Resource ساختهشده با Header Location |
202 |
کار Durable پذیرفتهشده ولی هنوز کامل نشده است |
204 |
موفق بدون Body |
400 |
Syntax یا ورودی نامعتبر عمومی |
401 |
احراز هویت نامعتبر یا منقضی |
403 |
هویت معتبر ولی Permission ناکافی |
404 |
Resource در Scope مجاز پیدا نشد؛ برای جلوگیری از افشا نیز قابل استفاده است |
409 |
Conflict، Version یا State Transition نامعتبر |
422 |
Validation معنایی قابل اصلاح توسط Client |
429 |
Rate Limit همراه Retry-After |
503 |
Dependency یا ظرفیت موقتا در دسترس نیست |
Retry روی 4xx بهجز 408 و 429 انجام نمیشود. 5xx فقط برای عملیات Safe یا Idempotent و با Backoff، Jitter و سقف محدود Retry میشود.
قالب خطا
خطا از application/problem+json مطابق RFC 9457 استفاده میکند:
{
"type": "https://api.tripylon.test/problems/validation-error",
"title": "درخواست معتبر نیست",
"status": 422,
"code": "validation_error",
"requestId": "01JEXAMPLE0000000000000000",
"errors": [
{
"field": "mobile",
"code": "invalid_mobile"
}
]
}
code برای منطق Client پایدار است؛ title و پیام قابل ترجمهاند. Stack، SQL، نام Table، Provider Response، Token و PII در پاسخ قرار نمیگیرد. URI نوع خطا باید به مستند قابل نگهداری Resolve شود.
Validation و امنیت ورودی
- Body، Header، Query و Path با Allowlist، طول و Range صریح Validate میشوند.
- Body عمومی بیش از 256 KiB پیش از Parse رد میشود؛ Upload فایل مسیر جدا دارد.
- JSON ناشناخته برای Commandهای حساس رد میشود تا اشتباه Client پنهان نماند.
- Parser زمان، موبایل، UUID و Enum مرکزی و دارای آزمون Fuzz است.
- CORS فقط Originهای تصویبشده را Allow میکند؛
*همراه Credential ممنوع است. - Headerهای امنیتی، TLS و Limit درخواست در Reverse Proxy و App هر دو اعمال میشوند.
- تست باید ثابت کند Middleware Chain برای پاسخ موفق، خطای Feature، 404، 405 و Panic همان Header و Problem Details امن را حفظ میکند.
Pagination و Filter
- فهرستهای رشدکننده Cursor-based هستند؛ Cursor Opaque و امضاشده یا غیرقابل دستکاری است.
- Sort همیشه پایدار و دارای Tie-breaker شناسه است؛ نمونه
(created_at DESC, id DESC). - اندازه پیشفرض صفحه 20 و حداکثر 100 است، مگر OpenAPI یک Feature مقدار کوچکتری ثبت کند.
- پاسخ شامل
itemsوnextCursorاست و Total Count فقط وقتی نیاز محصول و هزینه Query آن روشن باشد محاسبه میشود. - Filter و Sort Allowlist دارند؛ نام Column خام از Client وارد SQL نمیشود.
Idempotency و Concurrency
Idempotency-Key برای ساخت پرداخت، رزرو، Ticket، درخواست مالی، ارسال Command حساس و هر عملیات قابل Retry اجباری است.
- Key حداقل 128 بیت Entropy دارد و Scope آن Account، Tenant و Operation است.
- Server Hash درخواست را با Key ذخیره میکند؛ استفاده همان Key با Payload متفاوت
409میدهد. - پاسخ موفق و خطای قطعی برای Window مستند Replay میشود؛ عملیات در حال اجرا وضعیت قابل تشخیص دارد.
- Unique Constraint آخرین دفاع در برابر Duplicate است.
- Update رقابتی از Version یا
If-Matchو ETag استفاده میکند و Conflict را409یا412برمیگرداند. - Retry Client بدون Idempotency روی POST مالی یا اثرگذار ممنوع است.
بهینهسازی برای اینترنت ضعیف
- Payload فقط Field لازم را برمیگرداند و Endpointهای لیست Summary را از Detail جدا میکنند.
- Compression در Reverse Proxy برای JSON و متن فعال است؛ فایل از Object Storage یا CDN تحویل میشود.
- پاسخ قابل Cache دارای
ETagوCache-Controlدقیق است؛ داده شخصی یا Authprivateیاno-storeمیگیرد. - Client با
If-None-Matchپاسخ304میگیرد و Sync افزایشی با Cursor یاupdatedSinceانجام میشود. - Command سریع پس از ثبت Durable پاسخ میدهد و انتظار SMS، Push، Thumbnail یا Webhook را نگه نمیدارد.
- Upload از جریان دو مرحلهای Initiate/Complete و در صورت پشتیبانی Multipart Resume استفاده میکند.
- Mobile داده آخر موفق و صف Commandهای مجاز آفلاین را نگه میدارد؛ Server تعارض را با Version حل میکند.
- Retry Storm با Exponential Backoff، Full Jitter و احترام به
Retry-Afterکنترل میشود.
Timeout و بودجه پاسخ
| مسیر | هدف Server-side در p95 | توضیح |
|---|---|---|
| خواندن عادی | حداکثر 200ms | بدون زمان شبکه Client و Provider بیرونی |
| نوشتن عادی | حداکثر 400ms | شامل Commit، بدون Side Effect غیرهمزمان |
| درخواست OTP | حداکثر 400ms | تا ثبت Challenge و Job، نه تحویل SMS |
| Query پایگاه داده | حداکثر 100ms | بیشتر از آن Slow Query است |
هدفها با Load Test روی داده نماینده سنجیده میشوند و ضمانت کیفیت اینترنت Client نیستند. Endpoint خاص با بودجه متفاوت باید در OpenAPI و داشبورد SLO خود ثبت شود.
SSE و Push
- SSE فقط برای اعلان و تغییر وضعیت Foreground وب است؛ Chat و WebSocket در نسخه اول وجود ندارد.
- Event دارای
id،type،occurredAtو Payload حداقلی است. Client پس از دریافت، Detail را از REST میخواند. - Server
Last-Event-IDرا برای Resume میپذیرد و Heartbeat دورهای میفرستد. - Client با Backoff و Jitter Reconnect و در شکست طولانی از Polling کمفشار استفاده میکند.
- Message Center یا Notification Inbox منبع Canonical است؛ از دست رفتن Push یا SSE نباید داده را از بین ببرد.
- Mobile برای Background از Push استفاده میکند و پس از بازشدن App وضعیت را از REST Sync میکند.
Rate Limit
Rate Limit بر اساس هزینه و ریسک Endpoint تعریف میشود، نه یک عدد یکسان برای کل API. کلید میتواند ترکیبی از IP، Account، Device، Tenant و Operation باشد. محدودیت Auth و Provider سختتر است. پاسخ استاندارد 429، Retry-After و کد پایدار دارد و مقدار Limit قابل پایش ولی قابل دورزدن با Header جعلی نیست.
معیار پذیرش
- OpenAPI 3.1 بدون خطا Lint و Breaking Change آن در CI بررسی میشود.
- پیادهسازی دستی HTTP با Contract Test، Required Field و Header، Media Type، Schema بسته، Status و Response Headerهای OpenAPI را پوشش میدهد.
- Test قرارداد، Status و Problem Details را برای مسیر موفق و خطا پوشش میدهد.
- عملیات اثرگذار آزمون Retry، Duplicate و Payload متفاوت با Key یکسان دارد.
- تست شبکه شبیهسازیشده قطع، Timeout، Latency بالا و Packet Loss را پوشش میدهد.
- پاسخهای فهرست در اندازه صفحه پیشفرض از بودجه Payload و Latency عبور نمیکنند.