GraphQL Nedir?

GraphQL Nedir?

Merhabalar,

Bu yazıda API’lar için bir veri sorgulama dili olan GraphQL’den bahsedeceğiz. Öncesinde GraphQL’e kadar uzanan süreçten ve neden GraphQL’e ihtiyaç duyduğumuzdan bahsetmek istiyorum.

Web ve Mobil uygulama sayısı günden güne büyük bir artış göstermektedir. Uygulama sayısının bu kadar artmasıyla beraber veriye olan ihtiyaçta aynı oranda artmıştır. Bu gelişme API’ların kullanımını oldukça arttırmıştır. Bu doğrultuda REST mimarisi en çok tercih edilen mimarilerin başında gelmiştir.

REST (Representational State Transfer), modern web uygulamaları için kullanılan bir mimaridir. Bu mimari, uygulama bileşenleri arasındaki etkileşimleri tanımlamak için kullanılan bir yaklaşımdır.

REST, HTTP protokolünü kullanarak, web servisleri gibi uzak sunuculardan veri almak ve bu verileri sunmak için kullanılır. RESTful servisler, URL’ler aracılığıyla kaynakları tanımlar ve HTTP metodlarını (GET, POST, PUT, DELETE vb.) kullanarak bu kaynaklarla etkileşime girer.

Bir örnek ile anlatmak gerekirse, bir RESTful servis kullanarak bir öğrenci kaydı oluşturma sürecini ele alabiliriz. Örneğin bir öğrenci kaydı oluşturmak için, bir istemci (örneğin bir web uygulaması), kaydı oluşturmak istediği sunucuya bir HTTP POST isteği gönderir.

Sunucu, isteği aldıktan sonra, verileri doğrular ve bir cevap (örneğin bir 201 Created yanıtı) gönderir. İstek başarısız olduğunda, sunucu uygun bir hata kodu gönderir.

Bu şekilde, istemci ve sunucu arasında RESTful bir servis aracılığıyla iletişim kurulur ve kaynaklar CRUD işlemleri için kullanılabilir hale getirilir.

İşte bu şekilde RESTful servisleri sıklıkla kullanmaktayız ancak bu kullanımı gerçekleştirirken zaman içinde ihtiyaçların artması ve çeşitlenmesi ile RESTful servisler bize bazı problemler çıkarabilmektedir.

REST API kullanırken yaşanabilecek temel sorunlar aşağıdaki gibidir;

  • Overfetching (Gereksiz Veri Getirme): RESTful API’larda her bir sorgu, sadece ihtiyacımız olan verileri almak yerine, tüm verileri getirebilir. Bu da gereksiz yere fazla miktarda veri transferi yapmamıza neden olur. Bir istekte gereksiz veriler de getirilirse, ağ trafiği artar ve istekler daha yavaş gerçekleşir. Sonuç olarak uygulama performansını olumsuz etkiler.
  • Underfetching (Yetersiz Veri Getirme): Eğer uygulama birden fazla kaynaktan veri çekiyorsa, birden fazla HTTP çağrısı yapmamız gerekebilir. Bu durumda performans açısından bir sorun ortaya çıkabilir. Örneğin, bir blog yazısı listesi görüntülemesi yapmak istediğimizde, biri blog yazılarına, diğeri de yazar bilgilerine ulaşmak için iki HTTP isteği yapmamız gerekebilir. Yani bir istekte yeterli veri getirilmediğinde, istemci ek istekler yapmak zorunda kalabilir, bu da ağ trafiğini ve işlem süresini artırabilir. Aynı zamanda istekler birbirlerinin sonucuna bağlı olmuş olur. Önce ilgili blog bilgisine erişmemiz gerekir ardından bu gelen bilgideki yazar id gibi bir alan yardımıyla da başka bir endpoint ile yazar bilgilerini elde etmemiz gerekir.
  • API versiyonlama: Yeni bir özellik eklemek istediğinizde, API’ı değiştirmemiz ve tüm istemci uygulamalarını güncellemeniz gerekebilir. Ya da yeni bir versiyon yayınlayıp müşterileri bu versiyonu kullanmaları konusunda bilgilendirmemiz gerekebilir. Bu, hem zaman alıcı hem de zor bir süreç olabilir.

İşte Facebook da yeni mobil uygulama geliştirme sürecinde yukarıdaki sorunlarla fazlasıyla karşılaştı. Mevcut API’ların da karmaşıklığı düşünülünce yeni bir API yaklaşımının ortaya çıkması kaçınılmazdı. Bu doğrultuda 2012 yılında GraphQL’i geliştirmeye başladı. 2015 yılında ise piyasaya sundu.

GraphQL, yukarıda bahsedilen sorunları ortadan kaldırmak için tasarlanmış bir API sorgulama dilidir. İstemcilerin ihtiyaç duydukları verileri doğrudan sorgulamalarına olanak sağlar. Bununla beraber GraphQL, verileri daha iyi organize edebilir, daha tutarlı bir API oluşturmayı sağlar ve daha iyi bir dökümantasyon sunar. Böylece, API’ların yönetimi daha kolay hale gelir.

GraphQL sadece istenilen verileri GraphQL sorgusunda yazabildiğimiz için RESTful servislerde karşılaşabildiğimiz Overfetching (Gereksiz Veri Getirme) sorununu çözer.

GraphQL tek bir endpointe sahip olduğu için yine tek bir GraphQL sorgusunda farklı kaynakları belirtip RESTful servislerde yaşanabilen Underfetching (Yetersiz Veri Getirme) sorununada çözüm getirmektedir.

Aynı zamanda yeni bir değişiklik olduğunda da bu değişiklik hiçbir tarafı etkilemeden gerçekleşebilmektedir. Mesela yeni bir alana daha ihtiyaç duyduğumuzda tek yapmamız gereken ilgili alanı sorguya dahil etmek olacaktır. Bunun dışında bu değişiklikten etkilenecek başka bir yer yoktur.

Şimdi bir örnekle bir GET işleminin RESTful servislerle nasıl yapılabildiğini ve bu istekleri atarken karşılaştığımız problemleri görelim. Ardında aynı örneği GraphQL ile gerçekleştirip REST’deki sorunları GraphQL ile nasıl çözdüğümüze bakıp GraphQL’in artılarını gözlemleyelim.

Varsayalım ki bir uygulama geliştiriyoruz ve kullanıcının profil sayfasını göstermek istiyoruz. Kullanıcının profil bilgileri, profil resmi ve gönderilerine ihtiyacımız var. Bu durumda, üç farklı API çağrısı yapabiliriz:

  1. Kullanıcının temel bilgilerini dönen verileri almak için: GET /users/{id}
{
  "id": 123,
  "name": "Can Cuma",
  "surname": "Yaman",
  "birth_date": "1998-07-16"
  "email": "cancumayamann@gmail.com",
  "age": 24
}

2. Kullanıcının profil resmini almak için: GET /users/{id}/image

3. Kullanıcının tüm gönderilerini almak için: GET /users/{id}/posts

GET /users/{id} isteği ile kullanıcının temel bilgilerini elde etmiş olduk. Burada gelen bilgiler arasından ad, soyad ve doğum tarihi gibi temel bilgileri kullanacağımızı düşünelim fakat bizim servisimiz kullanacağımız veriler dışında kullanıcının yaşı ve mail adresi bilgilerinide dönmektedir. Bu durumda kullanmamış olmama rağmen gereksiz verilerde servisten gelmiş oldu. İşte bu durumda da overfetching sorunu ile karşılaşıyoruz.

Bu yaklaşım ile underfetching sorununa da sebep olmaktadır. Çünkü uygulama, ihtiyacı olan tüm verileri almak için üç farklı API çağrısı yapmak zorundadır. Bu da gereksiz ağ trafiği ve performans sorunlarına yol açabilir. Burada id değerine sahip olduğum için 3 endpointte birbirinden bağımsız olarak çalışabilir haldedir fakat kullanıcının id değerini de bilmediğim bir durumda ilgili endpointler çalışmadan önce kullanıcının id değerini getirecek farklı bir endpointe veya kaynağa bağımlı olacaktı. Bu durumda underfetching sorununa başka bir örnek olarak gösterilebilir.

Şimdi bu sorunları GraphQL yardımı ile nasıl çözeceğimizi inceleyelim.

Örneğimize devam edelim ve kullanıcının profil sayfasını göstermek için ihtiyacımız olan verileri sorgulayalım. Bu sefer, tek bir GraphQL sorgusuyla tüm verileri alacağız.

GraphQL’de, sorgulamak istediğimiz alanları uygun formatta belirtmek yeterli olacaktır. Örneğin, kullanıcının profil bilgileri, profil resmi ve gönderilerini almak için şu sorguyu yazabiliriz:

query getUserProfile($id: ID!) {
  user(id: $id) {
    id
    name
    surname
    image    
    posts {
      id
      title
      body
      created_at
    }
  }
}

Gördüğünüz gibi sadece kullandığım alanları istedim. Image ve postlar için ekstra bir sorgu yada endpoint kullanmadım. Tek bir sorguda tüm ihtiyacımı belirttim.

{
"data": {
"user": {
"id": "123",
"name": "Can Cuma",
"surname": "Yaman",
"birth_date": "1998-07-16",
"image": "https://example.com/avatar.jpg",
"posts": [
{
"id": "1",
"title": "My first post",
"body": "Lorem ipsum dolor sit amet...",
"created_at": "2023-05-01T12:34:56Z"
},
{
"id": "2",
"title": "My second post",
"body": "Lorem ipsum dolor sit amet...",
"created_at": "2023-04-30T12:34:56Z"
}
]
}
}
}

İlgili GraphQL sorgusunun çıktısı da yukarıdaki gibidir. Böylece sadece istediğim alanları elde ederek RESTful servislerde yaşanan overfetching sorununun önüne geçmiş olduk.

Bununla beraber tek bir endpoint yardımıyla tek sorgu içerisinde farklı kaynaklara taleplerde bulunup, pek çok endpoint kullanmayarak underfetching sorununa da GraphQL ile çözüm getirmiş olduk. Burada yukarıda yazdığım GraphQL sorgusunda ve daha nice GraphQL işlemlerinde istek attığım endpoint tektir. Bütün istekler api.example.com/graphql gibi bir adrese gitmektedir. Ben sadece GraphQL ile ilgili yazacağım sorguyu veya diğer işlemler için gerekli olan yapıyı değiştiriyorum endpoint sabit kalıyor.

İleride benden kullanıcının email bilgisininde gösterilmesi istenirse tek yapmam gereken ilgili GraphQL sorgusuna email yazmak olacaktır. Bu şekilde yaptığım değişiklikten uygulamam minimum düzeyde etkilenecektir.

İşte RESTful servislerde yaptığımız işlemleri GraphQL ile çok daha kolay bir şekilde gerçekleştirmekteyiz. İşlemleri kolayca yapmanın yanı sıra RESTful servislerde yaşadığımız sorunların üstesinden de GraphQL ile gelmekteyiz.

Şimdi de GraphQL’i biraz daha derinlemesine inceleyelim.

GraphQL kullanmaya başladığımızda bazı temel kavramlar karşımıza çıkmaktadır. Şimdi bu temel kavramların ne olduğuna göz atalım.

  • Type : Client olarak GraphQL’e göndereceğimiz verinin ve dönüşünde elde edeceğimiz verinin tipini bilmek isteriz. Bu sayede hangi tipte veriye ulaşacağımızı tespit eder kendi tarafımızda yapacağımız işlemleri bu veri tipine göre gerçekleştirebiliriz. İşte burada ki bahsedilen verinin yapısını ifade eden kavram GraphQL de Type olarak geçmektedir. Aşağıda örnek bir Type tanımlaması gösterilmiştir.
type User {
id: ID
name: String
email: String
age: Int
}
  • Field: GraphQL de Field, Type’ın tuttuğu her bir veriyi temsil eder. Yukarıdaki örnekten devam edecek olursak id, name, email, age alanları birer Field’dır.
  • Query : Veri sorgulamak için kullanılan operasyon türüdür. Bu kavram RESTful servislerde kullandığımız GET işlemine karşılık geliyor diyebiliriz. Yalnızca veri getirir, veri değiştiremez. Aşağıda id si 123 olan user bilgisini getirmek için kullanılan query gösterilmiştir.
query {
user(id: "123") {
name
email
}
}
  • Mutation: GraphQL de veri üzerinde değişiklik yapmak istediğimizde Mutation kullanırız. RESTful servislerdeki POST, PUT, PATCH, DELETE isteklerine karşılık gelir. Birincil amacı veri üzerinde değişiklik yapmaktır.
mutation {
createUser(input: { name: "Can Cuma Yaman", email: "cancumayamann@gmail.com",
age: 24 }) {
id
name
email
age
}
}
  • Variable: GraphQL’de “variable” (değişken), query’lere, mutation’lara dinamik değerler sağlamak için kullanılan bir yapıdır. Değişkenler, sorgu metnine eklenir ve ardından sorgunun çalıştırılması sırasında gerçek değerlerle değiştirilir. Bu, istemcinin aynı sorguyu farklı değerlerle tekrar kullanabilmesini sağlar. Aşağıdaki sorguda, “GetUser” adında bir sorgu tanımlanmıştır ve $userId adında bir değişken kullanılmıştır. Bu değişken, “ID!” türünde olup zorunlu bir değeri temsil eder.
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
email
}
}
  • Sorgu çalıştırıldığında, değişken değeri sağlanmalıdır. Bu, sorgunun çalıştırılacağı yerde değişkenlere değer atanarak yapılır. Örneğin, GraphQL sorgusunu çalıştıran bir istemci, $userId değişkenine bir değer sağlayarak sorguyu çalıştırabilir:
{
"userId": "123"
}
  • Fragment: Fragmentler, GraphQL sorgularında sık sık kullanılan alanların gruplandırılması ve yeniden kullanılması için kullanılan bir yapıdır. Aynı alanları farklı sorgularda tekrar tekrar belirtmek yerine, bir fragment oluşturarak bu alanları bir araya getirebiliriz. Bu, sorguların daha temiz ve daha okunabilir olmasını sağlar. Aşağıdaki örnekte, “UserInfo” adında bir fragment tanımlanmıştır. Bu fragment, “User” türündeki “id”, “name” ve “email” alanlarını içerir. Daha sonra, “GetUser” ve “GetUsers” sorgularında bu fragment kullanılmıştır. Bu sayede aynı alanları tekrar tekrar belirtmek zorunda kalmadan sorguları temiz bir şekilde oluşturabiliriz.
fragment UserInfo on User {
id
name
email
}
query GetUser($userId: ID!) {
user(id: $userId) {
...UserInfo
age
}
}
query GetUsers {
users {
...UserInfo
isActive
}
}
  • Schema: GraphQL API’ın yapısal tanımını içeren bir belgedir. Schema, type’ları, field’ları, query’leri, mutation’ları vb. tanımlayan bir belgedir. Schema, API’ların nasıl kullanılması gerektiğini ve hangi verilerin istenebileceğini belirler. Örnek bir schema aşağıdaki gibidir.
type User {
id: ID
name: String
email: String
age: Int
}

type Query {
user(id: ID): User
}

type Mutation {
createUser(input: CreateUserInput): User
updateUser(id: ID, input: UpdateUserInput): User
}

input CreateUserInput {
name: String
email: String
age: Int
}

input UpdateUserInput {
name: String
email: String
age: Int
}
  • Resolver: GraphQL query ve mutation’ların nasıl işleneceğini belirleyen işlevlerdir. Her GraphQL alanı, bir resolver’a sahip olmalıdır. Resolver, belirli bir alanın değerini almak için gerekli iş mantığını içeren işlevdir. Veri kaynaklarına (veritabanı, API, vb.) erişebilir ve ilgili veriyi alarak döndürebilir. Yani GraphQL sorgumun arka planda veriyi alıp nereden getireceğinin belirlendiği ve ilgili verinin gönderildiği kısımdır. Örnek resolver yapısı aşağıda gösterilmiştir.
const resolvers = {
Query: {
user: (parent, args) => {
// Kullanıcının id'sini kullanarak ilgili veriyi al ve döndür
const userId = args.id;
// Veritabanına veya başka bir veri kaynağına erişim sağla
const userData = getUserDataFromDatabase(userId);
return userData;
}
}
};
  • Subscription: GraphQL’de subscription, gerçek zamanlı olayları takip etmek ve istemcilere anlık güncellemeler sağlamak için kullanılan bir mekanizmadır. Subscription’lar, istemci tarafından yapılan bir istekle başlatılır ve sunucu tarafında gerçek zamanlı verilerin yayınlanmasıyla devam eder. Her alan, istemcinin güncellemeleri almak istediği bir olayı temsil eder. Örneğin, bir sohbet uygulamasında, bir kullanıcı yeni bir mesaj gönderdiğinde subscription kullanılarak diğer kullanıcılara anlık olarak mesajın iletilmesi sağlanabilir. Aşağıdaki örnekte, “newMessage” adında bir abonelik tanımlanmıştır. Bu abonelik, yeni bir mesaj yayınlandığında çalışır. Abonelikle alınacak veriler, “Message” türünde olan “id” ve “content” alanlarıdır. İstemci, abonelik yaparak yeni mesajları anlık olarak alabilir.
type Subscription {
newMessage: Message
}

type Message {
id: ID!
content: String!
}

subscription {
newMessage {
id
content
}
}
  • GraphQL Server: GraphQL sorgularını, mutasyonlarını veya aboneliklerini alıp işleyen ve bu isteklere yanıtlar döndüren bir hizmettir. GraphQL sunucusu, gelen isteklere dayanarak veri kaynaklarına erişim sağlar, verileri alır ve istemcilere geri döner. GraphQL sunucusunun temel görevi, GraphQL şemasını tanımlamak, istemcilerden gelen istekleri karşılamak ve bu isteklere uygun yanıtları oluşturmaktır. Sunucu, şemada tanımlanan tipleri, alanları, sorguları, mutasyonları ve abonelikleri uygular. Ayrıca, gelen isteklere göre veri kaynaklarıyla (veritabanı, API’lar, vb.) etkileşime geçer ve istemcilere istedikleri verileri sağlar. Aşağıda GraphQL kullanımında GraphQL Server’ın bulunduğu yer gösterilmiştir.
GraphQL Server

Uygulamaların geliştiriliceği platforma göre GraphQL Server değişkenlik göstermektedir. Örneğin Node.js uygulaması geliştiriyorsak Apollo Server kullanabiliriz, .NET uygulaması içinde Hot Chocolate ve GraphQL.NET en popüler GraphQL Server’larına örnek olarak verilebilir. Aşağıda Apollo Server kullanılarak basit bir GraphQL sunucusu oluşturulmuştur.

const { ApolloServer, gql } = require('apollo-server');

// Schema Tanımı
const typeDefs = gql`
type Query {
hello: String
}
`;

// Resolvers
const resolvers = {
Query: {
hello: () => 'Merhaba, GraphQL!'
}
};

// Sunucu oluşturma
const server = new ApolloServer({ typeDefs, resolvers });

// Sunucuyu çalıştırma
server.listen().then(({ url }) => {
console.log(`GraphQL sunucusu çalışıyor: ${url}`);
});

Schema (typeDefs) ve Resolvers (resolvers) tanımlanmıştır. Schema da tek bir “hello” sorgusu tanımlanmış ve bu sorgunun resolver’ı olarak bir ‘Merhaba, GraphQL!’ metin yanıtı döndürülmüştür.

Sunucu, ApolloServer sınıfıyla oluşturulur ve schema tanımı ile resolver’ı alır. Daha sonra, listen yöntemiyle sunucu belirtilen bir URL üzerinde çalıştırılır.

Bu örnekte, GraphQL sunucusu, “/graphql” yolunda istekleri dinler ve “hello” sorgusu için “Merhaba, GraphQL!” yanıtını döner. İstemciler, sunucunun sağladığı schema’ya uygun olarak sorgularını yapabilir ve sunucudan yanıtları alabilir.

GraphQL kullanımında karşımıza çıkan temel kavramlar bu şekildedir.

Son olarak GraphQL’in görece dezavantajlarından bahsedip yazıyı noktalamak istiyorum.

GraphQL Dezavantajları;

  • GraphQL’in RESTful servislere kıyasla, öğrenme eğrisi daha dik olabilir. RESTful servislerde yapacağımız bir işi GraphQL’de yapmaya çalıştığımızda çok daha fazla konuyu öğrenmemiz gerekebilir. Yazdığımız query’ler ihtiyaca göre çok fazla uzun ve iç içe olup karmaşıklık iyice artabilir. Tabi GraphQL tarafında bunu belirli bir sayı ile sınırlandırıp bu sorunun önüne geçmekte mümkündür.
  • Caching bir diğer dezavantajlarından biri olarak gösterilebilir. RESTful servislerde caching çok daha kolay olmakla beraber caching için olanaklarda fazladır. Http nin sağladığı imkanlar buna örnek olarak verilebilir. GraphQL de caching yapmak istediğimizde ekstra olarak redis gibi kütüphanelere ihtiyaç vardır. Kendi caching mekanizmamızı yazmamız gerekmektedir. Ayrıca istemci her zaman istediği verileri çekmek isteyeceği için her zaman belirli veriler istemciye ulaşmamış olacaktır bu durum caching işlemini zorlaştıracaktır. Kullanıcının isteyeceği verinin sabit olmaması Rate Limiting konusunda da bize zorluk çıkarabilir.
  • GraphQL sorgusunda bir hata ile karşılaştığımızda GraphQL geriye errors ismi ile json olarak hataları dönmektedir fakat hatanın olup olmamasına bakmaksızın geriye 200 OK durum kodu dönmektedir. Bu yüzden hata olup olmadığını dönen durum kodundan tespit edememekteyiz. Hatayı anlamak için GraphQL den dönen json veriyi parse etmek gerekir.

GraphQL yukarıdaki dezavantajlara sahip olmasına rağmen bu dezavantajların üstesinden gelmekte mümkündür. Günün sonunda GraphQL’in avantajları, dezavantajlarına göre çok daha ağır basmaktadır. Sonuç olarak GraphQL kullanarak yazılım geliştirme süreçlerini hızlandırıp performansı arttırıyoruz. Facebook, github, twitter, airbnb gibi pek çok büyük şirketler GraphQL den faydalanmaktadırlar.

Bu yazıda RESTful servislerde işleyişin nasıl olduğundan, RESTful servislerle karşılaşılan problemlerden bahsettik. Daha sonra GraphQL’in ne olduğundan bahsedip RESTful servislerde karşılaşılan sorunlara nasıl çözüm yolu getirdiğini inceledik. Son olarak GraphQL’in temel bileşenlerinden bahsettik.

Aşağıdaki link ile .NET de Hot Chocolate server kullanılarak yapılmış örnek bir projenin kaynak koduna ulaşabilirsiniz.

https://github.com/cancumayaman/GraphQLDemo

Bir sonraki yazılarda görüşmek üzere.

İyi çalışmalar.

Yorum bırakın