REST API-ներ
Նախագծեք API-ներ, որոնք կանխատեսելի են, անվտանգ կրկնելու համար և հաճելի ինտեգրելու համար։
30 վայրկյանում
REST API-ն ձեր տվյալները ներկայացնում է որպես ռեսուրսներ URL-ներում, որոնք client-ները կարդում և փոխում են ստանդարտ HTTP մեթոդներով։ Լավ API-ները կանխատեսելի են՝ հետևողական անուններ, ճիշտ status code-եր, հստակ սխալներ, pagination, տարբերակավորում և փաստաթղթավորում։ Լավ արված լինելու դեպքում վեբ, մոբայլ և գործընկերների հավելվածները կարող են ինտեգրվել արագ և անվտանգ։
Ինչու եմ օգտագործում
- Նախագծում և կառուցում եմ REST API-ներ, որոնք մեկ բեքենդից սպասարկում են կայքեր, ադմին պանելներ և մոբայլ հավելվածներ։
- Կանխատեսելի API-ն ավելի շատ ժամանակ է խնայում, քան ցանկացած framework․ ֆրոնտենդ և մոբայլ ծրագրավորողները դադարում են գուշակել։
- Վճարումներին և պատվերներին պետք են idempotency և հստակ սխալներ․ երկուսն էլ ներդրել եմ production API-ներում։
- Գործընկերներն ավելի արագ են ինտեգրվում, երբ API-ն տարբերակավորված է, արդարացիորեն rate-limited է և փաստաթղթավորված է OpenAPI-ով։
Ինչպես է աշխատում
01Ռեսուրսներ և URL-ներ
REST-ը (Representational State Transfer) ձեր domain-ը մոդելավորում է որպես ռեսուրսներ՝ գոյականներ, ինչպիսիք են products, orders և customers։ Յուրաքանչյուր ռեսուրս ունի URL, իսկ մեթոդն ասում է, թե ինչ անել դրա հետ։ Օգտագործեք հոգնակի գոյականներ և ID-ներ՝ /orders, /orders/1001, /orders/1001/items։ Խուսափեք path-երում բայերից, ինչպիսիք են /getOrders կամ /createOrder․ մեթոդն արդեն դա ասում է։ Nesting-ը պահեք մակերեսային (սովորաբար մեկ մակարդակը բավական է)։ Այն գործողությունների համար, որոնք պարզորեն չեն համապատասխանում CRUD-ին, մոդելավորեք դրանք որպես sub-resource-ներ կամ վիճակի փոփոխություններ՝ POST /orders/1001/cancellation կամ PATCH /orders/1001 status դաշտով։ Օգտագործեք հետևողական անվանում (camelCase կամ snake_case, ընտրեք մեկը), ISO 8601 ամսաթվեր UTC-ով և ID-ներ, որոնք չեն բացահայտում բիզնեսի ծավալը, որտեղ դա կարևոր է։ Անալոգիա՝ ռեսուրսները շենքի սենյակներն են, URL-ները՝ նրանց հասցեները, իսկ մեթոդները՝ այն, ինչ անում եք սենյակում։
GET /v1/orders?status=pending&page=2 # list
POST /v1/orders # create
GET /v1/orders/1001 # read one
PATCH /v1/orders/1001 # partial update
DELETE /v1/orders/1001 # delete
GET /v1/customers/77/orders # related collection
POST /v1/orders/1001/refunds # action as sub-resource
02Մեթոդներ, status code-եր և սխալի ֆորմատ
Գործողությունները համապատասխանեցրեք մեթոդներին, իսկ արդյունքները՝ status code-երին։ Ստեղծումը վերադարձնում է 201 Created՝ Location header-ով և նոր ռեսուրսով։ Առանց բովանդակության հաջողությունը վերադարձնում է 204։ Վալիդացիայի սխալները վերադարձնում են 422 (կամ 400՝ սխալ ձևավորված JSON-ի համար)՝ դաշտերի մակարդակի մանրամասներով։ Նույնականացված չէ՝ 401, թույլատրված չէ՝ 403, բացակայում է՝ 404, տարբերակի կամ վիճակի կոնֆլիկտ՝ 409, չափազանց շատ հարցումներ՝ 429, իսկ սերվերի բագերը՝ 500։ Ընտրեք սխալի մեկ ֆորմատ և օգտագործեք այն ամենուր։ RFC 9457 Problem Details-ը (application/problem+json) լավ ստանդարտ է՝ type, title, status, detail, գումարած ձեր սեփական դաշտերը, օրինակ՝ սխալներն ըստ դաշտերի և request ID՝ support-ի համար։ Production-ի սխալներում երբեք մի բացահայտեք stack trace-եր կամ SQL։
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation",
"title": "The request has invalid fields.",
"status": 422,
"errors": { "quantity": ["Must be at least 1."] },
"requestId": "req_7f3a9c"
}
03Pagination, ֆիլտրում, դասավորում
Երբեք մի վերադարձրեք անսահմանափակ ցուցակներ։ Offset pagination-ը (?page=3&per_page=20) պարզ է և թույլ է տալիս օգտատերերին ցատկել էջի, բայց մեծ աղյուսակներում դանդաղում է և կարող է բաց թողնել կամ կրկնել տարրեր, երբ տվյալները փոխվում են։ Cursor pagination-ը (?cursor=eyJpZCI6MTAwMH0&limit=20) օգտագործում է անթափանց ցուցիչ, օրինակ՝ վերջին տեսած ID-ն և created_at-ը․ այն արագ և կայուն է feed-երի, infinite scroll-ի և sync-երի համար, բայց չի կարող ցատկել 50-րդ էջ։ Վերադարձրեք մետատվյալներ կամ հղումներ, որպեսզի client-ներն իրենք URL-ներ չկառուցեն։ Ֆիլտրման համար օգտագործեք հստակ query պարամետրեր (?status=paid&created_from=2026-01-01), դասավորման համար՝ ?sort=-created_at,total, իսկ որոնման համար՝ ?q=։ Սահմանափակեք էջի առավելագույն չափը և ֆիլտրումն ու դասավորումը թույլատրեք միայն index արված դաշտերով։
GET /v1/orders?status=paid&sort=-created_at&limit=20&cursor=eyJpZCI6MTAyMH0
{
"data": [ { "id": 1019, "total": 12500, "status": "paid" } ],
"meta": { "limit": 20, "hasMore": true },
"links": { "next": "/v1/orders?status=paid&sort=-created_at&limit=20&cursor=eyJpZCI6MTAwMH0" }
}
04Տարբերակավորում և անվտանգ զարգացում
Client-ները, հատկապես մոբայլ հավելվածները, որոնք օգտատերերը չեն թարմացնում, տարիներ շարունակ կախված են ձեր API-ից։ Ավելացնող փոփոխություններն անվտանգ են՝ նոր endpoint-ներ, նոր ոչ պարտադիր դաշտեր, նոր enum արժեքներ, եթե client-ներին ասվել է հանդուրժել անհայտները։ Կոտրող փոփոխությունները ներառում են դաշտերի հեռացումը կամ վերանվանումը, տիպերի կամ իմաստի փոփոխությունը և ոչ պարտադիր մուտքը պարտադիր դարձնելը։ Կոտրող փոփոխությունների համար ներմուծեք նոր տարբերակ։ URL-ով տարբերակավորումը (/v1, /v2) ամենատեսանելին է և ամենահեշտը route անելու, քեշավորելու և debug անելու համար, header-ով կամ media-type-ով տարբերակավորումը URL-ները պահում է մաքուր, բայց ավելի դժվար է թեստավորել։ Որոշ API-ներ header-ում օգտագործում են ամսաթվով տարբերակներ։ Ինչ էլ ընտրեք, deprecation-ների մասին հայտարարեք ամսաթվերով, ուղարկեք Deprecation և Sunset header-ներ և հետևեք, թե որ client-ներն են դեռ կանչում հին տարբերակները, նախքան դրանք անջատելը։ Զուգակցեք սա մոբայլ հավելվածներում forced-update մեխանիզմի հետ։
05Նույնականացում, idempotency և rate limit-եր
Auth-ն ընտրեք ըստ client-ի տեսակի։ Նույն կայքի բրաուզերային հավելվածներ՝ անվտանգ HttpOnly session cookie-ներ գումարած CSRF պաշտպանություն։ Մոբայլ և SPA client-ներ՝ կարճաժամկետ access token-ներ (հաճախ JWT) refresh token-ներով, երրորդ կողմի login-ի համար՝ OAuth 2 flow-երով PKCE-ով։ Server-to-server գործընկերներ՝ սահմանափակ իրավունքներով API key-եր կամ OAuth client credentials, որոնք ուղարկվում են header-ներով, երբեք՝ URL-ներով։ Ցանցերը խափանվում են, ուստի գրելու գործողությունները դարձրեք անվտանգ կրկնելու համար․ client-ներն ուղարկում են Idempotency-Key header եզակի արժեքով, սերվերը պահում է key-ը արդյունքի հետ և key-ի կրկնության դեպքում վերադարձնում նույն պատասխանը՝ երկու անգամ գանձելու կամ պատվիրելու փոխարեն։ Պաշտպանեք API-ն rate limit-երով ըստ key-ի, օգտատիրոջ կամ IP-ի, պատասխանեք 429-ով Retry-After-ի հետ և սահմանափակումները ցույց տվեք header-ներում, որպեսզի client-ները կարողանան դանդաղեցնել։
POST /v1/payments HTTP/1.1
Authorization: Bearer <access_token>
Idempotency-Key: 6f1c2d4e-8a7b-4c3d-9e2f-1a2b3c4d5e6f
Content-Type: application/json
{"orderId": 1001, "amount": 12500, "currency": "AMD"}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Policy: "default";q=100;w=60
06Փաստաթղթավորում OpenAPI-ով
OpenAPI-ն (3.1 տարբերակն ընթացիկն է և համահունչ է JSON Schema-ին) YAML կամ JSON ֆայլում նկարագրում է ձեր endpoint-ները, պարամետրերը, հարցման և պատասխանի body-ները, սխալները և auth-ը։ Այդ մեկ աղբյուրից ստանում եք ինտերակտիվ փաստաթղթեր (Swagger UI, Redoc, Scalar), գեներացված տիպավորված client-ներ TypeScript-ի, Kotlin-ի կամ Swift-ի համար, mock սերվերներ և contract test-եր։ Գեներացրեք այն կոդի annotation-ներից կամ decorator-ներից (NestJS-ն ունի ներկառուցված աջակցություն, Laravel-ն ունի փաթեթներ, օրինակ՝ Scramble), որպեսզի այն երբեք չշեղվի իրականությունից։ Ավելացրեք օրինակներ յուրաքանչյուր հարցման և սխալի համար, նկարագրեք pagination-ը և rate limit-երը և հրապարակեք changelog։ Լավ փաստաթղթերը feature են․ դրանք ինտեգրման ժամանակը շաբաթներից կրճատում են օրերի։
paths:
/v1/orders/{id}:
get:
summary: Get one order
parameters:
- { name: id, in: path, required: true, schema: { type: integer } }
responses:
"200":
description: The order
content:
application/json:
schema: { $ref: "#/components/schemas/Order" }
"404": { $ref: "#/components/responses/NotFound" }
07REST vs GraphQL vs gRPC vs WebSockets vs webhooks
Դրանք լուծում են տարբեր խնդիրներ և հաճախ ապրում են կողք կողքի։ REST-ը լռելյայն ընտրությունն է հանրային և մոբայլ API-ների համար՝ պարզ, քեշավորվող, հասկանալի յուրաքանչյուր գործիքի համար։ GraphQL-ը թույլ է տալիս client-ներին մեկ endpoint-ից խնդրել հենց այն դաշտերը, որոնք իրենց պետք են, ինչն օգնում է, երբ շատ տարբեր էկրաններ միավորում են տվյալներ բազմաթիվ աղբյուրներից․ գինը բարդ քեշավորումն է, authorization-ն ըստ դաշտի և query-ի արժեքի սահմանափակումները։ gRPC-ն օգտագործում է Protocol Buffers HTTP/2-ի վրա՝ գեներացված client-ներով և streaming-ով․ հիանալի է արագ, տիպավորված service-to-service կանչերի համար, անհարմար է բրաուզերներում։ WebSockets-ը բաց է պահում երկկողմանի կապ չատի, պատվերի live կարգավիճակի և dashboard-ների համար։ Webhook-ները հակադարձ API-ներ են․ ձեր սերվերը կանչում է գործընկերոջ URL-ը, երբ ինչ-որ բան է տեղի ունենում, օրինակ՝ վճարումը հաջողվել է․ ստորագրեք դրանք HMAC-ով, կրկնեք backoff-ով և ստացողներին դարձրեք idempotent։
- Հանրային, գործընկերային կամ մոբայլ API՝ REST + OpenAPI։
- Շատ client-ներ՝ տվյալների շատ տարբեր կարիքներով՝ դիտարկեք GraphQL։
- Ներքին բարձր թողունակությամբ ծառայություններ՝ gRPC։
- Live թարմացումներ օգտատերերին՝ WebSockets կամ SSE։ Իրադարձություններ այլ համակարգերին՝ webhook-ներ։
Օգտագործեք, երբ
- Հանրային, գործընկերային և մոբայլ API-ներ, որոնք պետք է հեշտ լինեն հասկանալու և ինտեգրելու համար։
- CRUD-ով հագեցած back office-ներ և առցանց խանութներ։
- Երբ կարևոր են HTTP քեշավորումը, CDN-ները և ստանդարտ գործիքները։
- Որպես կայուն պայմանագիր բեքենդի և մի քանի ֆրոնտենդների միջև։
Մի՛ օգտագործեք, երբ
- Շատ «շատախոս» ներքին ծառայությունների կանչեր, որտեղ latency-ն և խիստ սխեմաներն ավելի կարևոր են՝ դիտարկեք gRPC։
- Real-time push դեպի client-ներ՝ polling-ի փոխարեն օգտագործեք WebSockets, SSE կամ push notification-ներ։
- Էկրաններ, որոնց պետք են բազմաթիվ աղբյուրներից շատ nested ռեսուրսներ մեկ հարցմամբ՝ GraphQL-ը կամ առանձին aggregate endpoint-ը կարող է ավելի պարզ լինել։
Այլընտրանքներ
- GraphQL
Client-ի ընտրած դաշտեր մեկ endpoint-ով․ ճկուն է, բայց քեշավորումն ու authorization-ն ավելի բարդ են։
- gRPC
Տիպավորված, արագ RPC HTTP/2-ի վրա՝ ներքին ծառայությունների համար։
- tRPC
End-to-end TypeScript տիպեր Node բեքենդի և TS ֆրոնտենդի միջև՝ առանց սխեմայի ֆայլի։
- WebSockets / webhooks
Push-ոճի հաղորդակցություն live թարմացումների և միջհամակարգային իրադարձությունների համար։
Լավագույն պրակտիկաներ
- Ամենուր օգտագործեք հոգնակի գոյականներ, հետևողական անվանում և ճիշտ status code-եր։
- Վերադարձրեք սխալի մեկ ֆորմատ (Problem Details)՝ դաշտերի սխալներով և request ID-ով։
- Pagination արեք յուրաքանչյուր ցուցակ, սահմանափակեք էջի չափը և ֆիլտրեք միայն index արված դաշտերով։
- Տարբերակավորեք առաջին օրվանից և deprecation-ների մասին հայտարարեք ամսաթվերով։
- Պահանջեք Idempotency-Key վճարումների և պատվերների ստեղծման վրա․ ավտոմատ կրկնեք միայն idempotent կանչերը։
- Rate-limit արեք ըստ key-ի կամ օգտատիրոջ և վերադարձրեք 429՝ Retry-After-ով։
- OpenAPI փաստաթղթերը գեներացրեք կոդից և հրապարակեք օրինակներ ու changelog։
Տարածված սխալներ
- Յուրաքանչյուր պատասխանի համար վերադարձնել 200 և սխալները թաքցնել body-ում։
- Տվյալների բազայի աղյուսակները բացահայտել մեկ առ մեկ՝ արտահոսելով ներքին դաշտեր և client-ներին կապելով սխեմային։
- Կոտրել մոբայլ client-ները՝ դաշտերը վերանվանելով առանց նոր տարբերակի։
- Անսահմանափակ ցուցակների endpoint-ներ, որոնք timeout են տալիս տվյալների աճի հետ։
- Ստուգել նույնականացումը, բայց ոչ authorization-ը, այնպես որ օգտատերերը կարող են կարդալ այլ օգտատերերի ռեսուրսները՝ ID-ն փոխելով։
Խորը վերլուծություն
08Authorization-ը API-ի թիվ մեկ ռիսկն է
OWASP API Security Top 10-ը (2023) առաջին տեղում դնում է Broken Object Level Authorization-ը. API-ն ստուգում է, որ օգտատերը login է արել, բայց ոչ այն, որ նրա պահանջած պատվերը, հաշիվ-ապրանքագիրը կամ ֆայլը իրեն է պատկանում, ուստի /orders/1001-ը /orders/1002 փոխելը բացահայտում է ուրիշի տվյալները։ Մոտիկից հետևում են broken function-level authorization-ը (սովորական օգտատերը կանչում է ադմինի endpoint) և broken object property level authorization-ը (mass assignment, երբ client-ը PATCH body-ում սահմանում է role կամ price)։ Հուսալի pattern-ը յուրաքանչյուր query-ն կանչողով սահմանափակելն է, ոչ թե հետո ստուգելը. բեռնեք օգտատիրոջ կամ tenant-ի relationship-ի միջոցով, որպեսզի օտար ID-ն պարզապես ոչինչ չգտնի և վերադարձնի 404։ Յուրաքանչյուր endpoint-ի համար whitelist արեք գրվող դաշտերը և օգտագործեք policy-ներ կամ guard-եր, որոնք աշխատում են ամեն route-ում։ Պատահական կամ prefix-ով ID-ները դժվարացնում են գուշակելը, բայց երբեք չեն փոխարինում authorization-ին։
// Laravel: scope by owner, then authorize with a policy
public function show(Request $request, int $id)
{
$order = $request->user()->orders()->findOrFail($id); // 404 for others
$this->authorize('view', $order);
return new OrderResource($order);
}
// Only allow known fields to be written
$data = $request->validate([
'note' => ['nullable', 'string', 'max:500'],
'quantity' => ['required', 'integer', 'min:1'],
]);
09Երկարատև գործողություններ և հուսալի իրադարձություններ
Որոշ հարցումներ չեն կարող ավարտվել սովորական timeout-ի ընթացքում. export-ներ, import-ներ, video-ի մշակում, զանգվածային թարմացումներ։ Կապը բաց պահելու փոխարեն ընդունեք աշխատանքը և վերադարձրեք 202 Accepted՝ Location header-ով, որը մատնացույց է անում status ռեսուրս, օրինակ՝ /v1/jobs/77։ Client-ը polling է անում այդ ռեսուրսը կամ բաժանորդագրվում webhook-ի, և վերջում անցնում է արդյունքի հղումով։ Հարակից խնդիր է իրադարձությունների հուսալի հրապարակումը։ Եթե կոդը պահպանում է պատվերը, հետո ուղարկում webhook, երկու քայլերի միջև crash-ը կորցնում է իրադարձությունը. նախ ուղարկելը, հետո պահպանման ձախողումը ստեղծում է «ուրվական» իրադարձություն։ Transactional outbox pattern-ը սա լուծում է. պատվերը և outbox տողը գրեք նույն database transaction-ում, և թողեք, որ worker-ը outbox տողերը առաքի retry-ներով։ Consumer-ները դեռ պետք է deduplicate անեն ըստ event ID-ի, քանի որ առաքումը at least once է։
POST /v1/exports HTTP/1.1
Content-Type: application/json
{"type": "orders", "from": "2026-10-01", "to": "2026-10-31"}
HTTP/1.1 202 Accepted
Location: /v1/jobs/77
GET /v1/jobs/77 -> {"status": "running", "progress": 40}
GET /v1/jobs/77 -> {"status": "done", "resultUrl": "/v1/exports/77/file"}
10Rate limiting-ի ալգորիթմներ
Fixed window-ը հաշվում է հարցումները ըստ օրացուցային րոպեի. պարզ է, բայց սահմանին թույլ է տալիս կրկնակի burst-եր (100՝ 12:00:59-ին և 100՝ 12:01:00-ին)։ Sliding window-ը հաշվի է առնում նախորդ պատուհանի քանակը՝ դա հարթելու համար։ Token bucket-ը յուրաքանչյուր client-ի տալիս է «դույլ», որը լցվում է կայուն արագությամբ մինչև առավելագույնը, ուստի կարճ burst-երը թույլատրվում են, իսկ երկարաժամկետ արագությունը մնում է ֆիքսված. սա համապատասխանում է իրական client-ների վարքագծին։ Counter-ները պահեք Redis-ում ատոմար գործողություններով, որպեսզի բոլոր app սերվերները կիսեն դրանք։ Key-երը ընտրեք զգույշ. ըստ API key-ի՝ գործընկերների համար, ըստ օգտատիրոջ՝ login արած տրաֆիկի համար, ըստ IP-ի՝ միայն որպես վերջին միջոց, քանի որ շատ օգտատերեր կարող են կիսել մեկ IP NAT-ի կամ մոբայլ օպերատորի հետևում։ Վերադարձրեք 429՝ Retry-After-ով, և թանկ կամ զգայուն endpoint-ների համար, ինչպես search-ը, login-ը և export-ները, օգտագործեք առանձին, ավելի խիստ limit-ներ։
// Laravel: named limiters, applied with ->middleware('throttle:api')
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(120)->by('user:'.$request->user()->id)
: Limit::perMinute(30)->by('ip:'.$request->ip());
});
RateLimiter::for('login', fn (Request $request) =>
Limit::perMinute(5)->by($request->input('email').'|'.$request->ip())
);
11Performance. payload-ի ձևը, N+1 և քեշավորում
Դանդաղ API-ների մեծ մասը դանդաղ է տվյալների բազայում։ List endpoint-ը, որը բեռնում է 50 պատվեր, հետո յուրաքանչյուրի համար query է անում հաճախորդին, կատարում է 51 query. սա N+1 խնդիրն է։ Eager-load արեք relation-ները, select արեք միայն անհրաժեշտ սյունակները և ավելացրեք index-ներ, որոնք համապատասխանում են ֆիլտրերին և դասավորությանը. development-ում log արեք query-ների քանակը յուրաքանչյուր հարցման համար։ Payload-ները ձևավորեք client-ի համար. sparse fieldset-ները (?fields=id,total,status) կամ include պարամետրերը (?include=customer) թույլ են տալիս մեկ endpoint-ին սպասարկել և՛ կոմպակտ ցուցակ, և՛ մանրամասն դիտում։ JSON-ը սեղմեք gzip-ով կամ Brotli-ով։ Շատ կարդացվող ռեսուրսների համար ուղարկեք ETag կամ Last-Modified, որպեսզի client-ները ստանան 304 պատասխաններ, և հաշվարկված պատասխանները քեշավորեք Redis-ում՝ գրառումների ժամանակ բացահայտ invalidation-ով։ Հրապարակային, ոչ անձնական endpoint-ները, ինչպես ապրանքների կատալոգը, կարելի է քեշավորել CDN-ում s-maxage-ով։ Չափեք p95 latency-ն ըստ endpoint-ի, ոչ թե միջինները։
Իրական իրավիճակներ
Օգտատերը կարող է կարդալ այլ հաճախորդների հաշիվները
Խնդիր
Անվտանգության հետազոտողը հաղորդում է, որ GET /v1/invoices/{id}-ում invoice ID-ն փոխելը վերադարձնում է այլ հաճախորդների PDF-ները։ Endpoint-ը պահանջում է վավեր token։
Ինչպես լուծել
Սա broken object level authorization է։ Նախ ուղղեք endpoint-ը. invoice-ը բեռնեք կանչողի account-ի միջոցով, օրինակ՝ օգտատիրոջ կամ tenant-ի invoices relation-ով, և վերադարձրեք 404, երբ այն չի գտնվում։ Հետո codebase-ում փնտրեք ID ընդունող յուրաքանչյուր route և կիրառեք նույն pattern-ը կամ policy ստուգում, ներառյալ nested ռեսուրսները և ֆայլերի ներբեռնումները։ Ավելացրեք ավտոմատ թեստեր, որոնք կանչում են յուրաքանչյուր endpoint երկրորդ օգտատիրոջ token-ով և սպասում 404։ Ստուգեք access log-երը հաջորդական ID-ների հասանելիության համար՝ պարզելու, թե արդյոք տվյալներ են բացահայտվել, և հետևեք ձեր incident-ի և disclosure-ի գործընթացին։
Անկայուն ցանցերում հաճախորդներից երկու անգամ գումար է գանձվում
Խնդիր
Support-ը տեսնում է կրկնակի վճարումներ մոբայլ օգտատերերից։ Log-երը ցույց են տալիս երկու POST /payments հարցում՝ մի քանի վայրկյան տարբերությամբ և նույնական body-ներով՝ client-ի timeout-ից հետո։
Ինչպես լուծել
Endpoint-ը դարձրեք idempotent։ Մոբայլ client-ը յուրաքանչյուր վճարման փորձի համար գեներացնում է UUID և ուղարկում այն որպես Idempotency-Key՝ նորից օգտագործելով retry-ների ժամանակ։ Սերվերը key-ը, body-ի hash-ը և պատասխանը պահում է աղյուսակում՝ client-ի և key-ի վրա unique constraint-ով։ Նույն body-ով կրկնությունը վերադարձնում է պահպանված պատասխանը, նույն key-ով այլ body-ն վերադարձնում է 422, իսկ կրկնությունը, մինչ առաջինը դեռ աշխատում է, վերադարձնում է 409։ Key-ը փոխանցեք նաև payment provider-ին, եթե այն աջակցում է idempotency, որպեսզի երկու շերտերն էլ պաշտպանված լինեն։
Release-ից հետո մոբայլ հավելվածի հին տարբերակը կոտրվում է
Խնդիր
Orders պատասխանում total դաշտը totalAmount վերանվանելուց հետո անցյալ տարվա հավելվածի տարբերակով օգտատերերը տեսնում են դատարկ գումարներ և չեն կարողանում checkout անել։
Ինչպես լուծել
Անմիջապես վերականգնեք հին դաշտը և առայժմ ուղարկեք երկու դաշտն էլ, քանի որ դաշտ ավելացնելը breaking չէ։ Ներմուծեք կանոն. դաշտերի հեռացումն ու վերանվանումը կատարվում են միայն API-ի նոր տարբերակում կամ deprecation ժամանակահատվածից հետո։ Յուրաքանչյուր հարցման header-ից log արեք հավելվածի տարբերակը, որպեսզի որևէ բան հեռացնելուց առաջ իմանաք, թե ով է դեռ օգտագործում հին դաշտերը։ Ավելացրեք OpenAPI spec-ից գեներացված contract թեստեր, որոնք ձախողվում են, երբ պատասխանը կորցնում է փաստաթղթավորված դաշտ։ Իսկապես չաջակցվող հին տարբերակների համար օգտագործեք forced-update էկրան՝ դրանք լուռ կոտրելու փոխարեն։
Արագ ուղեցույց
- Ստեղծում
POST /v1/orders -> 201 Created + Location: /v1/orders/1001- Մասնակի թարմացում concurrency ստուգմամբ
PATCH /v1/orders/1001 + If-Match: "v7" -> 200 or 412- Ընդունված background աշխատանքի համար
202 Accepted + Location: /v1/jobs/77- Validation սխալի ձևաչափ
Content-Type: application/problem+json (RFC 9457) with type, title, status, errors- Cursor pagination
GET /v1/orders?limit=20&cursor=eyJpZCI6MTAwMH0- Sparse fieldset-ներ և include-ներ
GET /v1/orders?fields=id,total,status&include=customer- Ապահով retry-ներ գրառումների համար
Idempotency-Key: 6f1c2d4e-8a7b-4c3d-9e2f-1a2b3c4d5e6f- Rate limit-ը գերազանցված է
429 Too Many Requests + Retry-After: 30- Deprecation ազդանշաններ
Deprecation: @1798761600 + Sunset: Fri, 01 Jan 2027 00:00:00 GMT- Lint անել OpenAPI ֆայլը
npx @redocly/cli lint openapi.yaml
Իմ իրական նախագծերում
- Ռեստորանային պատվերների պլատֆորմն աշխատում է մեկ Laravel REST API-ի վրա՝ առանձին վեբ և մոբայլ ալիքներով և controller-ներով, որոնք բաժանված են Shared, Ui, Mobile և Admin շերտերի, ընդհանուր առմամբ մոտ 330 endpoint։
- Multi-tenant POS SaaS-ում (NestJS, OpenAPI 3.1) աշխատել եմ մոբայլ հավելվածների համար տարբերակավորված guest API-ի և գործընկերների համար հանրային API-ի վրա՝ սահմանափակ իրավունքներով API key-երով, rate limit-երով և key rotation-ով։
- Այդ պլատֆորմն օգտագործում է idempotency key-եր գործընկերների պատվերների համար, HMAC-ով ստորագրված webhook-ներ, որոնք առաքվում են հերթի միջոցով, և այլ tenant-ի տողերի համար վերադարձնում է 404։
- Pet-store հավելվածում HTTP client-ը կրկնում է միայն idempotent մեթոդները, այնպես որ ցանցային սխալները երբեք չեն կրկնօրինակում պատվերները։
Հարցեր և պատասխաններ
Q1Ի՞նչն է API-ն դարձնում RESTful։
REST-ը ճարտարապետական ոճ է՝ URL-ներով նույնականացված ռեսուրսներ, որոնք կառավարվում են միասնական ինտերֆեյսով (ստանդարտ HTTP մեթոդներ), stateless հարցումներ, որոնք կրում են իրենց անհրաժեշտ ամբողջ կոնտեքստը, քեշավորվող պատասխաններ և ներկայացումներ, ինչպիսին է JSON-ը։ Գործնականում լավ REST API-ն օգտագործում է գոյականներ ռեսուրսների համար, մեթոդը՝ գործողությունն արտահայտելու համար, ճիշտ status code-եր, ստանդարտ header-ներ քեշավորման և auth-ի համար, և սխալների հետևողական ֆորմատներ։ Խիստ REST-ը ներառում է նաև hypermedia հղումներ, որոնք ուղղորդում են client-ներին, և որոնք քիչ API-ներ են ամբողջությամբ իրականացնում։ Ես կենտրոնանում եմ կանխատեսելիության և HTTP-ի ճիշտ սեմանտիկայի վրա, ոչ թե մաքրության, որովհետև հենց դա է ինտեգրումն արագ դարձնում։
Q2Ինչպե՞ս ես մշակում pagination-ը մեծ տվյալների համար։
Ադմին աղյուսակների համար, որտեղ օգտատերերը ցատկում են էջերի միջև, offset pagination-ը լավ է՝ index արված դասավորմամբ և սահմանափակ էջի չափով։ Մեծ կամ արագ փոփոխվող տվյալների, feed-երի, infinite scroll-ի և sync endpoint-ների համար օգտագործում եմ cursor pagination․ cursor-ը կոդավորում է վերջին տարրի դասավորման key-ը և ID-ն, իսկ query-ն օգտագործում է WHERE պայման այդ սյունակներով, որը արագ է մնում ցանկացած խորության վրա և երբեք չի բաց թողնում կամ կրկնօրինակում տողեր, երբ նոր տվյալներ են գալիս։ Վերադարձնում եմ հաջորդ cursor-ը և hasMore flag կամ հղումներ, որպեսզի client-ները երբեք URL-ներ չկառուցեն։ Ընդհանուր քանակը մեծ աղյուսակներում կարող է թանկ լինել, ուստի այն դարձնում եմ ոչ պարտադիր։
Q3Ի՞նչ է idempotency key-ը, և ինչպե՞ս ես այն իրականացնում։
Դա եզակի արժեք է, որը client-ը գեներացնում է մեկ տրամաբանական գործողության համար, օրինակ՝ վճարման, և ուղարկում Idempotency-Key header-ով։ Սերվերը key-ը պահում է հարցման fingerprint-ի և պատասխանի հետ միասին, client-ի տիրույթում, unique constraint-ով աղյուսակում կամ Redis-ում TTL-ով։ Եթե նույն key-ը կրկին է գալիս, սերվերը վերադարձնում է պահված պատասխանը՝ երկրորդ անգամ մշակելու փոխարեն, իսկ եթե նույն key-ը գալիս է այլ body-ով, վերադարձնում է 422 կամ 409։ Մինչ առաջին հարցումը դեռ ընթացքի մեջ է, կրկնօրինակը ստանում է 409, որպեսզի ավելի ուշ կրկնի։ Սա POST-ը դարձնում է անվտանգ կրկնելու համար timeout-երից հետո։
Q4Ինչպե՞ս ես տարբերակավորում API-ն, և ե՞րբ է պետք նոր տարբերակ։
Նոր տարբերակ ինձ պետք է միայն կոտրող փոփոխությունների համար՝ դաշտերի հեռացում կամ վերանվանում, տիպերի կամ իմաստի փոփոխություն, կամ պարտադիր մուտքերի ավելացում։ Ավելացնող փոփոխությունները մնում են ընթացիկ տարբերակում, և client-ներին ասվում է անտեսել անհայտ դաշտերը։ Նախընտրում եմ URL-ով տարբերակավորում, օրինակ՝ /v1, որովհետև այն բացահայտ է, հեշտ է route անել, լոգավորել և քեշավորել։ Երբ նոր տարբերակ է թողարկվում, հինը ստանում է deprecation ծանուցում sunset ամսաթվով, Deprecation և Sunset header-ներ, և մոնիտորինգ, թե ով է դեռ կանչում այն։ Մոբայլ հավելվածների համար, որոնք օգտատերերը միշտ չէ, որ թարմացնում են, հին տարբերակներն ավելի երկար եմ պահում և իսկապես չաջակցվող թողարկումների համար օգտագործում եմ forced-update մեխանիզմ։
Q5Ե՞րբ կընտրեիր GraphQL կամ gRPC REST-ի փոխարեն։
GraphQL-ը հարմար է, երբ շատ client-ների պետք են բազմաթիվ աղբյուրներից հավաքված տվյալների տարբեր ձևեր, օրինակ՝ վեբ, մոբայլ և գործընկերային ֆրոնտենդներով մեծ պրոդուկտ, և over-fetching-ը կամ բազմաթիվ round trip-երը դառնում են իրական խնդիրներ։ Գինը ավելի դժվար HTTP քեշավորումն է, authorization-ն ըստ դաշտի և query-ի բարդության սահմանափակումները։ gRPC-ն հարմար է ներքին service-to-service կանչերի համար, որտեղ կարևոր են ցածր latency-ն, խիստ տիպավորված պայմանագրերը և streaming-ը, իսկ բրաուզերները հիմնական client-ը չեն։ REST-ը մնում է իմ լռելյայն ընտրությունը հանրային, գործընկերային և մոբայլ API-ների համար, որովհետև այն հասկանում են յուրաքանչյուր գործիք, CDN և ծրագրավորող։ Հաճախ համակարգը համադրում է դրանք՝ REST դրսում, gRPC կամ հերթեր ներսում։
Q6Ինչպե՞ս ես ապահովում ուղարկվող կամ ստացվող webhook-ների անվտանգությունը։
Ուղարկելիս յուրաքանչյուր payload ստորագրում եմ HMAC-SHA256-ով՝ յուրաքանչյուր բաժանորդի համար առանձին secret-ով, և timestamp եմ ներառում ստորագրված բովանդակության և header-ների մեջ։ Առաքումն անցնում է հերթով՝ կրկնություններով և exponential backoff-ով, և յուրաքանչյուր իրադարձություն ունի եզակի ID։ Ստանալիս ստորագրությունը ստուգում եմ constant-time համեմատությամբ, մերժում եմ հին timestamp-երը՝ replay-ները արգելափակելու համար, և մշակում եմ idempotent՝ պահելով իրադարձությունների ID-ները, որովհետև պրովայդերներն առաքում են առնվազն մեկ անգամ և կարող են կրկին ուղարկել։ Արագ պատասխանում եմ 2xx-ով, իսկ ծանր աշխատանքն անում եմ background job-ում։ Վերջապես, գումարի հարցում երբեք կուրորեն չեմ վստահում payload-ին․ կրիտիկական վիճակը հաստատում եմ պրովայդերի API-ով։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կնախագծեք հրապարակային API third-party գործընկերների համար։
Սկսում եմ contract-ից. OpenAPI 3.1 spec, URL versioning, Problem Details սխալներ, cursor pagination և ISO ամսաթվեր, որոնք վերանայվում են իրականացումից առաջ։ Authentication-ը օգտագործում է scope արված API key-եր կամ OAuth client credentials, իսկ key-երը պահվում են hash արված, ցույց են տրվում մեկ անգամ, կարող են rotate և revoke լինել։ Յուրաքանչյուր հարցում սահմանափակված է գործընկերոջ tenant-ով, իսկ rate limit-ները կիրառվում են ըստ key-ի՝ header-ներով, որոնք ցույց են տալիս մնացած quota-ն։ Գրառումներն ընդունում են Idempotency-Key։ Իրադարձությունները ուղարկվում են HMAC-ով ստորագրված webhook-ներով՝ retry-ներով, առաքման log-ով և replay-ի հնարավորությամբ։ Թեստային տվյալներով sandbox միջավայրը թույլ է տալիս գործընկերներին ապահով ինտեգրվել։ Գինը նախնական դիզայնի ժամանակն է և երկարատև support-ի պարտավորությունը, քանի որ երբ գործընկերները կախված են տարբերակից, դրա փոփոխությունը նրանց իրական գումար է արժենում։
S2Ինչպե՞ս եք API-ն պահում հետևողական, երբ այն ընդգրկում է մի քանի ծառայություն կամ հերթ։
Մեկ ծառայության ներսում օգտագործում եմ database transaction-ներ։ Ծառայությունների միջև distributed transaction-ները հազվադեպ են արժենում իրենց գինը, ուստի նախագծում եմ eventual consistency-ի համար։ Յուրաքանչյուր ծառայություն տիրապետում է իր տվյալներին. փոփոխությունները հրապարակվում են որպես իրադարձություններ transactional outbox-ի միջոցով, իսկ consumer-ները դրանք մշակում են idempotent՝ ըստ event ID-ի։ Բազմաքայլ բիզնես հոսքերը, ինչպես պատվերը, վճարումը և պահեստի ամրագրումը, դառնում են saga. լոկալ քայլերի հաջորդականություն՝ փոխհատուցող գործողություններով, եթե հետագա քայլը ձախողվում է, օրինակ՝ պահեստն ազատել, երբ վճարումը մերժվում է։ API-ն սա ազնվորեն ցույց է տալիս pending, confirmed և failed վիճակներով, գումարած status endpoint կամ webhook, փոխանակ ձևացնելու, թե ամեն ինչ կատարվել է սինխրոն։ Գինը client-ներում մշակվող ավելի շատ վիճակներն են և խրված հոսքերի ավելի շատ մոնիթորինգը։
S3Ինչի՞ն կհետևեք production-ում աշխատող API-ի մոնիթորինգի ժամանակ։
Ըստ endpoint-ի և ըստ client-ի. հարցումների արագությունը, սխալների մակարդակը՝ բաժանված 4xx-ի և 5xx-ի, և latency-ն p50, p95 և p99 մակարդակներում։ Մեկ client-ից աճող 4xx-ը հաճախ նշանակում է կոտրված ինտեգրացիա, իսկ 5xx-ը՝ խնդիր մեր կողմում։ Հետևում եմ 429-ներին՝ տեսնելու, թե արդյոք limit-ները լավ են կարգավորված, և dependency-ների նկատմամբ timeout-ներին ու retry-ներին։ Յուրաքանչյուր հարցում ստանում է request ID, որը վերադարձվում է պատասխաններում և գրվում structured log-երում, որպեսզի support-ը կարողանա հաճախորդի հաղորդումը հետևել մինչև կոնկրետ log տողեր, իսկ distributed tracing-ը ցույց է տալիս, թե որտեղ է ծախսվում ժամանակը ծառայությունների միջև։ Բիզնեսի համար կրիտիկական հոսքերի համար ավելացնում եմ synthetic check-եր, որոնք թեստային պատվեր են տալիս։ Deprecated տարբերակների օգտագործումը հետևվում է, ուստի sunset-ները հիմնված են տվյալների վրա։ Alert-ները գործարկվում են օգտատերերի զգացած ախտանիշներով, ոչ թե CPU-ի յուրաքանչյուր պիկով։
Բառարան
- Resource
- Այն, ինչ բացահայտում է ձեր API-ն, օրինակ՝ պատվեր կամ հաճախորդ, որը նույնականացվում է URL-ով։
- Representation
- Ձևաչափը, որով ուղարկվում է ռեսուրսը, սովորաբար JSON, որը նկարագրում է նրա ընթացիկ վիճակը։
- BOLA / IDOR
- Broken object level authorization. օգտատերերը կարող են ID փոխելով հասանելիություն ստանալ իրենց չպատկանող օբյեկտներին։
- Mass assignment
- Երբ API-ն client-ի ուղարկած բոլոր դաշտերը պատճենում է մոդելի մեջ՝ թույլ տալով սահմանել role-ի կամ price-ի նման դաշտեր։
- Idempotency key
- Client-ի գեներացրած եզակի արժեք, որը թույլ է տալիս սերվերին ճանաչել և ապահով անտեսել կրկնված հարցումը։
- Cursor
- Դասավորված ցուցակում դիրքի անթափանց ցուցիչ, որն օգտագործվում է հաջորդ էջը հուսալի բեռնելու համար։
- Problem Details
- RFC 9457 ստանդարտ JSON ձևաչափ API սխալների համար, որը մատուցվում է որպես application/problem+json։
- Transactional outbox
- Իրադարձությունների պահպանում տվյալների հետ նույն database transaction-ում, հետո դրանց առաքում worker-ից։
- Token bucket
- Rate-limiting ալգորիթմ, որը կայուն արագությամբ վերալիցքավորում է թույլատրված քանակը և թույլ է տալիս կարճ burst-եր։
- OpenAPI
- HTTP API-ի մեքենայաընթեռնելի նկարագրություն, որն օգտագործվում է փաստաթղթերի, client-ների գեներացման և թեստավորման համար։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։