GraphQL Sorgu ve Değişimler (Mutations)
--
Önceki sayfa > GraphQL’e Giriş
Bu sayfada bir GraphQL sunucusuna nasıl sorgu gönderileceğini öğreneceksiniz.
Alanlar (fields)
En basit haliyle GraphQL Objeler üzerindeki belirli alanları sorgulamak için kullanılır. Çok basit bir sorgu ve cevabına bakarak başlayalım:
sorgu:
{
hero {
name
}
}
cevap (JSON):
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
Hemen fark edeceğiniz gibi sorgu ve cevabı birebir aynı yapıdadır. Bu GraphQL için çok önemlidir, çünkü her zaman beklediğiniz şekilde cevap alırsınız ve sunucu her zaman istemcinin beklediği alanları tam olarak bilir.
name
alanı String
tipinde bir cevap döndürür, bu örnekte Star Wars’un kahramanlarından biri olan"R2-D2"
adı cevap olarak dönmüş.
Bir önceki örnekte kahramanımızın sadece adını sorguladık ve String tipinde bir değişken aldık. Ancak alanlar aynı zamanda obje de olabilir. Bu durumda dönen objenin alanlarını almak için alt bir seçim yapabilirsiniz. GraphQL sorguları alakalı objeleri ve bu objelerin alt alanlarını gezebilir. Bu sayede istemcilerin bir istek içerisinde birbiriyle alakalı bir çok veriyi sorgulamasını sağlar. Tek bir istek içerisinde yapılabilen bu işlem geleneksel REST mimarisinde birden fazla istek ve cevap döngüsü gerektirecektir.
sorgu:
{
hero {
name
# sorgulara yorum da yazabilirsiniz!
friends {
name
}
}
}
cevap:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Bu örnekte friends
alanının bir dizge döndürdüğüne dikkat ettiniz mi? GraphQL sorguları tekil sonuçlar ve liste halinde sonuçlar için aynı gözükür, ancak hangi türü beklememiz gerektiğini şemada (schema) tanımlandığı haline bakarak anlayabiliriz.
Arguments (argümanlar)
Yapabildiği tek şey objeleri ve içindeki alanları gezmek olsaydı dahi GraphQL veri çekmek için faydalı bir araç olurdu. Buna ek olarak alanlara argümanlar besleyebilme özelliğini de eklediğinizde işler daha da ilginçleşiyor.
sorgu:
{
human(id: "1000") {
name
height
}
}
cevap:
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}
REST gibi sistemlerde QueryString ve URL parçaları gibi sadece tek bir parametre serisi besleyebilirsiniz. Ancak GraphQL’de her bir alan ve iç içe geçmiş alt obje kendi argümanlarını alabilir. Bu sayede GraphQL birden fazla API isteği yapmak yerine kullanılabilecek tam bir çözüm haline gelir. Her seferinde istemcide veri dönüşümü yapmak yerine tek bir seferde sunucu tarafında bu işlemi halletmek için skalar alanlara bile argüman geçebilirsiniz.
sorgu:
{
human(id: "1000") {
name
height(unit: SANTIMETRE)
}
}
cevap:
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 172
}
}
}
Argümanlar birden fazla türde olabilir. Üstteki örnekte sonu olan bir seri seçenekten oluşan Enumeration Tipini kullandık. (Bu örnekte uzunluk birimleri, METRE
ya da SANTIMETRE
) GraphQL varsayılan tip serisi ile birlikte gelir, ancak herhangi bir GraphQL sunucusu kendi özelleştirilmiş tiplerini içerebilir.
GarphQL tip sistemi için daha fazla bilgi alın.
Aliaslar (takma adlar)
Keskin bir gözünüz varsa cevap objesi içindeki alanlar sorgudaki alanlarla aynı isimde olduğu için aynı alanı farklı argümanlarla sorgulayamayacağınızı fark etmişsinizdir. Bu sebeple aliaslara ihtiyaç duyarsınız. Aliaslar bir alanın cevaptaki adını istediğini şekilde değiştirmenizi sağlar.
sorgu:
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
cevap:
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
Üstteki örnekte iki hero
alanı birbiriyle çakışacaktı, ancak alias kullanarak sonuçtaki isimleri değiştirdik. Bu sayede iki cevabı tek bir sorgu içerisinde alabildik.
Fragments (parçalar)
Diyelim ki uygulamamız içerisinde göreceli olarak karmaşık bir sayfa var, bu sayfada iki kahramanın bilgilerini ve tüm arkadaşlarını yan yana görebiliyoruz. Hayal edebileceğiniz gibi sorgudaki alanları her bir kahraman için en az bir kez tekrar yazmamız gerekeceğinden böyle bir sorgu çok çabuk karmaşıklaşacaktır.
Bu sebeple GraphQL tekrar kullanılabilir fragment adı verilen birimler ile birlikte gelir. Fragmentlar bir seri alan oluşturmanıza ve bu alanları istediğiniz sorgularınız içinde kullanmanıza olanak tanır. Aşağıda bunun bir örneğini görebilirsiniz:
sorgu:
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
cevap:
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Gördüğünüz gibi yukarıdaki sorgu alanlar tekrar edilirse çok fazla karmaşıklaşacaktır. Fragment konsepti karmaşık veriler gerektiren uygulamaların sorgularını parçalara bölmek için kullanılır. Bu konsept özellikle bir çok UI bileşenini tek bir istekte birleştirmeyi gerektiren durumlarda işe yarar.
Operasyon Adı
Sorgularımızda şu ana kadar query
anahtar kelimesi ve sorgu ismini içeren kısmı dışarıda bırakan kısayollar kullandık, ancak yayına çıkacak uygulamalarda bu bilgileri de kullanmak kodumuzu daha anlaşılır kılacaktır.
Aşağıda opersayon tipini belirten query
anahtar kelimesi ve HeroNameAndFriend
olarak belirtilmiş operasyon adını görebileceğiniz bir örnek bulunuyor
sorgu:
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
cevap:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Operasyon tipi query, mutation ya da subscription olabilir ve ne tür bir operasyon yapmak istediğimizi tanımlar. Query kısayolu sentaksını kullanmadığınız sürece operasyon tipi vermeniz zorunludur. Eğer kısayol sentaksı kullanacaksanız operasyon ismi ve değişken tanımlaması yapamazsınız.
Operasyon adı operasyonunuz için kullandığınız açık ve anlaşılabilir isimdir. Sadece birden fazla operasyon içeren dokümanlarda zorunludur ancak kullanımı daha sonra hata ayıklama yaparken faydalı olduğu için teşvik edilir. Uygulamanızda bir sorun çıktığında ağınızdaki ya da GraphQL sunucunuzdaki loglara baktığınızda isim verilmiş operasyonları bulmak kolay olacaktır. Operasyon adı en sevdiğiniz programlama dilindeki fonksiyon isimleri gibidir. Bulmak, hata ayıklamak, çağrı aldığında loglamak daha kolaydır.
Değişkenler
Şimdiye kadar tüm argümanlarımızı sorguların içine yazdık. Ancak çoğu uygulamada alanlara beslenen argümanlar dinamik olacaktır. Örneğin Star Wars bölümü seçmenize yarayan bir dropdown menünüz, arama kutunuz ya da filtreleriniz olabilir.
Bu değişebilen argümanları doğrudan sorgunun içine yazmak iyi bir fikir olmayabilir, çünkü bu durum istemci tarafında her bir sorgunun çalışma anında dinamik olarak manipüle edilmesini gerektirecektir. Bunun yerine GraphQL’in değişken değerleri sorgulara besleyebilmek için geliştirdiği birinci sınıf bir yöntemi var. Bu değerlere değişken adı veriliyor.
Değişkenlerle çalışmaya başladığımızda yapmamız gereken üç şey var;
- Sorgu içindeki statik değeri
$degiskenAdi
ile değiştirmeliyiz. $degiskenAdi
’nı sorgu tarafından kabul edilen bir değişken olarak tanımlamalıyız.degiskenAdi: deger
tanımını ayrı olarak değişken kitaplığına eklemeliyiz.
Tüm bunlar bir araya geldiğinde aşağıdaki gibi gözükür:
sorgu:
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
değişken tanımı:
{
"episode": "JEDI"
}
cevap:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Bu sayede artık yeniden sorgular yazmak yerine istemci tarafında farklı değişkenler besleyerek sorgumuzu çalıştırabiliriz. Aynı zamanda bu kullanım sorgudaki hangi argümanların dinamik olabileceğini belirlemek için kullanılan iyi bir yöntemdir. Kullanıcıdan alından değerlerle sorgu oluşturmak için asla string manipülasyonu yapmamalıyız.
Değişken Tanımlamaları
Değişken tanımlamaları üstteki örnekte ($episode: Episode)
şeklinde gözükmektedir. Tüm değişkenleri başında $
işareti ve sonunda değişkenin tipi (bu durumda Episode
)olacak şekilde tanımlar.
Tanımlanmış tüm değişkenler ya scalar, ya enum ya da input obje tipinde olmalıdır. Yani bir alana karmaşık bir obje beslemek istediğinizde sunucu tarafındaki input tipini bilmeniz gerekmektedir. Input obje tipi ile ilgili daha fazla bilgi almak için Şema sayfasına bakabilirsiniz.
Değişken tanımlamaları opsiyonel yada zorunlu olabilir. Üstteki örnekte Episode
tipinin yanında !
işareti olmadığından bu değerin opsiyonel olduğunu anlıyoruz. Ancak değişkeni beslediğiniz alan null olmayan bir argüman gerektirdiğinde değişkenin de zorunlu olması gerekmektedir.
Bu değişken tanımlamalarının sentaksı ile ilgili daha fazla bilgi sahibi olmak için GraphQL Şema Dilini öğrenmek faydalı olacaktır. Şema dili şema sayfasında detaylı olarak anlatılmıştır.
Varsayılan Değişkenler
Varsayılan değişkenler tip tanımından sonra eklenerek sorguya dahil edilebilir. Aşağıdaki sorguda varsayılan değişken olarak JEDI
tanımlanmıştır.
sorgu:
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
Sorgudaki tüm değişkenler için varsayılan değer tanımlanmışsa sorguyu herhangi bir değişken beslemeden çağırabilirsiniz. Eğer herhangi bir değer beslenirse varsayılan değerleri ezecektir.
Direktifler (directives)
Dinamik sorgular oluşturmak için manuel string manipülasyonundan kaçınmamızı sağlayan değişkenler üzerine konuştuk. Argümanlara değişken besleyebilmek bu alandaki bir çok sorunu çözüyor, ancak sorgunun yapısını dinamik olarak değiştirmemize yarayan başka bir yönteme daha ihtiyacımız var. Örneğin özet ve detaylı görünüm içeren bir UI bileşenimiz olduğunu hayal edelim, bunlardan biri diğerinden daha fazla alan içeriyor.
Böyle bir bileşen için bir sorgu yazalım:
sorgu:
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}
değişken tanımı:
{
"episode": "JEDI",
"withFriends": true
}
cevap:
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
GraphQL’in direktif adındaki yeni bir özelliğini kullanmaya ihtiyaç duyduk. Direktifler bir alana yada fragment’a ilişkilendirilebilir ve sorgunun yürütülmesini sunucunun istediği gibi yapmasını sağlar. Çekirdek GraphQL spesifikasyonu tam olarak iki direktif içerir ve bu direktifler spesifikasyona uyumlu GraphQL sunucu implementasyonlarında desteklenmek zorundadır.
@include(if: Boolean)
argümanın sonucutrue
ise bu alanı dahil eder.@skip(if: Boolean)
argümanın sonucutrue
ise bu alanı eklemez.
Direktifler alan eklemek ve çıkarmak için string manipülasyonu yapmak zorunda olacağınız durumları engellemek için vardır. Sunucu implementasyonları deneysel ek özellikler getiren yeni direktifler içerebilir.
Mutations (değişimler)
GraphQL ile ilgili bilgi paylaşımlarının çoğu veri çekme üzerine odaklanır, ama tam bir veri platformu sunucu tarafındaki veriyi manipüle edebilmeye de ihtiyaç duyar.
REST mimarisinde herhangi bir sorgu sunucu tarafında yan etkiler (side-effects) oluşturabilir, ama GET
isteklerinde sunucu tarafındaki verinin manipüle edilmemesi önerilir. GraphQL’de de benzeri bir durum vardır, teknik olarak query (sorgu) veri manipülasyonu yapabilecek şekilde implemente edilebilir. Ancak veri değişimine sebep olabilecek herhangi bir operasyonun mutationlar aracılığıyla gerçekleştirilmesini gerektiren bir gelenek oluşturmak çok faydalı olacaktır.
Sorgularda olduğu gibi mutation’larda da dönen alan bir obje tipindeyse bu nesne içindeki alanları almak için sorgu yazabilirsiniz. Bu durum herhangi bir veri güncellemesinden sonra güncellenen veriyi kullanmak için çok faydalıdır. Basit bir mutation örneğine bakalım:
mutation:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
değişken tanımı:
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
cevap:
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
createReview
alanının yeni oluşturulmuş bir review için stars
ve commentary
alanlarını döndürdüğünü görüyorsunuz. Bu durum özellikle mevcut veriyi güncellediğimizde faydalıdır. Örneğin bir alanın değerini arttırdığımızda. Çünkü bu sayede tek sorgu içinde hem değeri değiştirebiliriz, hem de yeni değeri alabiliriz.
Aynı zamanda üstteki sorguda bulunan review
değişkenine geçtiğimiz değerin scalar olmadığını fark etmiş olabilirsiniz. Bu bir Input objesi tipi, argüman olarak geçilebilecek özel bir obje tipi. Input obje tipi ile ilgili daha fazla bilgi almak için Şema sayfasına bakabilirsiniz.
Mutasyonlar içinde birden fazla alan kullanmak
Bir mutasyon aynen sorgularda olduğu gibi birden fazla alan içerebilir. İsimleri dışında mutation ve sorgu arasında önemli bir fark vardır.
Sorgu alanları paralel olarak çalıştırılırken mutation alanları birbiri arkasına seriler halinde çalıştırılır.
Bu demek oluyor ki bir çağrı içinde iki incrementCredits
mutation’u gönderiyorsak önce ilkinin tamamlanıp sonrasında ikinciye geçeceğinden emin olabiliriz.
Inline Fragments (satır içi parçalar)
Diğer tüm tip sistemleri gibi, GraphQL şemaları da interface ve union tipleri tanımlamaya olanak tanır. Şema sayfasında bunlar hakkında daha fazla bilgi alabilirsiniz.
Bir interface ya da union tipi dönen alanı sorguluyorsanız alttaki tip içindeki veriye erişebilmek için inline fragment’lar kullanmak durumunda kalırsınız. Bunu bir örnekle gösterelim:
sorgu:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
değişken tanımı:
{
"ep": "JEDI"
}
cevap:
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
Bu sorguda hero
alanı Character
tipinde bir sonuç döndürür. Karakterler episode
argümanına göre ya Human
ya da Droid
olabilir. Doğrudan bir seçimde name
gibi sadece Character
interface’i içinde bulunan alanları sorgulayabilirsiniz.
Concrete tip içerisinde bulunan bir alan için sorgu yaptığınızda bir tip koşulu ile birlikte inline fragment kullanmanız gerekmektedir. İlk fragment ... on Droid
olarak etiketlendiği için primaryFunction
alanı hero
sorgusundan dönen Character
‘in tipi Droid
ise çalıştırılacaktır. Aynı şekilde dönen tip eğer Human
ise height
alanı çalıştırılır.
İsimlendirilmiş fragment’ler de aynı şekilde kullanılabilir, çünkü isimlendirilmiş fragmanlar her zaman bir tip ile ilişkilendirilir.
Meta alanları
Bir GraphQL servisinden dönecek alanın tipini bilmediğiniz durumlarda veriyi işleyebilmek için istemci tarafında kullanmak üzere bir yola ihtiyaç duyarsınız. GraphQL bir meta alanı olan__typename
çağırmanıza olanak tanır. Bu sayede sorgu içerisinde objenin tipi istenilen noktada alınıp kullanılabilir.
sorgu:
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
cevap:
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo"
},
{
"__typename": "Human",
"name": "Leia Organa"
},
{
"__typename": "Starship",
"name": "TIE Advanced x1"
}
]
}
}
Üstteki sorguda,search
union tipte bir sonuç döndürür. Bu sonuç üç tipten biri olacaktır. Değişik tiplere sahip bu cevaptaki veri türlerini __typename
alanı olmadan bilmek mümkün değildir.
GraphQL servisleri introspection sisteminde kullanmak üzere bir kaç farklı meta alanı içerir.
Okumaya Devam Et > Şemalar ve Tipler