Persisted queries ile cache control
GraphQL genellikle POST aracılığıyla çalışır; tüm queries tek bir endpoint'e karşı çalıştırılır ve parametreler isteğin gövdesinden geçirilir. O tek endpoint'in URL'si farklı yanıtlar üretecektir; bu da URL'yi tanımlayıcı olarak kullanarak önbelleğe alınamayacağı anlamına gelir.
Dolayısıyla GraphQL'de önbelleklemeyi desteklemenin standart yolu istemci katmanındadır: döndürülen nesneleri birbirinden bağımsız olarak önbelleğe alan, benzersiz global ID'leriyle tanımlayan Apollo client ve benzeri kütüphaneler aracılığıyla.
(Buna karşın, sunucu tarafında önbelleğe alırken normalde URL'yi tanımlayıcı olarak kullanırız ve yanıttaki tüm varlıklara ait verileri bir arada önbelleğe alırız.)
Ancak bu çözümün birkaç dezavantajı vardır:
- Uygulama, istemci tarafında çalıştırmak için daha fazla JavaScript içerir. Web sitesine düşük kaliteli bir cep telefonuyla erişmek performans kaybına yol açacaktır
- Uygulama, daha fazla hareketli parçayla daha karmaşık hale gelir; artık önbellekleme katmanını uygulamayı da düşünmemiz gerekir
- Herkes JavaScript bilmez (örneğin: web sitesi PHP ile kodlanmış olabilir), ama şimdi JS ile ilgilenmek de bir sorumluluk haline gelir
Çok daha iyi bir çözüm HTTP önbellekleme kullanmaktır. Bunun işe yaraması için gereken ön koşulları görelim.
GET ile GraphQL'e Erişim
HTTP önbellekleme kullanmak, GraphQL yanıtını URL'yi tanımlayıcı olarak kullanarak önbelleğe alacağımız anlamına gelir. Bunun 2 sonucu vardır:
- GraphQL'in tek endpoint'ine
GETüzerinden erişmemiz gerekir - Query ve değişkenleri URL parametreleri olarak geçirmemiz gerekir
O halde tek endpoint /graphql ise GET işlemi /graphql?query=...&variables=... URL'sine karşı çalıştırılabilir.
Bu, sunucudan veri almak için (query işlemi aracılığıyla) geçerlidir. Veri değiştirmek için (mutation işlemi aracılığıyla) hâlâ POST kullanmamız gerekir. Burada herhangi bir sorun yoktur; çünkü mutation'lar her zaman taze olarak çalıştırılır; bir mutation'ın sonuçlarını önbelleğe alamayız, dolayısıyla HTTP önbelleklemeyi zaten kullanmayız.
Bu yaklaşım çalışır (ve hatta resmi sitede önerilir), ancak dikkat etmemiz gereken bazı hususlar vardır.
GraphQL Queries'ini URL Parametresi Olarak Kodlama
Bir GraphQL query'si normalde birden fazla satıra yayılır. Örneğin:
{
posts {
id
title
}
}Ancak bu çok satırlı dizeyi doğrudan URL parametresine giremeyiz.
Çözüm, onu kodlamaktır. Örneğin, GraphiQL istemcisi yukarıdaki query'yi şöyle kodlar:
%7B%0A%20%20posts%20%7B%0A%20%20%20%20id%0A%20%20%20%20title%0A%20%20%7D%0A%7D
Peki, bu çalışıyor. Ama pek iyi görünmüyor, değil mi? O query'den kim anlam çıkarabilir?
GraphQL'in erdemlerinden biri, queries'inin anlaşılmasının çok kolay olmasıdır. Biraz pratikle, query'yi gördüğümüzde hemen anlıyoruz. Ancak kodlandıktan sonra bunların hepsi gidiyor ve yalnızca makineler onu kavrayabiliyor; insan denklemin dışında kalıyor.
Başka bir çözüm, query'deki tüm satır sonlarını boşlukla değiştirmek olabilir; bu, satır sonlarının query'ye anlamsal anlam katmaması nedeniyle çalışır. O zaman yukarıdaki query şu şekilde temsil edilebilir:
?query={ posts { id title } }
Bu basit queries için iyi çalışır. Ancak gerçekten uzun bir query'niz varsa, pek çok { } açıp kapatıyorsanız ve alan argümanları ile direktifler ekliyorsanız, anlamak giderek zorlaşır.
Örneğin şu query:
{
posts(limit:5) {
id
title @titleCase
excerpt @default(
value:"No title",
condition:IS_EMPTY
)
author {
name
}
tags {
id
name
}
comments(
limit:3,
order:"date|DESC"
) {
id
date(format:"d/m/Y")
author {
name
}
content
}
}
}Şu tek satırlık query'ye dönüşürdü:
{ posts(limit:5) { id title @titleCase excerpt @default(value:"No title", condition:IS_EMPTY) author { name } tags { id name } comments(limit:3, order:"date|DESC") { id date(format:"d/m/Y") author { name } content } } }
Yine, query'yi çalıştırmak işe yarar; ancak ne çalıştırdığımızı bilemeyiz.
Query ayrıca fragment'lar içeriyorsa, o zaman tamamen vazgeçin; ondan anlam çıkarmak artık mümkün değildir.
Persisted Queries İmdada Yetişiyor
Query'yi URL'ye geçirmek tatmin edici değilse, başka ne seçeneğimiz var? Peki, query'yi URL'ye geçirmemek!
Bu yaklaşıma "persisted query" denir: Query'yi sunucuda saklıyoruz ve onu almak için bir tanımlayıcı (sayısal bir ID veya query girdi olarak alınarak bir hashing algoritması uygulanarak üretilen benzersiz bir dize gibi) kullanıyoruz. Son olarak, query yerine bu tanımlayıcıyı URL parametresi olarak geçiriyoruz.
Örneğin, query 2908 ID'siyle (veya "50ac3e81" gibi bir hash ile) tanımlanabilir ve ardından GET işlemini /graphql?id=2908 URL'sine karşı çalıştırırız. GraphQL sunucusu bu ID'ye karşılık gelen query'yi alır, çalıştırır ve sonuçları döndürür.
Gato GraphQL bunu daha da basit hale getirir: bir persisted query, özel bir içerik türü olarak uygulanır; böylece herhangi bir normal gönderi gibi oluşturup yayımlayabiliriz ve seçtiğimiz slug (varsayılan olarak girdiğimiz başlığa dayalı) tanımlayıcısı olacaktır. Persisted queries, HTTP önbelleklemenin uygulanmasını son derece kolaylaştırır.
max-age Değerini Hesaplama
HTTP önbellekleme, yanıtta Cache-Control başlığı gönderilerek çalışır; yanıtın önbelleğe alınması gereken süreyi belirten bir max-age değeri veya önbelleğe alınmaması gerektiğini belirten no-store içerir.
GraphQL sunucusu, farklı alanların farklı max-age değerlerine sahip olabileceği göz önüne alındığında, query için max-age değerini nasıl hesaplayacaktır?
Yanıt şudur: Query'de istenen tüm alanlar için max-age değerini alın ve en düşük olanı bulun. Bu, yanıtın max-age değeri olacaktır.
Örneğin, User türünde bir varlığımız olduğunu varsayalım. Bu varlığa atanan davranışa göre, ilgili alanın ne kadar süre önbelleğe alınabileceğini atayabiliriz:
🛠 ID'si asla değişmez ⇒ id alanına 1 yıllık max-age veririz
🛠 URL'si çok nadiren (varsa) güncellenir ⇒ url alanına 1 günlük max-age veririz
🛠 Kişinin adı zaman zaman değişebilir (örn: bir durum eklemek veya "Milton (maske takıyor)" demek için) ⇒ name alanına 1 saatlik max-age veririz
🛠 Sitedeki kullanıcının karma'sı her an değişebilir (örn: birisi yorumunu olumlu oyladıktan sonra) ⇒ karma alanına 1 dakikalık max-age veririz
🛠 Oturum açmış kullanıcının verilerini sorguluyor isek, hangi alanı getirdiğimizden bağımsız olarak yanıt hiç önbelleğe alınamaz ⇒ max-age no-store olmalıdır
Sonuç olarak, aşağıdaki GraphQL queries'ine verilen yanıtlar şu max-age değerlerine sahip olacaktır (bu örnek için Root.users alanının max-age değerini görmezden geliyoruz, ancak pratikte bu da dikkate alınacaktır):
| Query | max-age değeri |
|---|---|
| 1 yıl |
| 1 gün |
| 1 saat |
| 1 dakika |
| no-store (önbelleğe alma) |
Cache Control List Oluşturma
Her alan için max-age değerini belirledikten sonra, bu bilgileri bir Cache Control List aracılığıyla gireriz:

Gato GraphQL daha sonra yanıtın max-age değerini otomatik olarak hesaplar ve bunu Cache-Control HTTP başlığı olarak geri gönderir.