GraphQL SDL: Yapı Taşları ve Kapsamlı Bir Referans
GraphQL, modern API geliştirme dünyasında veri sorgulama ve manipülasyonu için güçlü ve esnek bir yaklaşım sunar. Geleneksel RESTful API’lerin aksine, GraphQL istemcilerin tam olarak ihtiyaç duydukları veriyi tek bir istekte almalarına olanak tanır, bu da aşırı veya eksik veri getirme sorunlarını ortadan kaldırır. Ancak GraphQL’in bu gücü, temelinde yatan iyi tanımlanmış bir sözleşmeye, yani Şema Tanımlama Dili’ne (Schema Definition Language – SDL) dayanır. SDL, GraphQL API’nizin yapısını, mevcut veri tiplerini, sorgulanabilir alanları ve veri manipülasyonu (mutasyonlar) için kullanılacak yöntemleri açıkça belirten dilden bağımsız bir formattır.
Bu makale, GraphQL SDL’in derinlemesine bir referansını sunmayı amaçlamaktadır. SDL’in temel yapı taşlarından başlayarak, farklı tip tanımlamalarına, alanlara, argümanlara, yönergelere ve gelişmiş kavramlara kadar her bir bileşeni ayrıntılı bir şekilde inceleyeceğiz. SDL’in nasıl çalıştığını, API’nizin nasıl şekillendiğini ve bu dilin GraphQL ekosistemindeki merkezi rolünü anlamak, hem API sağlayıcıları hem de tüketicileri için kritik öneme sahiptir. İster yeni bir GraphQL API tasarlıyor olun, ister mevcut bir API’yi anlamaya çalışın, bu kapsamlı rehber SDL’in inceliklerini kavramanıza yardımcı olacaktır.
GraphQL SDL’in Temel Kavramları
GraphQL SDL, bir GraphQL hizmetinin sunduğu tüm veri ve işlemlerin formal bir tanımını sağlar. Bu tanım, istemcinin API ile nasıl etkileşim kurabileceğini belirleyen bir sözleşme görevi görür. SDL’in temelinde, bir API’nin hangi sorguları kabul edebileceğini, hangi verileri döndürebileceğini ve hangi değişiklikleri yapabileceğini açıklayan bir dizi tip tanımı bulunur.
Şema (Schema)
Bir GraphQL SDL belgesinin kökünde schema tanımı bulunur. Bu tanım, API’nizin ana giriş noktalarını belirtir: sorgular (query), mutasyonlar (mutation) ve abonelikler (subscription). Her bir giriş noktası, belirli bir nesne tipine işaret eder ve bu nesne tipleri aracılığıyla API’nin sunduğu tüm işlemlere erişilir.
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
Bu örnekte, Query, Mutation ve Subscription adında üç farklı nesne tipi tanımlanmış ve bunlar API’nin ana giriş noktaları olarak belirlenmiştir. Query tipi, veri okuma işlemleri için, Mutation tipi veri yazma veya değiştirme işlemleri için, Subscription tipi ise gerçek zamanlı veri akışları için kullanılır. Bir şema tanımında tüm bu üç giriş noktasının bulunması zorunlu değildir; örneğin, sadece veri okuma yapan bir API yalnızca query giriş noktasına sahip olabilir.
Tipler (Types)
Tipler, GraphQL SDL’in temel yapı taşlarıdır ve API’nizin döndürebileceği veya kabul edebileceği tüm veri yapılarını tanımlar. GraphQL, farklı ihtiyaçlar için çeşitli tip kategorileri sunar.
Nesne Tipleri (Object Types)
Nesne tipleri, GraphQL şemasının en yaygın ve temel bileşenleridir. Bunlar, bir sistemdeki belirli bir varlığı (örneğin, bir kullanıcı, bir ürün, bir gönderi) temsil eder ve bu varlığın sahip olduğu alanları tanımlar. Her alanın bir adı ve bir tipi vardır.
type User {
id: ID!
name: String!
email: String
posts: [Post!]!
createdAt: DateTime!
}
Yukarıdaki User nesne tipi örneğinde:
* id: ID! tipinde bir alan. ID GraphQL’in yerleşik bir skaler tipidir ve benzersiz tanımlayıcıları temsil eder. Sondaki ! işareti, bu alanın boş (null) olamayacağını, yani her zaman bir değer içermesi gerektiğini belirtir.
* name: String! tipinde bir alan, yine boş olamaz.
* email: String tipinde bir alan. ! işareti olmadığı için bu alanın boş olabileceği anlamına gelir.
* posts: [Post!]! tipinde bir alan. Bu, Post nesne tiplerinden oluşan bir liste olduğunu gösterir. İçteki ! işareti, listenin elemanlarının boş olamayacağını (yani her postun bir değeri olmalı) belirtirken, dıştaki ! işareti, listenin kendisinin boş olamayacağını (yani her zaman bir liste dönecek, boş bir liste bile olsa) gösterir.
* createdAt: DateTime! tipinde bir alan. DateTime burada özel (custom) bir skaler tip olabilir.
Nesne tipleri arasındaki ilişkiler de alanlar aracılığıyla tanımlanır. Örneğin, bir User tipinin posts alanı, Post tipleriyle olan bire-çok ilişkisini gösterir.
Skaler Tipler (Scalar Types)
Skaler tipler, bir GraphQL şemasındaki en küçük, atomik veri birimleridir. Bunlar, daha fazla alt alana sahip olmayan ve tek bir değere sahip olan tiplerdir. GraphQL, beş yerleşik skaler tip sunar:
* String: UTF-8 karakter dizisi.
* Int: 32 bit imzalı tamsayı.
* Float: Çift hassasiyetli kayan nokta değeri.
* Boolean: true veya false değeri.
* ID: Benzersiz bir tanımlayıcıyı temsil eden bir dize. Genellikle bir nesnenin birincil anahtarı olarak kullanılır ve serileştirme sırasında String olarak işlenir.
Yerleşik skaler tiplerin yanı sıra, kendi özel skaler tiplerinizi de tanımlayabilirsiniz. Örneğin, tarih/saat bilgileri veya JSON nesneleri gibi karmaşık veri yapılarını temsil etmek için özel skaler tipler kullanılabilir.
scalar DateTime
scalar JSON
Özel skaler tiplerin tanımı SDL’de basittir, ancak bu tiplerin çalışma zamanında nasıl serileştirilip serileştirileceği (istemci ve sunucu arasında nasıl dönüştürüleceği) sunucu tarafı kodunda implemente edilmelidir.
Enum Tipleri (Enum Types)
Enum tipleri, bir alanın alabileceği değerleri belirli bir sabit setle kısıtlamak için kullanılır. Bu, tip güvenliğini artırır ve olası değerleri açıkça belirtir.
enum Role {
ADMIN
EDITOR
VIEWER
}
type User {
id: ID!
name: String!
role: Role!
}
Bu örnekte, Role enum tipi, bir kullanıcının rolünün yalnızca ADMIN, EDITOR veya VIEWER olabileceğini belirtir. Bu, hem istemcinin hem de sunucunun geçerli değerler kümesini bilmesini sağlar.
Girdi Nesne Tipleri (Input Object Types)
Girdi nesne tipleri, özellikle mutasyonlarda argüman olarak karmaşık veri yapılarını iletmek için kullanılır. Yapısal olarak nesne tiplerine benzerler ancak input anahtar kelimesiyle tanımlanırlar ve kendi içlerinde diğer nesne tiplerini doğrudan alan olarak barındıramazlar (skalerler, enumlar, diğer input tipleri veya listeleri olabilir).
input CreateUserInput {
name: String!
email: String
role: Role = VIEWER # Varsayılan değer atanabilir
}
type Mutation {
createUser(input: CreateUserInput!): User!
}
Yukarıdaki örnekte, CreateUserInput bir kullanıcının oluşturulması için gerekli bilgileri içeren bir girdi nesne tipidir. createUser mutasyonu, bu tipi bir argüman olarak alır. Bu, birçok argümanı tek bir yapı altında toplamanın temiz bir yoludur.
Arayüz Tipleri (Interface Types)
Arayüz tipleri, birden fazla nesne tipinin paylaşması gereken ortak bir alan kümesini tanımlar. Bir nesne tipi bir arayüzü uyguladığında, o arayüzde tanımlanan tüm alanları içermek zorundadır. Bu, polimorfizm sağlamak için kullanılır.
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
email: String
}
type Post implements Node {
id: ID!
title: String!
content: String
}
Bu örnekte, Node arayüzü, id adında bir ID! alanına sahip olması gereken tüm tipleri tanımlar. Hem User hem de Post tipleri Node arayüzünü uyguladığı için, her ikisinin de bir id alanı olmak zorundadır. Bu sayede, Node tipinde bir değer bekleyen bir sorgu, hem User hem de Post tiplerini döndürebilir ve istemci bu ortak id alanına erişebilir.
Birleşim Tipleri (Union Types)
Birleşim tipleri, bir alanın belirli bir anda birden fazla farklı nesne tipinden herhangi birini döndürebileceğini belirtir. Arayüzlerden farklı olarak, birleşim tipleri, birleşime dahil olan tipler arasında ortak alanlar gerektirmez.
union SearchResult = User | Post | Comment
type Query {
search(text: String!): [SearchResult!]!
}
Bu örnekte, SearchResult birleşim tipi, bir arama sonucunun User, Post veya Comment tiplerinden biri olabileceğini gösterir. İstemci, bir SearchResult aldığında, __typename meta alanı aracılığıyla gerçek tipini öğrenebilir ve buna göre veri parçalarını sorgulayabilir.
Alanlar ve Argümanlar
GraphQL şemasındaki her nesne tipi, bir dizi alan tanımlar. Bu alanlar, o tipin sahip olduğu veri parçalarını veya gerçekleştirebileceği işlemleri temsil eder.
Alanlar (Fields)
Her alanın bir adı ve bir tipi vardır. Alan tipleri, yukarıda bahsedilen skalerler, enumlar, nesne tipleri, arayüzler, birleşimler veya bunların listeleri olabilir.
type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
posts: [Post!]!
}
users alanında [User!]! tipini görüyoruz. Bu, alanın User tiplerinden oluşan bir liste döndüreceğini ve hem listenin kendisinin hem de içindeki elemanların null olamayacağını gösterir.
Argümanlar (Arguments)
Alanlar, isteğe bağlı veya zorunlu argümanlar alabilir. Argümanlar, bir alanın döndüreceği veriyi filtrelemek, sıralamak veya parametreleştirmek için kullanılır. Argümanlar, adları ve tipleriyle tanımlanır ve varsayılan değerlere sahip olabilirler.
type Query {
user(id: ID!): User # id zorunlu bir argüman
users(limit: Int = 10, offset: Int = 0): [User!]! # limit ve offset varsayılan değerlere sahip
post(id: ID!, status: PostStatus = PUBLISHED): Post
}
* user(id: ID!): id argümanı ID! tipindedir ve ! işareti zorunlu olduğunu gösterir. Bir user sorgusu yaparken id argümanı mutlaka sağlanmalıdır.
* users(limit: Int = 10, offset: Int = 0): limit ve offset argümanları sırasıyla Int tipindedir ve 10 ile 0 varsayılan değerlere sahiptir. Eğer istemci bu argümanları sağlamazsa, sunucu varsayılan değerleri kullanır.
* post(id: ID!, status: PostStatus = PUBLISHED): status argümanı PostStatus enum tipindedir ve PUBLISHED varsayılan değerine sahiptir.
Argümanlar, sorguların ve mutasyonların esnekliğini önemli ölçüde artırır, istemcilerin tam olarak ihtiyaç duydukları veriyi daha spesifik bir şekilde talep etmelerine olanak tanır.
Yönergeler (Directives)
Yönergeler, GraphQL SDL’in meta-programlama yetenekleridir. Şema tanımlarına veya sorgu yürütme davranışına ek bilgiler eklemek veya bu davranışları değiştirmek için kullanılırlar. Yönergeler, @ sembolüyle başlar ve argümanlar alabilirler.
GraphQL, birkaç yerleşik yönerge sunar:
* @include(if: Boolean): Belirli bir alanın veya parçanın sorguya dahil edilip edilmeyeceğini koşullu olarak belirler. if argümanı true ise dahil edilir, false ise hariç tutulur.
* @skip(if: Boolean): @include‘ın tersidir; if argümanı true ise belirli bir alan veya parça sorgudan atlanır.
* @deprecated(reason: String = "No longer supported"): Bir alanın veya enum değerinin kullanım dışı bırakıldığını işaretlemek için kullanılır. reason argümanı, neden kullanım dışı bırakıldığını açıklayan bir mesaj sunar. Bu, araçların ve dokümantasyonun geliştiricilere bilgi vermesini sağlar.
type User {
id: ID!
name: String!
oldEmail: String @deprecated(reason: "Use the new 'email' field instead.")
email: String
}
Bu örnekte, oldEmail alanı @deprecated yönergesiyle işaretlenmiştir. Bu, istemcilerin bu alanı kullanmaktan kaçınması gerektiğini ve yeni email alanına geçmeleri gerektiğini belirtir.
Yerleşik yönergelerin yanı sıra, kendi özel yönergelerinizi de tanımlayabilirsiniz. Özel yönergeler, sunucu tarafında iş mantığını tetiklemek veya şemaya özel meta veriler eklemek için güçlü bir mekanizma sunar.
directive @auth(role: Role!) on FIELD_DEFINITION | FIELD
enum Role { ADMIN, USER }
type Query {
adminData: String @auth(role: ADMIN)
userData: String @auth(role: USER)
}
Bu örnekte, @auth adında özel bir yönerge tanımlanmıştır. Bu yönerge, role adında zorunlu bir argüman alır ve FIELD_DEFINITION (alan tanımları üzerinde) veya FIELD (sorgudaki alanlar üzerinde) konumlarında kullanılabilir. adminData alanı üzerinde role: ADMIN ile kullanılması, bu alana erişimin yalnızca ADMIN rolüne sahip kullanıcılarla sınırlı olduğunu gösteren bir kuralı temsil edebilir. Bu yönergenin gerçek implementasyonu GraphQL sunucusu tarafından sağlanmalıdır.
Açıklamalar (Descriptions)
GraphQL SDL, şema bileşenlerini belgelemek için yerleşik bir mekanizma sunar: açıklamalar. Açıklamalar, üç tırnak işareti (""") içine alınmış çok satırlı dizeler olarak veya tek tırnak işareti (") içine alınmış tek satırlı dizeler olarak tanımların hemen üzerine yerleştirilir.
"""
Sistemdeki bir kullanıcıyı temsil eder.
Benzersiz bir ID'ye, isme ve isteğe bağlı e-posta adresine sahiptir.
"""
type User {
id: ID!
"Kullanıcının tam adı."
name: String!
email: String
}
"""
Sistemdeki bir gönderinin durumunu belirtir.
"""
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
Bu açıklamalar, GraphiQL veya GraphQL Playground gibi araçlar tarafından otomatik olarak alınır ve API dokümantasyonunu zenginleştirmek için kullanılır. İyi yazılmış açıklamalar, API’nizi kullanan geliştiriciler için paha biçilmezdir, şemanın amacını ve her alanın veya tipin neyi temsil ettiğini açıkça anlamalarını sağlar.
Gelişmiş SDL Kavramları
SDL’in temel yapı taşlarının ötesinde, daha karmaşık senaryoları ele almak için bazı gelişmiş özellikler de bulunur.
Uzatmalar (Extensions)
extend anahtar kelimesi, mevcut bir tipi veya şema tanımını değiştirmeden ona yeni alanlar veya yönergeler eklemek için kullanılır. Bu, özellikle şema birleştirme (schema stitching) veya federasyon gibi senaryolarda ya da üçüncü taraf bir şemayı kendi projenizde genişletirken kullanışlıdır.
# Mevcut bir Query tipine yeni alan ekleme
extend type Query {
newField: String
anotherNewField(arg: Int): Boolean
}
Mevcut bir User tipine yeni alan ekleme
extend type User {
age: Int
}
Şema tanımına yönerge ekleme
extend schema @federation
extend kullanımı, orijinal tanımın bulunduğu yerden farklı bir dosyada veya modülde mevcut tiplere ekleme yapmanıza olanak tanır. Bu, modüler şema tasarımı için önemlidir.
Şema Yönergeleri (Schema Directives)
Yönergeler sadece tipler veya alanlar üzerinde değil, aynı zamanda schema tanımının kendisi üzerinde de kullanılabilir. Bu, tüm şema için geçerli olan meta verileri veya davranışları belirtmek için kullanılır.
schema @customSchemaDirective(version: "1.0") {
query: Query
}
directive @customSchemaDirective(version: String!) on SCHEMA
Bu örnekte, @customSchemaDirective yönergesi schema tanımına uygulanmıştır. Bu, şemanın genel sürümünü veya diğer global özelliklerini belirtmek için kullanılabilir. Apollo Federation gibi çözümler, federasyon özelliklerini belirtmek için şema yönergelerini yoğun bir şekilde kullanır.
SDL’in Pratik Kullanımı ve Araçlar
GraphQL SDL, sadece API’nizin bir tanımı olmanın ötesinde, GraphQL ekosistemindeki birçok aracın ve metodolojinin temelini oluşturur.
Schema First vs. Code First
GraphQL API geliştirme yaklaşımlarından ikisi olan “Schema First” ve “Code First” paradigmaları bulunur. SDL, “Schema First” yaklaşımının merkezindedir. Bu yaklaşımda, API’nin sözleşmesi (yani SDL şeması) ilk olarak tasarlanır ve ardından sunucu ve istemci kodları bu şemaya göre oluşturulur. Bu, API’nin tasarımına odaklanmayı ve istemci ile sunucu arasında net bir sözleşme oluşturmayı teşvik eder. “Code First” yaklaşımında ise, şema doğrudan sunucu tarafı kodundan (örneğin TypeScript veya C# sınıflarından) otomatik olarak türetilir. Her iki yaklaşımın da kendine göre avantajları olsa da, SDL’in netliği ve platformdan bağımsızlığı, Schema First yaklaşımını birçok büyük ölçekli projede tercih edilen seçenek haline getirir.
Kod Üretimi (Code Generation)
SDL’in en güçlü kullanım alanlarından biri kod üretimidir. GraphQL Code Generator gibi araçlar, bir SDL şemasını alarak TypeScript, Flow, Java, C#, Swift gibi çeşitli dillerde istemci veya sunucu tarafı kodları (tipler, sorgu/mutasyon yardımcıları, resolver arayüzleri) otomatik olarak üretebilir. Bu, geliştirme hızını artırır, insan hatasını azaltır ve istemci ile sunucu arasında tip güvenliğini sağlar. Örneğin, bir User tipini SDL’de tanımladığınızda, kod üretici otomatik olarak bu tipe karşılık gelen bir sınıf veya arayüz oluşturabilir.
Doğrulama (Validation)
GraphQL araçları, bir SDL şemasının geçerliliğini kontrol etmek için kullanılır. SDL, belirli kurallara uymak zorundadır (örneğin, benzersiz tip adları, doğru alan tipleri). Geçersiz bir şema, API’nin düzgün çalışmamasına neden olabilir. Editör eklentileri ve CI/CD süreçlerine entegre edilen araçlar, şema hatalarını erken aşamada tespit etmeye yardımcı olur.
Dokümantasyon (Documentation)
Yukarıda bahsedildiği gibi, SDL’deki açıklamalar, otomatik dokümantasyon araçları için temel oluşturur. GraphiQL ve GraphQL Playground gibi interaktif geliştirme ortamları, şemanızı doğrudan SDL’den okuyarak zengin, gezinilebilir dokümantasyon sunar. Bu, API’nizi kullanan geliştiricilerin kolayca anlayıp keşfetmesini sağlar.
Federasyon ve Ağ Geçitleri (Federation and Gateways)
Büyük ve karmaşık mikroservis mimarilerinde, tek bir monolitik GraphQL şeması yerine birden fazla bağımsız GraphQL hizmetini (alt grafikleri) bir araya getirmek gerekebilir. Apollo Federation gibi çözümler, bu alt grafikleri birleştirmek için SDL’i kullanır. Her alt grafik, kendi SDL şemasını tanımlar ve federasyon ağ geçidi (gateway), bu şemaları birleştirerek istemcilere tek bir birleşik GraphQL API sunar. Bu senaryoda SDL, alt grafiklerin birbirleriyle nasıl etkileşime gireceğini ve ortak tipleri nasıl paylaşacağını tanımlayan kritik bir rol oynar.
Sonuç
GraphQL SDL, modern API geliştirme dünyasında sadece bir tanım dili olmanın ötesinde, bir API’nin kalbini ve ruhunu temsil eder. API’nin sunduğu tüm veri yapılarını, sorgulanabilir alanları ve manipülasyon yeteneklerini dilden bağımsız, okunabilir ve makine tarafından yorumlanabilir bir formatta sunar. SDL sayesinde, istemciler API’nin yeteneklerini tam olarak anlayabilir, sunucular ise gelen istekleri güvenli bir şekilde işleyebilir.
Bu makalede, SDL’in temel yapı taşları olan şema, nesne, skaler, enum, girdi, arayüz ve birleşim tiplerini ayrıntılı olarak inceledik. Alanların ve argümanların nasıl tanımlandığını, yönergelerin şema davranışını nasıl değiştirebildiğini ve açıklamaların dokümantasyon için ne kadar önemli olduğunu gördük. Ayrıca, uzatmalar ve şema yönergeleri gibi gelişmiş kavramlara ve SDL’in kod üretimi, doğrulama, dokümantasyon ve federasyon gibi pratik kullanım alanlarına değindik.
SDL’i iyi anlamak ve doğru bir şekilde kullanmak, sadece daha iyi GraphQL API’leri inşa etmekle kalmaz, aynı zamanda API’nizin yaşam döngüsü boyunca sürdürülebilirliğini, genişletilebilirliğini ve geliştirici deneyimini önemli ölçüde artırır. GraphQL ekosistemi geliştikçe, SDL’in merkezi rolü daha da güçlenecek ve bu dili ustaca kullanabilen geliştiriciler, geleceğin veri odaklı uygulamalarını şekillendirmede kilit bir konuma sahip olacaktır.
