Mimari
MimariAlan çözümleme sırasını yönetme

Alan çözümleme sırasını yönetme

Multiple Query Execution tarafından sağlanan @export direktifinin amacı, bir alanın (veya alan kümesinin) değerini bir değişkene aktarmak ve bunu sorguda başka bir yerde kullanmaktır.

Bu direktif, değeri değişkene aktarmadan önce değişkenin okunması durumunda çalışmaz. Bu nedenle motorun alan yürütme sırasını kontrol etmenin bir yolunu sağlaması gerekmektedir.

Gato GraphQL, sorgunun kendisi aracılığıyla alan yürütme sırasını yönetmenin bir yolunu sunar. Motor, her tür için yinelemeli olarak veri yükler; önce sorguda karşılaştığı ilk türdeki tüm alanları çözer, ardından sorguda karşılaştığı ikinci türdeki tüm alanları çözer ve işlenecek tür kalmayana kadar bu şekilde devam eder.

Örneğin, Director, Film ve Actor türündeki nesneleri içeren şu sorgu:

{
  directors {
    name
    films {
      title
      actors {
        name
      }
    }
  }
}

...GraphQL motoru tarafından şu sırayla çözümlenir:

Türlerin yinelemeler halinde işlenmesi

İşlendikten sonra, yüklenmemiş verileri almak için (örneğin: ek nesnelerden veya halihazırda yüklenmiş nesnelerin ek alanlarından) bir tür sorguda tekrar referans alınırsa, tür yineleme listesinin sonuna yeniden eklenir.

Örneğin, Actor'ın preferredDirector alanını da sorgularsak (bu alan Director türünde bir nesne döndürür):

{
  directors {
    name
    films {
      title
      actors {
        name
        preferredDirector {
          name
        }
      }
    }
  }
}

...GraphQL motoru sorguyu şu sırayla işler:

Yinelemeler içinde tekrarlanan türler

Tek bir sorguda @export yürütmek için bunun nasıl işlediğini görelim. İlk denemede, alan yürütme sırasını düşünmeden normalde yapacağımız gibi sorguyu oluştururuz:

query GetPostsAuthorNames {
  user(by: { id: 1 }) {
    name @export(as: "authorName")
  }
  posts(filter: { search: $authorName }) {
    id
    title
  }
}

Sorgu çalıştırıldığında şu yanıtı üretir:

Değişken kullanarak sorgu yürütme

...ve şu hatayı içerir:

{
  "errors": [
    {
      "message": "Expression 'authorName' is undefined",
    }
  ]
}

Bu hata, $authorName değişkeni okunduğunda henüz ayarlanmamış olduğu anlamına gelir; undefined durumundaydı.

Bunun neden olduğunu görelim. Öncelikle, sorguda hangi türlerin göründüğünü aşağıda yorum olarak eklenmiş haliyle analiz edelim:

# Type: Root
query GetPostsAuthorNames {
  # Type: User
  user(by: {id: 1}) {
    # Type: String
    name @export(as: "authorName")
  }
  # Type: Post
  posts(filter: { search: $authorName }) {
    # Type: ID
    id
    # Type: String
    title
  }
}

Türleri işlemek ve verilerini yüklemek için veri yükleme motoru, Root sorgu türünü bir FIFO (First-In, First-Out, "ilk giren ilk çıkar") listesine ekler; bu sayede [Root], algoritmaya iletilen başlangıç listesi olur ve ardından türler üzerinde sıralı olarak şu şekilde yineler:

#İşlemListe
0FIFO listesini hazırla[Root]
1aListedeki ilk türü çıkar (Root)[]
1bRoot türünden sorgulanan tüm alanları işle:
user(by: {id: 1})
posts(filter: { search: $authorName })
Türlerini (User ve Post) listeye ekle
[User, Post]
2aListedeki ilk türü çıkar (User)[Post]
2bUser türünden sorgulanan alanı işle:
name @export(as: "authorName")
Skaler bir tür (String) olduğundan listeye eklenmesine gerek yok
[Post]
3aListedeki ilk türü çıkar (Post)[]
3bPost türünden sorgulanan tüm alanları işle:
id
title
Bunlar skaler türler (ID ve String) olduğundan listeye eklenmelerine gerek yok
[]
4Liste boş, yineleme sona erer. 

Burada sorunu görebiliriz: @export 2b adımında yürütülür, ancak 1b adımında okunmuştur.

Alan yürütme akışını kontrol etmemiz gereken yer burasıdır. Uygulanan çözüm, Root türünden self alanını yapay olarak sorgulayarak aktarılan değişkenin ne zaman okunacağını geciktirmektir.

self alanı, adından da anlaşılacağı gibi aynı nesneyi döndürür; Root nesnesine uygulandığında aynı Root nesnesini döndürür. "Zaten kök nesneye sahipsem, neden onu tekrar almam gereksin?" diye merak edebilirsiniz. Çünkü motorun algoritması bu yeni Root referansını FIFO listesinin sonuna eklemek zorunda kalacak ve biz yinelemelerin her birinden önce veya sonra sorgulanan alanları bilinçli olarak dağıtabiliriz.

Bu nedenle posts(filter:{ search: $authorName }) alanı yukarıdaki sorguda bir self alanının içine yerleştirilmiştir ve sorgu çalıştırıldığında beklenen yanıtı üretir:

query GetPostsAuthorNames {
  user(by: {id: 1}) {
    name @export(as: "authorName")
  }
  self {
    posts(filter: { search: $authorName }) {
      id
      title
    }
  }
}

@export ile ilk sorgunun çalıştırılması

Bu sorgunun neden düzgün çalıştığını anlamak için türlerin işlenme sırasını inceleyelim:

#İşlemListe
0FIFO listesini hazırla[Root]
1aListedeki ilk türü çıkar (Root)[]
1bRoot türünden sorgulanan tüm alanları işle:
user(by: {id: 1})
self
Türlerini (User ve Root) listeye ekle
[User, Root]
2aListedeki ilk türü çıkar (User)[Root]
2bUser türünden sorgulanan alanı işle:
name @export(as: "authorName")
Skaler bir tür (String) olduğundan listeye eklenmesine gerek yok
[Root]
3aListedeki ilk türü çıkar (Root)[]
3bRoot türünden sorgulanan alanı işle:
posts(filter:{ search: $authorName })
Türünü (Post) listeye ekle
[Post]
4aListedeki ilk türü çıkar (Post)[]
4bPost türünden sorgulanan tüm alanları işle:
id
title
Bunlar skaler türler (ID ve String) olduğundan listeye eklenmelerine gerek yok
[]
5Liste boş, yineleme sona erer. 

Artık sorunun çözüldüğünü görebiliriz: @export 2b adımında yürütülür ve 3b adımında okunur.

Multiple Query Execution, sorguları ayrıştırırken tam olarak bunu yapar: GraphQL belgesini self alanları ekleyerek dönüştürür; böylece her işlemdeki alanlar yalnızca önceki tüm işlemlerdeki tüm alanlar çözümlendikten sonra yürütülür.