17 β GraphQL dizayni¶
β¬ οΈ Oldingi: 16 β Keshlash (HTTP caching) Β· π README Β· Keyingi: 18 β gRPC va Protocol Buffers β‘οΈ
Bu bobda: REST'dan keyingi ikkinchi yirik uslub β GraphQL. U bitta endpoint (odatda
POST /graphql) orqali ishlaydigan, mijoz qaysi maydonlarni xohlasa, aynan shularni so'raydigan so'rov tili va ish vaqti (runtime). Biz uning tipli sxemasini (SDL),Query/Mutation/Subscriptionildiz tiplarini, over/under-fetching muammosini qanday hal qilishini va β eng muhimi β uning halol trade-off'larini (N+1, keshlash, murakkablik, xato, versiyalash) ko'rib chiqamiz. Oxirida REST va GraphQL'ni qachon tanlashni muhokama qilamiz.Halollik / Eslatma: GraphQL β RFC emas, GraphQL Foundation (asli Facebook) e'lon qilgan rasmiy spetsifikatsiya. Faktlar shu spetsifikatsiyaga tayanadi: bitta endpoint, tipli sxema, mijoz maydonlarni tanlaydi, N+1 muammosi DataLoader/batching bilan yengiladi. "Qachon GraphQL, qachon REST" qismi β mutlaq qoida emas, trade-off; kontekst hal qiladi. Bir muhim halollik: GraphQL odatda HTTP xato kodi emas,
200 OK+ javob tanasidaerrors[]qaytaradi β bu REST'ning status-kod falsafasiga zid; buni yashirmaymiz. SDL va JSON namunalari to'g'ri sintaksisda; spetsifikatsiya va asboblar vaqt bilan o'zgaradi.
GraphQL nima va nega kerak¶
16-bobgacha biz asosan REST haqida gaplashdik: resurslar, URI'lar, HTTP fe'llari. REST kuchli va sodda. Lekin uning ikkita o'jar kamchiligi bor, va ularni biz 07-bobda "over-fetching va under-fetching" deb nomlagan edik. Eslatib o'tay:
- Over-fetching (ortiqcha olish): mijozga faqat foydalanuvchining ismi kerak, lekin
GET /users/42butun obyektni β email, telefon, manzil, ro'yxatdan o'tgan sana β hammasini qaytaradi. Kerakmas baytlar tarmoqda yuriydi. Mobil ilova uchun bu β sekinroq ekran, ko'proq trafik. - Under-fetching (kam olish): bitta ekranni chizish uchun bir nechta resurs kerak. Masalan, foydalanuvchi + uning postlari + har postning izohlari. REST'da bu uch-to'rt alohida so'rov:
GET /users/42, keyinGET /users/42/posts, keyin har post uchunGET /posts/{id}/comments. Har so'rov β yangi tarmoq aylanishi (round-trip).
Tasavvur qiling, restoranda ofitsiant sizga butun menyuni tayyorlab keltiradi (over-fetching) yoki har taomni alohida-alohida, har safar oshxonaga qaytib (under-fetching). GraphQL'da esa siz bitta aniq buyurtma berasiz: "menga shu, shu va shu kerak" β va oshxona aynan shuni, bir martada keltiradi.
Rasman: GraphQL β API'lar uchun so'rov tili (query language) va shu so'rovlarni bajaruvchi ish vaqti (runtime). U Facebook'da 2012-yilda mobil ilovalarning aynan shu og'rig'ini hal qilish uchun tug'ildi, 2015-yilda ochiq qilindi, hozir esa mustaqil GraphQL Foundation spetsifikatsiyasi sifatida rivojlanadi.
GraphQL'ning REST'dan tubdan farq qiladigan uchta g'oyasi:
- Bitta endpoint. REST'da har resurs o'z URI'siga ega (
/users,/posts,/comments). GraphQL'da odatda bitta manzil bor β masalanPOST /graphql. "Nima kerakligi" URL'da emas, so'rov tanasida ifodalanadi. - Mijoz maydonlarni tanlaydi. Server "men nimani qaytarsam, shu" demaydi; mijoz "menga aynan shu maydonlar kerak" deydi va javob aynan o'sha shaklda keladi.
- Kuchli tipli, contract-first sxema. Server o'z imkoniyatlarini sxema (schema) sifatida e'lon qiladi: qanday tiplar bor, har tipda qanday maydonlar, qaysi so'rovlar mumkin. Bu β mashina o'qiy oladigan, bir ma'noli shartnoma. Buni biz 04-bobda API uslublarini taqqoslaganda eslatib o'tgan edik.
Eslatma: "GraphQL β REST o'rnini bosadi" degan da'vo soddalashtirilgan. To'g'rirog'i: GraphQL β REST hal qila olmagan muayyan muammolarga (ko'p manbali, o'zgaruvchan, mijoz-boshqaradigan ma'lumot ehtiyoji) javob. Ko'p tizimlar ikkalasini birga ishlatadi.
Sxema (Schema) β yurakdagi shartnoma¶
GraphQL'da hamma narsa sxemadan boshlanadi. Sxema β bu serverning to'liq "imkoniyatlar xaritasi": qanday tiplar mavjud, ular qanday bog'langan, mijoz nimani so'ray oladi. Sxema SDL (Schema Definition Language β sxema ta'rifi tili) deb ataladigan maxsus sintaksisda yoziladi.
Tiplar va maydonlar¶
Eng asosiy qurilish bloki β obyekt tipi (type). U maydonlardan iborat, har maydonning o'z tipi bor:
type User {
id: ID!
name: String!
email: String
role: Role
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
published: Boolean!
author: User!
}
Bu yerda diqqat qilinadigan detallar:
- Skalyar tiplar (atomik, "barg" qiymatlar):
Int,Float,String,Booleanva maxsusID(noyob identifikator, ko'pincha satr sifatida seriyalanadi). Bular boshqa maydonga ega bo'lmaydi β daraxtning bargi. !(undov) β maydon null bo'la olmaydi degani.name: String!β ism doim bo'ladi;email: Stringβ email bo'lmasligi mumkin (null).[Post!]!β ro'yxat. Ichkaridagi!"ro'yxat ichida null element yo'q", tashqaridagi!"ro'yxatning o'zi null emas (bo'sh bo'lishi mumkin, lekin null emas)" degani.- Bog'lanish (relationship) β
User.postsPosttipiga,Post.authoresaUsertipiga ishora qiladi. Bu graf hosil qiladi β nomi ham shundan: ma'lumotlar graf kabi bir-biriga ulangan.
GraphQL'ning yana bir nechta qurilish bloki:
enumβ cheklangan qiymatlar to'plami:enum Role { ADMIN USER GUEST }.interfaceβ umumiy maydonlar to'plami; turli tiplar uni amalga oshiradi (masalan,Node { id: ID! }).unionβ bir maydon bir nechta tipdan biri bo'lishi mumkin:union SearchResult = User | Post.inputβ mutatsiyaga argument sifatida uzatiladigan tuzilma (oddiytype'dan farqi: faqat kirish uchun).
Uchta ildiz tip: kirish nuqtalari¶
Sxemada uchta maxsus ildiz tip (root type) bor β ular API'ga kirish eshiklari:
| Ildiz tip | Vazifasi | REST analogi |
|---|---|---|
Query |
Ma'lumot o'qish | GET |
Mutation |
Ma'lumot o'zgartirish (yaratish/yangilash/o'chirish) | POST/PUT/PATCH/DELETE |
Subscription |
Real-time oqim (server hodisa yuborib turadi) | WebSocket/SSE |
type Query {
user(id: ID!): User
posts(limit: Int = 10): [Post!]!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
type Subscription {
postAdded: Post!
}
input CreatePostInput {
title: String!
body: String!
}
Subscription β bu real-time mexanizm bo'lib, odatda WebSocket ustida ishlaydi; biz uni 20-bobda WebSocket va SSE bilan birga chuqurroq ko'ramiz. Bu bobda asosiy e'tibor Query va Mutation'da.
Standart: GraphQL'ning kuchli tomoni β sxema majburiy va birinchi darajali. REST'da shartnoma (OpenAPI) ko'pincha keyin yoziladi yoki umuman bo'lmaydi; GraphQL'da sxema bo'lmasa, server umuman ishlamaydi. Bu "contract-first" yondashuvni tabiiy qiladi β 21-bobdagi OpenAPI g'oyasiga o'xshash, lekin majburiy.
Query: mijoz nimani so'rasa, shuni oladi¶
Endi eng o'ziga xos qism. GraphQL so'rovi β bu sizga kerakli maydonlarning daraxt shaklidagi tasviri. Siz aynan kerakli maydonlarni yozasiz, server esa javobni aynan o'sha shaklda qaytaradi.
So'rov:
Buni HTTP darajasida ko'raylik β GraphQL odatda POST /graphql orqali, so'rov tanasida JSON sifatida yuboriladi:
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
{"query": "query { user(id: \"42\") { name posts { title } } }"}
HTTP/1.1 200 OK
Content-Type: application/json
{"data":{"user":{"name":"Ali","posts":[{"title":"Salom"},{"title":"API dizayni"}]}}}
E'tibor bering:
- Javob shakli so'rov shakliga aynan mos. Siz
namevaposts.titleso'radingiz β javobda aynan shular bor, na ko'p, na kam.email,role,bodyβ so'ralmadi, demak javobda yo'q. - Javob doim
datakaliti ostida keladi. Xato bo'lsa, qo'shimchaerrorskaliti qo'shiladi (buni keyinroq ko'ramiz). - HTTP status
200 OKβ hatto so'rovning bir qismi xato bo'lsa ham (bu β muhim trade-off, pastda batafsil).
Argumentlar, alias, fragment, o'zgaruvchilar¶
GraphQL so'rovlari yana bir nechta vositaga ega:
Argumentlar β maydonni sozlash: user(id: "42"), posts(limit: 5).
Alias β bir maydonni har xil argument bilan ikki marta so'rash; nomlar to'qnashmasligi uchun nom beriladi:
Fragment β takrorlanuvchi maydonlar to'plamiga nom berish (DRY):
query {
user(id: "42") {
...userFields
posts { ...userFields }
}
}
fragment userFields on User {
id
name
}
O'zgaruvchilar (variables) β qiymatni so'rov matniga yopishtirib qo'ymasdan, alohida uzatish (bu xavfsizroq va keshlanadigan):
JSON tanasi ikki qismdan iborat bo'ladi:
Xavfsizlik: Qiymatlarni so'rov matniga qo'lda yopishtirish (string konkatenatsiya) β REST'dagi SQL-injection'ga o'xshash xavf. O'zgaruvchilardan foydalaning β bu GraphQL darajasida tipni ham tekshiradi.
Mutation: o'zgartirish¶
O'qish query orqali bo'lsa, har qanday o'zgartirish (yaratish, yangilash, o'chirish) mutation orqali bo'ladi. Sintaksisi bir xil, faqat boshida mutation so'zi turadi va Mutation ildiz tipidagi maydon chaqiriladi:
Diqqat: mutatsiya ham o'zgartirgandan keyin natijani so'raydi β qaysi maydonlar qaytishini siz tanlaysiz (id, title, published). Bu REST'dagi "yaratdim, endi qayta GET qilib olishim kerakmi?" muammosini yo'q qiladi.
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>
{"query":"mutation { createPost(input: {title: \"Yangi\", body: \"Matn\"}) { id title published } }"}
HTTP/1.1 200 OK
Content-Type: application/json
{"data":{"createPost":{"id":"101","title":"Yangi","published":false}}}
Eslatma: GraphQL spetsifikatsiyasi
querymaydonlarini parallel,mutationmaydonlarini esa ketma-ket (birin-ketin) bajarishni kafolatlaydi β chunki o'zgartirishlar tartibi muhim. Bu idempotentlik mavzusiga tegishli; 15-bobda parallellik haqida gaplashgandik.
Subscription: qisqacha¶
subscription β server tomonidan mijozga uzatiladigan hodisalar oqimi. Masalan, yangi post qo'shilganda barcha tinglovchilarga yuborish:
Bu odatda WebSocket (yoki SSE) transport ustida ishlaydi β uni 20-bobda batafsil ko'ramiz.
Over/under-fetching'ni qanday hal qiladi¶
Endi boshidagi muammoga qaytaylik. Bir ekran kerak bo'lsin: foydalanuvchining ismi, avatari va uning oxirgi 3 ta postining sarlavhasi.
REST'da (sodda variantda):
GET /users/42 -> butun user obyekti (over-fetch)
GET /users/42/posts?limit=3 -> 2-chi aylanish (under-fetch'ni qoplash uchun)
Ikki so'rov, va birinchisida kerakmas maydonlar. Mobil tarmoqda har aylanish 100β300 ms qo'shishi mumkin.
GraphQL'da β bitta so'rov, aynan kerakli maydonlar:
Bitta tarmoq aylanishi, na ortiqcha bayt, na yetishmagan ma'lumot. Bu GraphQL'ning eng kuchli tomoni va u eng yaxshi yaltiraydigan joy:
- Mobil ilovalar β tarmoq va batareya qimmat; kerakmas baytlar zarar.
- Murakkab, o'zgaruvchan UI β bir ekran ko'p manbadan ma'lumot yig'adi; har frontend jamoasi o'z so'rovini moslaydi, backend'ga tegmasdan.
- Aggregatsiya (BFF) β bir GraphQL qatlami ortda bir nechta mikroservis/REST API'ni birlashtirib, mijozga yagona graf taqdim etadi. Buni biz 23-bobda BFF (Backend-for-Frontend) sifatida ko'ramiz.
Trade-off: Bu moslashuvchanlik bepul emas. Mijoz endi og'ir so'rovlar ham yubora oladi (chuqur ichma-ich, ko'p bog'lanishli) β bu serverga yuk. Pastda buni qanday cheklashni ko'ramiz. Qoidaning o'zi shu: GraphQL kuch-quvvatni mijozga beradi, demak mas'uliyatni ham serverga qaytaradi.
Muammolar va trade-off'lar (halol qism)¶
GraphQL β sehrli tayoqcha emas. Uning real, kundalik muammolari bor. Yaxshi muhandis ularni oldindan biladi.
1. N+1 muammosi¶
Bu β GraphQL'ning eng mashhur tuzog'i. Tasavvur qiling, so'rov:
Sodda amalga oshirilganda server quyidagicha ishlaydi:
postsresolveri:SELECT * FROM postsβ 10 ta post oladi (bu "1").- Har post uchun
authorresolveri alohida chaqiriladi:SELECT * FROM users WHERE id = ?β 10 marta (bu "N").
Jami 1 + N = 11 ta DB so'rovi. 1000 ta post bo'lsa β 1001 ta so'rov. Tizim tiz cho'kadi.
Yechim β DataLoader (batching + per-request kesh). G'oya oddiy: bog'liq elementlarni darrov so'ramaymiz, balki bitta "tick" davomida barcha kerakli id'larni yig'amiz, keyin bitta so'rov bilan olamiz:
Endi jami 2 ta so'rov: posts uchun bitta, barcha author'lar uchun bitta. DataLoader yana bir so'rov ichida kesh ham qiladi β agar bir id ikki marta kerak bo'lsa, DB'dan bir marta olinadi.
Diqqat: N+1 β GraphQL'ga xos kasallik EMAS; u REST'da ham, ORM'larda ham bor. Lekin GraphQL'da u osonroq paydo bo'ladi, chunki mijoz ichma-ich bog'lanishlarni erkin so'ray oladi. Shuning uchun DataLoader yoki shunga o'xshash batching deyarli har bir jiddiy GraphQL serverida shart.
2. Keshlash qiyinlashadi¶
16-bobda biz HTTP keshlashning kuchini ko'rdik: GET so'rovlari URL bo'yicha keshlanadi, ETag/Cache-Control butun infratuzilma (brauzer, CDN, proksi) tomonidan bepul ishlatiladi. GraphQL bu mexanizmni buzadi:
- Hamma narsa
POSTβPOSTodatda keshlanmaydi (RFC 9111). - Bitta endpoint β URL har doim
/graphql, demak "URL = kesh kaliti" ishlamaydi. - Javob shakli har so'rovda har xil β bir xil URL turli natija beradi.
Yechimlar (har biri kontekstga bog'liq):
- Persisted queries (saqlangan so'rovlar) β so'rov matni o'rniga uning hash'i yuboriladi; server oldindan tanigan so'rovlarni
GETorqali keshlanadigan qiladi. Bu CDN keshini qaytaradi. - Ilova darajasidagi kesh β server ichida obyekt darajasida kesh (masalan Redis), DataLoader keshi.
- Mijoz tomonidagi normalizatsiyalangan kesh β Apollo Client, Relay kabi kutubxonalar javobni
idbo'yicha normalizatsiya qilib saqlaydi.
Trade-off: HTTP keshlash REST'ning "bepul" ustunligi edi. GraphQL'da uni qaytarish uchun qo'shimcha ish (persisted queries yoki ilova-darajali kesh) kerak. Bu β GraphQL moslashuvchanligi uchun to'lanadigan haq.
3. So'rov murakkabligi va rate limiting¶
REST'da bir so'rov β odatda bir resurs, taxminan oldindan bilinadigan yuk. GraphQL'da esa mijoz juda og'ir so'rov yozishi mumkin β masalan, chuqur ichma-ich bog'lanish:
Bu eksponensial yuk yaratishi mumkin. 14-bobdagi oddiy "soatiga N so'rov" cheklovi bu yerda yetarli emas β chunki bir so'rovning o'zi minglab obyektni so'rashi mumkin. Shuning uchun GraphQL'da maxsus himoyalar qo'llanadi:
- Query depth limit β so'rov chuqurligini cheklash (masalan, maksimum 7 daraja).
- Query complexity limit β har maydonga "narx" berib, umumiy "ball"ni cheklash.
- Pagination majburiy β ro'yxat qaytaruvchi maydonlarda
limitargumenti shart. - Timeout β uzoq cho'ziladigan so'rovni to'xtatish.
4. Xato: 200 OK + errors[]¶
Bu β eng muhim halollik. REST'da xato HTTP status kodida ifodalanadi: 404, 400, 500 (03-bobdagi status oilalari). GraphQL esa boshqacha ishlaydi: ko'p hollarda u 200 OK qaytaradi, xatolar esa javob tanasidagi errors ro'yxatida bo'ladi:
{
"data": { "user": null },
"errors": [
{
"message": "User topilmadi",
"path": ["user"],
"extensions": { "code": "NOT_FOUND" }
}
]
}
Nega shunday? Chunki bir so'rov qisman muvaffaqiyatli bo'lishi mumkin: user topildi, lekin uning posts maydonida xato yuz berdi. REST'ning bitta status kodi bunday "yarmi ishladi" holatini ifodalay olmaydi. GraphQL data'da ishlagan qismni, errors'da esa muammolarni qaytaradi.
Anti-pattern / Diqqat: Bu REST'ga o'rgangan mijozlar uchun tuzoq. "200 keldi, demak hammasi yaxshi" deb
errors'ni tekshirmaslik β eng keng tarqalgan GraphQL xatosi. Doimerrorsro'yxatini tekshiring. Shuni ham bilib qo'ying: transport darajasidagi xato (autentifikatsiya yo'q, server qulagan) baribir HTTP status kodida (401,500) keladi β faqat GraphQL ichidagi xatolar200 + errorsshaklida bo'ladi.
09-bobdagi RFC 9457 "Problem Details" β REST uchun. GraphQL o'z xato formatiga ega (errors[] + extensions.code), shuning uchun u boshqa konvensiya. Ikkalasini aralashtirmang.
5. Versiyalash: "versiyasiz evolyutsiya"¶
10-bobda biz REST'ni qanday versiyalashni ko'rdik (/v1, /v2...). GraphQL bu masalaga boshqacha qaraydi va "versiyasiz evolyutsiya" ni targ'ib qiladi:
- Maydon qo'shish β buzmaydigan o'zgarish. Eski mijozlar yangi maydonni so'ramaydi, demak ularga ta'sir qilmaydi.
- Maydonni
@deprecateddeb belgilash β o'chirish o'rniga, eskirgan deb belgilanadi (sxemada ko'rinadi, asboblar ogohlantiradi), lekin ishlashda davom etadi:
Mijoz faqat o'ziga kerakli maydonni so'ragani uchun, yangi maydonlar qo'shish hech kimni buzmaydi β bu /v2 yaratish ehtiyojini kamaytiradi.
Halollik: "Versiyasiz" so'zi mubolag'a. Maydon o'chirish yoki tipini o'zgartirish baribir buzuvchi o'zgarish (breaking change) β buni hech qanday sxema yashira olmaydi. GraphQL faqat eng keng tarqalgan evolyutsiya turini (maydon qo'shish) og'riqsiz qiladi. Real loyihalarda deprecate qilingan maydonlar ham bir kun o'chiriladi β bu hali ham boshqarilishi kerak.
REST vs GraphQL: qachon qaysi¶
Mana eng muhim savol. Javob β "ikkalasi ham, kontekstga qarab". Quyidagi jadval qaror berishga yordam beradi:
| Mezon | REST mos keladi | GraphQL mos keladi |
|---|---|---|
| Ma'lumot shakli | Barqaror, sodda CRUD | O'zgaruvchan, mijoz-boshqaradigan |
| Mijozlar | Bir xil, taxmin qilinadigan | Ko'p xil (mobil, web, ichki), har xil ehtiyoj |
| Manbalar | Bitta backend/DB | Ko'p mikroservis/manba aggregatsiyasi |
| Keshlash | HTTP/CDN keshi muhim | Ilova-darajali kesh joyida |
| Over/under-fetch | Muammo emas | Asosiy og'riq nuqtasi |
| Fayl yuklash, binary | Tabiiy | Noqulay (alohida mexanizm kerak) |
| Public, oddiy API | β Sodda, tanish | Ortiqcha murakkablik |
| Murakkab UI/grafli ma'lumot | Ko'p so'rov kerak | β Bitta moslashuvchan so'rov |
Amaliy yo'l-yo'riq (trade-off sifatida, qoida emas):
- Sodda, public, CRUD-asosli API (masalan, to'lov, weather, oddiy ma'lumotnoma) β REST. U tanish, HTTP keshi bepul, asboblari pishgan, o'rganish oson.
- Murakkab, o'zgaruvchan mijoz ehtiyoji, ko'p manba (masalan, ijtimoiy tarmoq, dashboard, mobil-og'ir mahsulot) β GraphQL. Frontend jamoalari mustaqil ishlaydi, over/under-fetching yo'qoladi.
- Ichki, yuqori-unumli xizmatlararo aloqa β ko'pincha gRPC (18-bob) β bu uchinchi yo'l.
- Ko'pincha eng yaxshi javob β aralash: GraphQL'ni frontend uchun (BFF), REST/gRPC'ni ichkarida.
Trade-off: GraphQL "yaxshiroq" emas β u boshqa trade-off'lar to'plami. U moslashuvchanlikni oladi, lekin operatsion murakkablik (N+1, keshlash, query himoyasi, monitoring) qo'shadi. Kichik jamoa va sodda domen uchun bu murakkablik o'zini oqlamasligi mumkin. Tanlovni domen va jamoa qaroriga ko'ra qiling.
Xavfsizlik: qisqacha¶
GraphQL'ning o'ziga xos xavfsizlik nuqtalari bor β REST bilimingiz (13-bob) o'rinli, lekin yetarli emas:
- Introspection β GraphQL serveri o'z sxemasini so'rov orqali oshkor qila oladi (asboblar shuni ishlatadi). Bu qulay, lekin production'da hujumchiga butun API xaritasini beradi. Ko'p jamoalar uni production'da o'chiradi yoki cheklaydi.
- Query complexity/depth β yuqorida ko'rganimizdek, og'ir so'rovlar β DoS vektori (OWASP API4: Unrestricted Resource Consumption). Depth/complexity limit shart.
- Avtorizatsiya har resolverda β eng muhim nuqta. GraphQL'da bir so'rov ko'p maydonga tegadi; ruxsatni endpoint darajasida emas, har maydon/resolver darajasida tekshirish kerak. Aks holda mijoz boshqa foydalanuvchining
posts.author.email'ini graf orqali o'g'irlashi mumkin β bu 12-bobdagi BOLA (Broken Object Level Authorization, OWASP API1) ning aynan GraphQL ko'rinishi.
Xavfsizlik: GraphQL'ning grafli tabiati avtorizatsiyani murakkabroq qiladi. "Endpoint himoyalangan" yetarli emas β har bog'lanish bo'ylab yurib boradigan ruxsatni o'ylash kerak.
To'liq misol: sxema, query, javob¶
Hammasini birlashtiramiz. Kichik blog API'si:
type Query {
post(id: ID!): Post
}
type Post {
id: ID!
title: String!
author: User!
comments(limit: Int = 5): [Comment!]!
}
type User {
id: ID!
name: String!
}
type Comment {
id: ID!
text: String!
author: User!
}
Mijoz so'rovi β bitta postning sarlavhasi, muallifi va birinchi 2 ta izohi (har izoh muallifi bilan):
query GetPost($id: ID!) {
post(id: $id) {
title
author { name }
comments(limit: 2) {
text
author { name }
}
}
}
To'liq HTTP almashinuvi:
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"query":"query GetPost($id: ID!){post(id:$id){title author{name} comments(limit:2){text author{name}}}}","variables":{"id":"7"}}
HTTP/1.1 200 OK
Content-Type: application/json
{"data":{"post":{"title":"GraphQL haqida","author":{"name":"Ali"},"comments":[{"text":"Zo'r maqola","author":{"name":"Vali"}},{"text":"Rahmat","author":{"name":"Hasan"}}]}}}
Bitta so'rov, uchta tip bo'ylab yurib o'tdi, javob aynan so'ralgan shaklda. REST'da bu kamida 3β4 ta so'rov bo'lardi (post, muallif, izohlar, izoh mualliflari). Ana shu β GraphQL'ning mohiyati. Va ortda DataLoader bo'lmasa β bu aynan N+1 tuzog'i (comments[].author har izoh uchun alohida so'rov). Demak: kuch va mas'uliyat birga keladi.
Asosiy g'oyalar (bobni qisqacha)¶
- GraphQL β bitta endpoint (
POST /graphql) ustida ishlaydigan, tipli sxemaga asoslangan so'rov tili; mijoz qaysi maydonlarni xohlashini so'raydi, javob aynan o'sha shaklda keladi. - Sxema (SDL) β birinchi darajali, majburiy shartnoma.
type, skalyar (Int/Float/String/Boolean/ID),enum,interface,union,input. Uchta ildiz tip:Query(o'qish),Mutation(o'zgartirish),Subscription(real-time). - GraphQL over-fetching (ortiqcha maydon) va under-fetching (ko'p so'rov) muammolarini bitta moslashuvchan so'rov bilan hal qiladi β mobil va murakkab UI uchun ayni muddao.
- Trade-off'lar halol: N+1 (DataLoader/batching bilan yeching), keshlash qiyin (persisted queries / ilova-kesh), query murakkabligi (depth/complexity limit), xato
200 OK + errors[](REST status falsafasidan farqli), versiyasiz evolyutsiya (lekin o'chirish baribir buzuvchi). - REST vs GraphQL β mutlaq emas, kontekst: sodda public CRUD β REST; murakkab/o'zgaruvchan mijoz ehtiyoji, ko'p manba aggregatsiyasi β GraphQL. Ko'pincha aralash.
- Xavfsizlik: introspection'ni production'da cheklang, query complexity'ni cheklang, avtorizatsiyani har resolverda tekshiring (GraphQL'ning BOLA ko'rinishi).
Mashqlar¶
Oson¶
1-mashq. Quyidagi sxemada qaysi tiplar Query ildiz tipining maydoni orqali kirish nuqtasiga ega, qaysilari faqat bog'lanish orqali yetib boriladi? id maydonidagi ! nimani anglatadi?
type Query { product(id: ID!): Product }
type Product { id: ID! name: String! reviews: [Review!]! }
type Review { id: ID! rating: Int! text: String }
2-mashq. Yuqoridagi sxema uchun bitta mahsulotning nomi va uning sharhlarining faqat rating'ini oladigan query yozing (id = "55"). text va Product.id so'ralmasin.
3-mashq. GraphQL so'rovi 200 OK qaytardi, lekin javob tanasi {"data": {"product": null}, "errors": [...]} ko'rinishda. Mijoz "200 keldi, hammasi joyida" deb hisoblasa, qanday xato qiladi?
O'rta¶
4-mashq. Bir mobil ekran foydalanuvchining name'i va oxirgi 3 ta buyurtmasining total'ini ko'rsatadi. Buni (a) REST'da va (b) GraphQL'da qanday olishni ko'rsating. REST variantida over- yoki under-fetching qayerda yuzaga keladi?
5-mashq. Product tipiga yangi mahsulot yaratadigan createProduct mutatsiyasini yozing. input tipini ham e'lon qiling (name majburiy, price majburiy). Mutatsiya yaratilgan mahsulotning id va name'ini qaytarsin.
6-mashq. Quyidagi so'rovda N+1 muammosi qayerda yuzaga keladi? Necha DB so'rovi bo'lishi mumkin (10 ta mahsulot va har birida 1 ta sotuvchi bo'lsa)?
Qiyin¶
7-mashq. 6-mashqdagi N+1 muammosini DataLoader qanday hal qiladi? "Batching" va "per-request kesh" tushunchalarini va so'rovlar sonining 11 dan 2 ga tushishini tushuntiring.
8-mashq. Sizning GraphQL API'ngiz tez ishlamayapti, chunki HTTP/CDN keshi umuman ishlamayapti. Nega GraphQL'da oddiy HTTP keshlash (16-bob) ishlamaydi, va buni qaytarish uchun qanday strategiyalar bor?
9-mashq. Sizdan yangi ichki mahsulot uchun API uslubi tanlash so'raldi. Mahsulot β murakkab, ko'p ekranli mobil ilova bo'lib, ma'lumot 4 ta turli mikroservisdan keladi va frontend jamoasi tez-tez yangi ekran qo'shadi. REST yoki GraphQL? Qaroringizni kamida uchta mezon (over/under-fetching, manba aggregatsiyasi, jamoa mustaqilligi) va kamida bitta trade-off (nima yo'qotasiz) bilan asoslang.
Yechimlar
1-mashq yechimi¶
Faqat product(id: ID!) Query ildiz tipining maydoni β demak kirish nuqtasi yagona: Product'ga undan kiriladi. Review'ga to'g'ridan-to'g'ri kirib bo'lmaydi; unga faqat Product.reviews bog'lanishi orqali yetib boriladi (graf bo'ylab yurib). id: ID! dagi ! β bu maydon null bo'la olmaydi degani: har mahsulotning id'si doim mavjud va to'ldirilgan bo'ladi.
2-mashq yechimi¶
Product.id, Review.id, Review.text so'ralmadi β demak javobda ham bo'lmaydi. GraphQL javobi aynan so'rov shakliga mos keladi: {"data":{"product":{"name":"...","reviews":[{"rating":5},...]}}}.
3-mashq yechimi¶
Mijoz errors ro'yxatini e'tiborsiz qoldiradi β bu eng keng tarqalgan GraphQL xatosi. REST'dan farqli o'laroq, GraphQL 200 OK qaytarsa ham so'rov to'liq yoki qisman muvaffaqiyatsiz bo'lishi mumkin: ishlagan qism data'da, muammolar esa errors'da bo'ladi. Bu yerda product null va errors to'la β demak mahsulot olinmadi (masalan, topilmadi yoki ruxsat yo'q). To'g'ri mijoz doim errors ro'yxatini tekshirishi va data ichidagi null maydonlarni nazarda tutishi kerak. Faqat HTTP status kodiga ishonish β noto'g'ri.
4-mashq yechimi¶
(a) REST:
GET /users/42 -> butun user obyekti (over-fetch: kerakmas maydonlar keladi)
GET /users/42/orders?limit=3 -> ikkinchi aylanish (under-fetch'ni qoplash)
- Over-fetching:
GET /users/42name'dan tashqari email, telefon, manzil... hammasini qaytaradi β bizga faqatnamekerak edi. - Under-fetching: foydalanuvchi obyekti buyurtmalarni o'z ichiga olmaydi, shuning uchun ikkinchi so'rov kerak β bu qo'shimcha tarmoq aylanishi.
(b) GraphQL β bitta so'rov, aynan kerakli maydonlar:
Bitta aylanish, ortiqcha bayt yo'q, ikkinchi so'rov yo'q.
5-mashq yechimi¶
type Mutation {
createProduct(input: CreateProductInput!): Product!
}
input CreateProductInput {
name: String!
price: Float!
}
Mutatsiyani chaqirish:
input tipi type'dan farqi β u faqat kirish uchun ishlatiladi (argument sifatida). Mutatsiya o'zgartirgandan keyin natijaning qaysi maydonlarini qaytarishni biz tanlaymiz (id, name) β bu REST'dagi "yaratdim, endi qayta GET qilay" ehtiyojini yo'q qiladi.
6-mashq yechimi¶
N+1 muammosi seller { name } qismida yuzaga keladi:
productsresolveri: bitta so'rov βSELECT * FROM products(10 ta mahsulot). Bu "1".- Har mahsulot uchun
sellerresolveri alohida chaqiriladi:SELECT * FROM sellers WHERE id = ?β 10 marta. Bu "N".
Jami 1 + 10 = 11 ta DB so'rovi. Agar mahsulotlar 1000 ta bo'lsa β 1001 so'rov. Sabab: GraphQL resolveri har element uchun mustaqil ishlaydi va sodda amalga oshirilganda bog'liq ma'lumotni alohida-alohida oladi.
7-mashq yechimi¶
DataLoader ikki mexanizm bilan ishlaydi:
-
Batching (yig'ish): Har
seller'ni darrov so'rash o'rniga, DataLoader bir "tick" (event-loop sikli) davomida barcha kerakli sotuvchiid'larini to'playdi β[1, 2, 3, ..., 10]. Tick oxirida bularning hammasini bitta so'rovga aylantiradi:SELECT * FROM sellers WHERE id IN (1, ..., 10). Natijadaselleruchun N ta so'rov o'rniga bitta so'rov. -
Per-request kesh (dedup): Agar bir xil
idbir so'rov ichida bir necha marta kerak bo'lsa (masalan, 3 ta mahsulot bir sotuvchiga tegishli), DataLoader uni DB'dan bir marta oladi va keshdan qaytaradi.
Natija: products uchun 1 + barcha seller'lar uchun 1 = 2 ta so'rov. 1000 mahsulot bo'lsa ham 2 (yoki sahifalangan bo'lsa undan ham kam). N+1 yo'qoladi.
Muhim: DataLoader keshi bir so'rov doirasida (per-request) ishlaydi β har yangi GraphQL so'rovida yangi DataLoader yaratiladi, aks holda eski ma'lumot tarqalib ketadi.
8-mashq yechimi¶
Nega HTTP keshlash ishlamaydi (16-bob):
- GraphQL deyarli hamma narsani
POSTorqali yuboradi;POSTodatda keshlanmaydi (RFC 9111). - URL doim bitta β
/graphql; HTTP keshi "URL = kalit" tamoyiliga tayanadi, lekin bu yerda URL hech narsani farqlamaydi. - Bir xil URL'ga turli so'rovlar turli javob beradi β kesh kaliti yo'q.
Qaytarish strategiyalari:
- Persisted queries: so'rov matni o'rniga uning hash'ini yuborish; serverga tanish so'rovlarni
GETorqali (URL'da hash bilan) keshlanadigan qilish β bu CDN/brauzer keshini qaytaradi. - Ilova-darajali kesh: server ichida obyekt darajasida kesh (Redis va sh.k.), DataLoader per-request keshi.
- Mijoz tomonidagi normalizatsiyalangan kesh: Apollo/Relay javobni
idbo'yicha normalizatsiya qilib mijozda saqlaydi, takroriy so'rovlarni kamaytiradi.
Asosiy g'oya: REST'da "bepul" bo'lgan HTTP keshi GraphQL'da qo'shimcha qatlam bilan qaytariladi β bu moslashuvchanlik uchun to'lov.
9-mashq yechimi¶
Qaror: GraphQL. Asoslar:
- Over/under-fetching: Murakkab, ko'p ekranli mobil ilova har ekranda turli maydon to'plamini talab qiladi. REST'da bu yo ortiqcha maydonlar (over-fetch β mobil trafik isrofi), yo ko'p so'rov (under-fetch β sekin ekran). GraphQL'da har ekran aynan kerakli maydonlarni bitta so'rovda oladi.
- Manba aggregatsiyasi: Ma'lumot 4 ta mikroservisdan keladi. GraphQL qatlami (BFF, 23-bob) ularni yagona grafga birlashtiradi β mijoz 4 ta API bilan emas, bitta sxema bilan ishlaydi.
- Jamoa mustaqilligi: Frontend tez-tez yangi ekran qo'shadi. Maydon qo'shish GraphQL'da buzmaydigan o'zgarish, demak frontend ko'p hollarda backend'ga teginmasdan yangi so'rovlar yoza oladi β mustaqil rivojlanish.
Trade-off (nima yo'qotasiz): HTTP/CDN keshining "bepulligi"ni yo'qotasiz β uni persisted queries yoki ilova-kesh bilan qayta qurish kerak. Qo'shimcha operatsion murakkablik paydo bo'ladi: N+1'dan himoya (DataLoader), query depth/complexity limit, har resolverdagi avtorizatsiya (BOLA xavfi), monitoring. Kichik/sodda domen uchun bu murakkablik o'zini oqlamasdi β lekin tasvirlangan kontekstda (murakkab, ko'p manbali, tez o'zgaruvchan mobil mahsulot) GraphQL afzalliklari trade-off'lardan ustun. Agar domen sodda, public CRUD bo'lganida β REST tanlardik.
β¬ οΈ Oldingi: 16 β Keshlash (HTTP caching) Β· π README Β· Keyingi: 18 β gRPC va Protocol Buffers β‘οΈ