GraphQL Sorgu Çalıştırma
Önceki Sayfa > Doğrulama
Bir GraphQL sorgusu, doğrulaması yapıldıktan sonra çalıştırılması için GraphQL sunucusuna iletilir.
Bir tip sistemi olmadan GraphQL sorguları çalıştıramaz. Bir sorgunun nasıl çalıştırıldığını göstermek için örnek bir tip sistemi kullanalım. Bu tip sistemi daha önceki örneklerimizde kullandığımız tip sisteminin bir parçasıdır.
type Query {
human(id: ID!): Human
}type Human {
name: String
appearsIn: [Episode]
starships: [Starship]
}enum Episode {
NEWHOPE
EMPIRE
JEDI
}type Starship {
name: String
}
Bu sorgu çalıştırıldığında neler olduğunu anlatmak için uçtan uca bir örnek yapalım:
sorgu:
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
cevap:
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}
GraphQL sorgularındaki her alanı sonraki tipte veri dönen önceki tip methodu ya da fonksiyonu olarak düşünebilirsiniz. Aslında GraphQL tam olarak böyle çalışır. Her alandaki her tip GraphQL sunucusunun geliştiricisi tarafından sağlanan resolver adında bir fonksiyon tarafından desteklenir. Bir alan çalıştırıldığında bu alanla ilişkili resolver çağırılır ve bir sonraki değeri üretir.
Eğer bir alan string ya da integer gibi skalar bir değer üretirse çalıştırma tamamlanmış demektir. Ancak bir alan obje tipinde değer üretirse sorgu bu objenin alt alanları için de çalışmaya devam eder. Çalıştırma süreci skalar değerlere ulaşılana kadar devam eder. GraphQL sorguları her zaman skalar değerlerle tamamlanır.
Kök alanlar & Resolver’lar
Her GraphQL sunucusunun en üst seviyesinde GraphQL API’sine mümkün olan tüm giriş noktalarını temsil eden bir tip tanımlanır. Bu tip genellikle Root Tipi ya da Query tipi olarak adlandırılır.
Bu örnekte Query tipimiz human
adında argüman olarak id
bekleyen bir alan sağlar. Bu alanla ilişkili olan resolver fonksiyon muhtemelen veritabanına bağlanacak ve Human
objesini oluşturacaktır.
Query: {
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
}
Bu örneği JavaScript ile yazdık, ancak GraphQL sunucusu bir çok başka dille yazılmış olabilir. Her resolver fonksiyonu dört adet argüman alır;
obj
Önceki obje, genellikle kök sorgu üzerindeki alanlar için kullanılmaz.args
GraphQL sorgusundan alana özel olarak gönderilmiş olan argümanlar.context
Her resolver tarafından sağlanan ve mevcut giriş yapmış kullanıcı ya da veritabanına erişme bilgisi gibi önemli içerik(context) bilgilerini içeren bir değer.info
alana özel ve mevcut sorgu ile ilişkili bilgileri ve şema detaylarını içeren bir değer. Daha fazla bilgi için GraphQLResolveInfo tipine göz atın.
Asenkron Resolver’lar
Resolver fonksiyonu içinde neler olduğuna yakından bakalım:
human(obj, args, context, info) {
return context.db.loadHumanByID(args.id).then(
userData => new Human(userData)
)
}
GraphQL sorgusu tarafından iletilen argümanlar içerisindeki id parametresini kullanarak kullanıcı verisini veri tabanından çekmek için gereken veritabanı erişimi context kullanılarak alınmış. Veri tabanına sorgu atmak asenkron bir işlem olduğu için bu fonksiyon bir Promise döner. JavaScript’te promise’ler asenkron değerlerle çalışmak için kullanılır. Ancak benzeri konseptler diğer birçok dilde de vardır, bunlara genel olarak Futures, Tasks ya da Deferred adı verilir. Veritabanı işini bitirip değer döndürdükten sonra yeni bir Human
objesi oluşturulup işleme devam edilir.
Resolver fonksiyonlar Promise’ler göz önünde bulundurarak tasarlanmalıdır, ancak GraphQL sorguları Promise’ler ile ilgilenmez. Sadece human
alanı daha sonra name
alanı içeren bir obje olarak dönsün ister. Çalıştırma sırasında GraphQL Promise’ler, Future’ler ve Task’ların tamamlanmasını bekler ve bu işi optimal eşzamanlılıkta yapar.
Basit Resolverlar
Artık Human
objesi elimizde olduğuna göre GraphQL alt alanlar ile ilgili yürütmeye devam edebilir.
Human: {
name(obj, args, context, info) {
return obj.name
}
}
Bir GraphQL sunucusu bir sonraki adımı belirleyen bir tip sistemi ile desteklenmektedir. human
alanı herhangi bir değer döndürmeden önce bile GraphQL sonraki adımı bilir.
Bu durumda Human içerisindeki name alanını çözümlemek çok kolaydır. name resolver fonksiyonu çağırılır, argümanlar arasındaki obj bir önceki alanda yer alan new Human
objesidir. Bu durumda Human objesinin name
adında bir alt alanı olduğunu bildiğimizden doğrudan bu bilgiyi okuyup dönebiliriz.
Aslında bir çok GraphQL kütüphanesi bu tarz basit resolver’ları yazmanıza gerek bırakmaz. Alan için bir resolver olmadığında aynı isimdeki property’nin okunup dönülmesi gerektiğini varsayar.
Skalar’a Zorlama
name
alanının değeri çözümlenirken, eş zamanlı olarak appearsIn
ve starships
alanları da çözümlenebilir. appearsIn
alanı için de çok basit bir resolver yazılabilir, yakından bakalım:
Human: {
appearsIn(obj) {
return obj.appearsIn // [ 4, 5, 6 ] döner
}
}
Farkındaysanız tip sistemimiz appearsIn
alanının Enum bir değer döneceğini belirtiyor, ancak bu fonksiyon bir rakam döner. Aslında sonuç objesine bakarsak bu alanın gerçekte Enum değerleri döndürdüğünü fark ederiz. Burada ne oluyor?
Bu durum skalar’a zorlama’nın (scalar coercion) bir örneğidir. Tip sistemi bu resolver’ın üreteceği sonuçtan ne bekleyeceğini bilir ve dönen değeri başka bir resolver fonksiyon sayesinde API dökümantasyonunda belirtilen değere çevirir. Bu durumda, içinde 4,5 ve 6 gibi rakamları kullanan bir Enum beklenmesi gerektiği belirtilmiş olabilir, ancak GraphQL tip sistemi bu değerleri Enum değerleri olarak görüntüleyebilir.
Liste Resolver’ları
Bir önceki örnekte appearsIn
gibi bir alan bir liste döndüğünde ne olduğunu biraz gözlemleme şansı bulduk. Enum değerlerden oluşan bir liste döndü, ve tip sistemimiz de bunu beklediği için dönen değerleri alakalı Enum değerlerle değiştirdi. Peki starships
alanı çözümlendiğinde ne olur?
Human: {
starships(obj, args, context, info) {
return obj.starshipIDs.map(
id => context.db.loadStarshipByID(id).then(
shipData => new Starship(shipData)
)
)
}
}
Bu alanın çözümleyicisi sadece bir Promise değil, bir Promise’ler listesi döner. Human
objesi içinde bu kişinin kullandığı her birStarships
objesinin id’sini döndü. Bu durumda her bir starship objesini bu id’leri kullanarak çözümlememiz gerekiyor.
GraphQL eş zamanlı olarak tüm bu Promise’lerin çözümlenmesini bekler, ve bir liste ile karşılaştığında listedeki her bir elemanın name
alanını eşzamanlı olarak yüklemeye devam eder.
Sonuç Üretmek
Her bir alan çözümlenirken sonuç olarak üretilen değer alan adı ya da alias’ı key, çözümlenen değer ise value olmak üzere bir key-value objesine yerleştirilir. En dipten başlayarak Query tipine kadar bu işlem devam eder. Tüm bu çözümlemeler tamamlanınca yapılan sorgunun yapısını birebir yansıtan bir sonuç üretilir ve genellikle JSON olarak isteği yapan istemciye dönülür.
Sorgu ve cevaba son kez bakıp tüm çözümleyici fonksiyonlar çalıştıktan sonra ortaya çıkan sonucu görelim:
sorgu:
{
human(id: 1002) {
name
appearsIn
starships {
name
}
}
}
cevap:
{
"data": {
"human": {
"name": "Han Solo",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"starships": [
{
"name": "Millenium Falcon"
},
{
"name": "Imperial shuttle"
}
]
}
}
}