پرش به محتویات

قرارداد 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 و Accept Validate می‌شوند. 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 دقیق است؛ داده شخصی یا Auth private یا 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 عبور نمی‌کنند.

منابع رسمی