Uygulamayı Farklı GraphQL Sunucularıyla Çalışacak Şekilde Tasarlamak
"Uygulamaları arayüzlere karşı, uygulamalara değil yazın" ilkesi; bir işlevi doğrudan çağırmak yerine, hangi girdilerin gerekli olduğunu ve beklenen çıktının ne olduğunu listeleyen bir sözleşme aracılığıyla çağırmak ve uygulamanın nasıl yapıldığını gizlemek anlamına gelir. Bu strateji, uygulamayı belirli bir implementasyondan, sağlayıcıdan veya yığından bağımsız kılmaya yardımcı olur; uygulama kodunu değiştirmek zorunda kalmadan bunlar arasında geçiş yapmayı mümkün kılar.
Bu stratejiyi GraphQL ile de uygulayabiliriz. GraphQL, uygulama ile sunucu arasında aracı görevi görebilir; tüm gerekli değişiklikleri yalnızca GraphQL queries üzerinde yapmamızı, iş mantığını ise dokunulmaz bırakmamızı sağlar.
Bir GraphQL query'si, istemci ile sunucu arasında bir arayüz görevi görür. Bir query çalıştırılırken, GraphQL sunucusu onu işler ve gerekli verileri istemciye döndürür. Veriler nereden geliyor? Nasıl elde edildi? İstemci bilmez ve önemsemez.

Query'ye verilen yanıt, query ile aynı şekle sahip olacaktır. Bu GraphQL query'si için:
{
post(by: { id: 1 }) {
id
title
}
}...yanıt şu şekilde olacaktır:
{
"data": {
"post": {
"id": 1,
"title": "Hello world!"
}
}
}Aynı query farklı parametrelerle verildiğinde, döndürülen veriler farklı olacaktır; ancak şekil sabit kalacaktır. Bu, query değişmediği sürece uygulamanın verileri nasıl okuyup işleyeceğine dair mantığını değiştirmesi gerekmediği ve benzer şekilde hangi GraphQL sunucusunun query'yi çalıştırdığının önemi olmayacağı anlamına gelir.
Dolayısıyla bir GraphQL sunucusunu başka biriyle sorunsuzca değiştirebiliriz.
Queries, GraphQL Şemasına Bağlıdır
Ancak son paragraf biraz fazla iyimser, çünkü GraphQL query'sinin GraphQL sunucusuna bağlı olarak değişmesi gerekebilir. Daha kesin olmak gerekirse, query GraphQL şemasına dayanır; farklı sunucular farklı şemalar sunuyorsa query de farklı olacaktır.
Örneğin, Cursor Connections Specification kullanan bir GraphQL sunucusu şu query'yi çalıştırabilir:
{
categories(first: 10000) {
edges {
node {
categoryId
description
id
name
slug
}
}
}
}WordPress tarzı sayfalama kullanan başka bir sunucu (Gato GraphQL gibi) ise aynı query'yi şu şekilde çalıştıracaktır:
{
postCategories(pagination: { limit: 10000 }) {
id
description
globalID
name
slug
}
}İki query arasındaki farkları görebiliriz:
| Özellik | Sunucu #1 | Sunucu #2 |
|---|---|---|
| Yazı kategorileri alanı | categories | postCategories |
| Sonuç sayısını sınırlamak için alan argümanı | first | pagination.limit |
Bir nesnenin id alanı şunu temsil eder | benzersiz global ID'si | tipi için benzersiz ID'si |
| Query'nin şekli | edges.node nedeniyle daha derin | daha düz |
Uygulamadaki birinci sunucuya ait query'yi ikinci sunucunun eşdeğeriyle değiştirmek tek başına işe yaramaz. Bunun nedeni, mantığın yanıttaki verilere orijinal query'nin şekline ve alanlarına göre erişmeye devam etmesidir.
Olası bir çözüm, istemcideki veri alma mantığını da değiştirmektir. Örneğin, aşağıdaki mantık:
const categories = data?.data.categories.edges.map(({ node = {} }) => node);...şu şekilde değiştirilebilir:
const categories = data?.data.postCategories;Ama tam olarak kaçınmak istediğimiz şey bu. Değişiklikleri minimum düzeyde tutmak, yalnızca arayüzü (GraphQL query'sini) değiştirmek ve iş mantığını değiştirmeden korumak istiyoruz.
Neyse ki, yalnızca GraphQL queries değiştirilerek farkları kapatmak mümkündür; aşağıdaki adımlar izlenerek:
- GraphQL queries'i uygulamadan bağımsız tutmak
- Alan adlarını alias'lar aracılığıyla uyarlamak
- Yanıtın şeklini bir
selfalanı aracılığıyla uyarlamak
Bu 3 adım aracılığıyla bir uygulamayı farklı bir GraphQL sunucusuna nasıl yönlendirebileceğimizi görelim.
GraphQL Queries'i Uygulamadan Bağımsız Tutmak
GraphQL queries'i uygulama mantığından ayırmak şunları gerektirir:
- Her GraphQL query'sini (veya bir grubunu) ayrı bir dosyada ve hepsini belirli bir klasörde saklamak
- Queries'i dışa aktarmak ve uygulamaya içe aktarmak
Örneğin, her GraphQL query'sini src/data altında ayrı bir dosyaya yerleştirebilir ve dışa aktarabiliriz:
// file `src/data/categories.js`
export const QUERY_ALL_CATEGORIES = gql`
{
categories(first: 10000) {
edges {
node {
databaseId
description
id
name
slug
}
}
}
}
`;Uygulama daha sonra GraphQL query'sini içe aktarabilir ve kullanabilir:
import { QUERY_ALL_CATEGORIES } from 'data/categories';
export async function getAllCategories() {
const apolloClient = getApolloClient();
const data = await apolloClient.query({
query: QUERY_ALL_CATEGORIES,
});
const categories = data?.data.categories.edges.map(({ node = {} }) => node);
return {
categories,
};
}Bu yapı sayesinde, tüm değişikliklerin yalnızca src/data altındaki dosyalarda yapılması gerekmektedir.
Alan Adlarını Alias'lar Aracılığıyla Uyarlamak
Bir alan alias'ı, ikinci GraphQL sunucusunun yanıtındaki bir alanı birinci sunucudaki o alanın adıyla yeniden adlandırmak için kullanılabilir.
Bu sayede postCategories, id ve globalID alanları, uygulamanın beklediği adlar kullanılarak alınabilir: sırasıyla categories, categoryId ve id:
{
categories: postCategories(pagination: { limit: 10000 }) {
categoryId: id
description
id: globalID
name
slug
}
}categories alanının first argümanına sahip olduğunu, buna karşılık gelen postCategories alanının ise pagination.limit argümanını kullandığını lütfen unutmayın. Ancak alan argümanları yanıttaki alan adına yansımadığından, bunlar hakkında endişelenmemize gerek yoktur.
Yanıtın Şeklini Bir self Alanı Aracılığıyla Uyarlamak
Son zorluk biraz daha karmaşıktır: Cursor Connections spec'inden gelen edges ve node için ek seviyeleri ekleyerek yanıtın şeklini değiştirmemiz gerekmektedir.
Bunu başarmak için, GraphQL şemasındaki tüm tiplere uygulandığı nesneyi geri yansıtan bir self alanı ekleyeceğiz:
type QueryRoot {
self: QueryRoot!
}
type Post {
self: Post!
}
type User {
self: User!
}self alanı, sorgulanan nesneden ayrılmadan query'ye ek seviyeler eklenmesine olanak tanır. Bu query çalıştırıldığında:
{
__typename
self {
__typename
}
post(by: { id: 1 }) {
self {
id
__typename
}
}
user(by: { id: 1 }) {
self {
id
__typename
}
}
}...şu yanıt üretilir:
{
"data": {
"__typename": "QueryRoot",
"self": {
"__typename": "QueryRoot"
},
"post": {
"self": {
"id": 1,
"__typename": "Post"
}
},
"user": {
"self": {
"id": 1,
"__typename": "User"
}
}
}
}Artık nodes ve edge seviyelerini yapay olarak eklemek için self kullanabiliriz:
{
categories: self {
edges: postCategories(pagination: { limit: 10000 }) {
node: self {
categoryId: id
description
id: globalID
name
slug
}
}
}
}edges ve self için GraphQL şemasındaki nesnenin türü açıkça farklıdır. Ancak bu, uygulama için önemli değildir; çünkü uygulama, GraphQL sunucusunda modellenen gerçek nesneyle etkileşime girmez. Bunun yerine, verileri bir JSON nesnesi olarak alır ve bir PostConnection ya da Post nesnesinden gelen bir alan için o veri parçası aynı olacaktır.
categories alanının self aracılığıyla, edges alanının ise postCategories aracılığıyla çözümlendiğini (tersinin değil) lütfen unutmayın. Bu, döndürülen öğelerin kardinalitesinin Cursor Connections spec'ini kullanan alanlar tarafından tanımlananla uyuşmasını sağlamak içindir:
type RootQuery {
categories: RootQueryToCategoryConnection
}
type RootQueryToCategoryConnection {
edges: [RootQueryToCategoryConnectionEdge]
}
type RootQueryToCategoryConnectionEdge {
node: Category
}Uyarlanmış GraphQL query'si tersine olsaydı (yani categories: postCategories ve edges: self şeklinde sorgulanıyor olsaydı), verilere erişim başarısız olurdu; çünkü data.categories bir dizi olurdu ve şu işlem çalıştırılırken data.categories.edges bir hataya yol açardı:
const categories = data?.data.categories.edges.map(({ node = {} }) => node);Tüm Queries'i Uyarlamak
Aynı strateji src/data içindeki tüm GraphQL queries'e uygulandıktan sonra, uygulama bir GraphQL sunucusundan diğerine kolayca geçebilir.