GraphQL Kullanım Alanları ve Örnek Bir Uygulama
Bu yazıda GraphQL’in ne olduğundan ve hangi amaç doğrultusunda kullanılabileceğinden bir örnek üzerinden bahsetmeye çalışacağım.
GraphQL Nedir?
GraphQL kendisini, “API’leriniz için bir sorgulama dili” olarak tanımlamaktadır. GraphQL herhangi bir database veya depolama motoruna bağımlı değildir. Bunun yerine mevcut kodunuz ve verilerinizden beslenmektedir.
GrahpQL herhangi bir dile bağımlı olmaksızın geliştirlebilmektedir. Javascript, PHP, GO, Java gibi bir çok programlama dili ile GraphQL sunucusu oluşturulabilir.
GraphQL genellikle tek bir endpoint ile sunucularımızdan veri almamıza olanak sağlamaktadır. Ayrıca bir playground üzerinden tanımlanmış olan şemayı görselleştirerek bu playground üzerinden sorgulamalar yapabilmemize de imkan vermektedir. Yine bu playground üzerinden şema tanımlaması yaparken eklediğimiz açıklamalar sayesinde dökümantasyon işini de çok az efor ile GraphQL servisini kullanacak kişilere sağlayabilmekteyiz.
GraphQL’in Kullanım Amacı
Uygulamalarımızın clientlar ile iletişimini sağlamak için belli başlı servisler yazmaktayız. Yazdığımız servisler tek bir client uygulaması tarafından kullanılabileceği gibi birden fazla client uygulaması ile de kullanılabilmektedir. Örneğin oluşturduğumuz bir servisi web, mobile ve desktop gibi bir çok farklı platformdan çağırabilmekteyiz.
Servislerimiz çoğu zaman her client’ın ihtiyaç duyduğu veriyi tam olarak dönemeyebilir. Client tarafında uygulama geliştiren kişiler/ekipler servislerden gelen verileri handle edebilmek adına bir çok logic işletebilmektedir.
Örneğin müşteri bilgilerini dönen bir serviste müşterinin bakiyesinin ve döviz bilgisinin ayrı alanlarda geldiğini düşünelim. Client tarafında bazı durumlarda sadece bakiyeyi, bazı durumlarda müşterinin döviz bilgisini bazen de her ikisini birlikte formatlanmış bir şekilde kullanmamız gerektiği durumlar söz konusu olabilir. Bu durumda client tarafında bahsettiğimiz bu koşulları sağlayacak logic’ler işletmemiz gerekecektir. Birbirinden bağımsız birden fazla client uygulamamız olduğunu düşündüğümüzde, bu logic’lerin her uygulama için tekrar edilmesi gerekecektir. İşte bu gibi durumlarda GraphQL güzel bir çözüm olacaktır. Client, direkt olarak servis ile iletişim kurmak yerine GraphQL sunucusuna ihtiyacı olan alanları iletecek ve GraphQL sunucusu da ilgili servisten veriyi alıp logic’i işleterek client’ın istediği şekilde geri dönecektir.
Bunun yanında client uygulamalarımız — her ekran için, ilgili ekranın ihtiyaçlarını karşılayacak yeni bir endpoint oluşturmanın pek mümkün olmamasından dolayı — çoğu zaman birden fazla endpoint’e istekte bulunmaktadır. Bu noktada da yine GraphQL güzel bir çözüm olmaktadır. Mesala GQL sunucumuza, bize müşteri bilgilerini ve müşterinin hesap hareketlerini getir dediğimizde, tek bir endpoint üzerinden verileri toplayıp bize dönebilmektedir. Bu durumda müşterilerimiz ve hesap bilgileri aynı serviste farklı endpointlerde tutulabileceği gibi, farklı servislerde de tutuluyor olabilir.
Yada müşteri hareketlerini ayrı bir servisten veya farklı bir veritabanından alıyor olabiliriz. Fakat client tarafında bu bilgilerin müşteriye bağlı olarak (ilişkili olarak) dönmesini istiyor olabiliriz. Her iki durumda da GraphQL tarafında bu verilere ulaşıp client’a ilgili veriyi istediğimiz gibi dönebiliriz.
Projelerimiz büyüdükçe ve servislerimizden farklı uygulamalar beslenmeye başladıkça, servislerimizin döndüğü veri modeli, clientların ihtiyaçlarını karşılayamamakta, ya gereksiz veri dönmekte yada yeterli veriye sahip olmadığı için client tarafında farklı servislere istekteklerde bulunma ihtiyacı doğmaktadır. Bunun neticesinde de client tarafında yapacağımız veri manipülasyonları artmakta ve client uygulamalarımız yönetimi zor, complex bir hale gelmektedir.
İşte bu gibi durumlarda GraphQL, veri modelini optimize etmek amacıyla bir çözüm olarak tercih edilebileceği gibi, veritabanı ile direkt iletişim halinde olacak RESTFull API’larına alternatif olarak da kullanılabilir.
GraphQL’in kullanım alanına örnek olarak Oğuz Kılıç’ın BFF ile Ön Yüz Uygulamalarınız için Veri Modelinizi Optimize Edin başlıklı yazısını okuyabilirsiniz.
Konsept
GraphQL en temelde veri modelimizi tutan bir şemaya, verileri alacağı bir veri setine ve şemada tanımladığımız tiplere karşılık gelen verileri ilgili data setinden dönecek bir resolver’a ihtiyaç duyar.
Schema
Her GraphQL sunucusu, client’ın sorgulayabileceği veri yapısını tanımlamak için bir şema kullanır. Veri sorgulama işlemi için Query, veri ekleme, silme veya güncelleme gibi işlemler için ise Mutation kullanılmaktadır.
Yukarıda Query barındıran basit bir şema bulunmaktadır. Şema tanımlama detayları, Query, Mutation ve daha fazlası için https://graphql.org/learn/schema/ adresinden detaylı bilgi edinebilirsiniz.
Data Set
Data set, ihtiyacımız olan verileri alabileceğimiz herhangi bir kaynak olabilir (Database, API, static bir dosya). Örnek olarak şemamıza uygun static bir obje tanımlayabiliriz.
Resolver
Resolverlar, GraphQL Server’a şemamızda tanımladığımız, belirli bir type ile ilişkili verileri nasıl getireceğini söyler. Örneğin tanımlamış olduğumuz query’nin resolver’ı aşağıdaki şekilde olacaktır.
# schema.graphql...type Query {
customers: [Customer]
}
Konunun daha iyi anlaşılabilmesi adına basit bir örnek üzerinden ilerleyelim.
Bu örnek yalnızca sorgulama işlemi barındırmaktadır. GraphQL ile sunucu üzerinden veri çekmek için Query, insert/update/delete gibi işlemler için ise Mutation kullanılmaktadır. Detaylı bilgi için graphql.org adresini ziyaret edebilirsiniz.
Apollo Server kullanarak giriş bölümünde verdiğimiz müşteri-müşteri hareketleri ve bakiye örneği üzerinden basit bir ugulama geliştirelim. Express ile 4001 ve 4002 portlarında çalışan birisi müşteri bilgilerini, diğeri müşteri hareketlerini dönecek iki farklı servisimiz olsun. 4000 portunda da GraphQL sunucumuzu çalıştıralım.
Bu örnekte verileri nerede tutacağımızın pek bir önemi yok. İster farklı veritabanlarında, ister farklı servislerde istersek bir kısmını veri tabanında bir kısmını bir serviste tutalım, her koşulda GraphQL üzerinden bu verileri toplayıp, client’ın istediği şekilde geri döndürebiliriz.
GraphQL için Apollo Server kullanılmıştır.
Detaylı bilgi için https://www.apollographql.com/docs/apollo-server adresini ziyaret edebilirsiniz.
Müşterileri dönen servis:
Müşteri hareketlerini dönen servis:
GraphQL şemamız:
Yukarıda Customer ve CustomerTransaction isimli iki tip oluşturduk. Her müşterinin hareketlerinin ise customerTransactions altında bir array olarak döneceğini belirttik. Ayrıca müşterinin bakiyesini formatlanmış olarak alabileceğimiz balanceText isimli bir field ekledik. Son olarak müşterilerin listesini dönecek customers isimli bir Query tanımladık ve dönüş tipi olarak Customer array döneceğini belirttik.
Bazı field’ların sonunda bulunan ünlem (!) işareti bu alanın zorunlu olarak gelmesi gerektiğini belirtmektedir. Detaylı bilgi için graphql.org adresi ziyaret edilebilir.
Resolverlarmız:
Yukarıda bulunan resolver dosyasında şemamızda belirttiğimiz customers sorgusunun karşılığını görebilirsiniz. Şemada tanımladığımız her bir query için bir resolver tanımlamak zorundayız.
Query: {
customers: async () =>
await fetch("http://localhost:4001/customers", {
method: "get",
}).then((res) => res.json()),
},
Şemamızda müşterilerin hareketlerini customer type’a bağlı olarak customerTransactions altında getireceğimizi belirtmiştik.
type Customer {
...
customerTransactions: [CustomerTransaction]
}
Her müşterinin hareketlerini bu şekilde client’a dönebilmemiz için Customer adında ayrı bir nesne oluşturup içerisine customerTransactions adında bir resolver oluşturmamız gerekiyor.
Customer: {
customerTransactions: async (customer) =>
await fetch(`http://localhost:4002/transactions/${customer.id}`, {
method: "get",
}).then((res) => res.json()), ...
},
Tiplere bağlı olan resolverlar parametre olarak root objeyi yani bizim örneğimizde customer’ı almaktadır.
Customer: {
customerTransactions: async (customer /*root object*/) => ...,
},
Bu sayede her müşteri için müşteri hareketlerini dönen transactions servisine istekte bulunabilmekteyiz. Özetle yazdığımız type’ların her bir field’ı için bir resolver tanımlayabilmekteyiz. (Nedense bu yapıyı ilk gördüğümde çok hoşuma gitmişti 🙂)
Şemamızda ayrıca balanceText adında bir field ve buna bağlı olarak resolver tarafında balanceText için bir resolver tanımladık.
// schema
type Customer {
...
balanceText: String!
...
}// resolvers
const resolvers = {
Query: { ... },
Customer: {
...
balanceText: (customer/* root object */) => {
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: customer.currency,
});
return formatter.format(customer.balance);
},
},
};
Bu resolverın içerisinde herhangi bir servise çıkmaksızın root objemizin (customer) bakiyesini (balance) formatladık. Bu sayede balanceText’i içeren bir query gönderdiğimizde GQL sunucumuz bize formatlı bir şekilde bakiyeyi dönmüş olacak. Bu işlemi customer servisinden gelen verileri şemamıza uygun hale gertireceğimiz bir mapper içerisinde de yapabiliriz.
Günün sonunda sunucumuza aşağıdaki gibi bir GQL sorgusu gönderdiğimizde hem localhost:4001/customers hemde localhost:4002/transactions/:customerId servislerine çıkıp verileri alıp istediğimiz formatta ulaşmış olacağız.
Bu noktada customer servisinden birden fazla kayıt geldiği durumda, her müşterinin hareketlerini almak için transaction servisine gelen kayıt sayısı kadar request göndermiş olacağız. Aynı şekilde hareketleri bir veri tabanında tutuyor olsaydık birden fazla veritabanı sorgusu çalıştırmış olacaktık. Bu bilinen bir problem. Bu soruna N + 1 problemi deniyor.
N + 1 problemi dataloader gibi bir kütüphane ile çözülebilmektedir. Daha detaylı bilgi için makalenin sonunda bununla ilgili linkler bulabilirsiniz. Konuyu dağıtmamak adına sadece bu soruna değinmekle yetiniyorum.
Örnek uygulamaya aşağıdaki repodan ulaşabilirsiniz.
Özet
- GraphQL’in ne olduğunu hangi problemlere nasıl çözümler getirdiğini öğrendik.
- GraphQL’in temelde nasıl bir konsept’e sahip olduğunu öğrendik.
- Basit bir örnek üzerinden şema, resolver ve data set’in ne olduğunu gördük.
- N + 1 problemine değindik.
Kaynaklar
- https://www.apollographql.com/docs/apollo-server/
- https://graphql.org/
- https://rahmanfadhil.com/graphql-dataloader/(N + 1 problemi)
- https://medium.com/zillow-tech-hub/graphql-one-endpoint-to-rule-them-all-4daaec273e5
Bulunduğum Platformlar
https://medium.com/@ozanturhan adresinden daha önce yazmış olduğum yazıları inceleyebilirsiniz.