GraphQL Sorgu ve Değişimler (Mutations)

Harun Akgün
9 min readOct 19, 2018

--

Ö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;

  1. Sorgu içindeki statik değeri $degiskenAdi ile değiştirmeliyiz.
  2. $degiskenAdi’nı sorgu tarafından kabul edilen bir değişken olarak tanımlamalıyız.
  3. 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ı Charactertipinde 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

--

--