Kodun Tekrarını Önleme: GraphQL Fragment Kullanımı
API geliştirirken en sık karşılaşılan sorunlardan biri, aynı alan listelerini defalarca yazmak zorunda kalmaktır. GraphQL sorgularında da bu durum sıkça yaşanır: Bir kullanıcı nesnesinin alanlarını bir sorguda yazarsın, başka bir sorguda aynısını tekrar yazarsın, mutation’da bir kez daha… Derken bakarsın ki aynı id, name, email, createdAt satırı onlarca yerde karşına çıkıyor. İşte bu sorunu çözmek için GraphQL, Fragment adında zarif bir mekanizma sunuyor.
Fragment Nedir ve Neden Kullanılır?
Fragment, GraphQL’de yeniden kullanılabilir alan gruplarını tanımlamanı sağlayan yapılardır. Bir tip üzerinde tanımlanırlar ve birden fazla sorgu, mutation veya subscription içinde kullanılabilirler. Teknik açıdan bakıldığında, fragment bir tip üzerindeki alan seçimini (selection set) kapsüllemenin yoludur.
Neden kullanmalısın?
- DRY prensibi: Aynı alan listesini defalarca yazmaktan kurtulursun
- Bakım kolaylığı: Bir alanda değişiklik yaptığında tek yeri güncellemeniz yeterli olur
- Okunabilirlik: Sorgular daha temiz ve anlaşılır hale gelir
- Tutarlılık: Farklı sorgular arasında aynı veri yapısının döndürülmesini garantilersin
- Test edilebilirlik: Fragment’ları izole şekilde test edebilirsin
Büyük projelerde fragment kullanımı bir tercih değil, bir zorunluluk haline gelir. Özellikle microservice mimarisinde, birden fazla ekibin aynı GraphQL şeması üzerinde çalıştığı durumlarda fragment’lar hayat kurtarır.
Temel Fragment Sözdizimi
Fragment tanımlamak oldukça basittir. fragment anahtar kelimesiyle başlar, bir isim verilir ve hangi tip üzerinde çalıştığı belirtilir.
# Temel fragment örneği
fragment UserBasicInfo on User {
id
name
email
avatarUrl
}
Bu fragment’ı bir sorguda kullanmak için spread operatörünü (...) kullanırsın:
# Fragment kullanarak kullanıcı sorgulama
query GetUser($userId: ID!) {
user(id: $userId) {
...UserBasicInfo
phoneNumber
address {
city
country
}
}
}
fragment UserBasicInfo on User {
id
name
email
avatarUrl
}
Dikkat etmişsindir, fragment tanımı sorgunun altında bile olsa çalışır. GraphQL derleyicisi tüm dökümanı parse ettikten sonra çalıştırır, bu yüzden sıralama önemli değildir.
Gerçek Dünya Senaryosu: E-Ticaret Uygulaması
Bir e-ticaret projesi üzerinde çalıştığını düşün. Ürün bilgilerini çeken onlarca sorgun var. Sepet sayfası, ürün listesi, ürün detayı, sipariş geçmişi… Hepsinde ürün alanlarına ihtiyaç duyuyorsun.
Fragment kullanmadan önce nasıl görünürdü:
# Fragment'sız, tekrar eden kod - KÖTÜ ÖRNEK
query GetCart {
cart {
items {
product {
id
name
price
currency
imageUrl
stockCount
category {
id
name
}
}
quantity
}
}
}
query GetProductList {
products {
id
name
price
currency
imageUrl
stockCount
category {
id
name
}
}
}
query GetOrderHistory {
orders {
items {
product {
id
name
price
currency
imageUrl
stockCount
category {
id
name
}
}
quantity
purchasedPrice
}
}
}
Şimdi fragment kullanarak nasıl temizlenir:
# Fragment ile temizlenmiş hali - İYİ ÖRNEK
fragment ProductCard on Product {
id
name
price
currency
imageUrl
stockCount
category {
id
name
}
}
query GetCart {
cart {
items {
product {
...ProductCard
}
quantity
}
}
}
query GetProductList {
products {
...ProductCard
}
}
query GetOrderHistory {
orders {
items {
product {
...ProductCard
}
quantity
purchasedPrice
}
}
}
Fark bariz. Hem kod miktarı azaldı, hem de ProductCard fragment’ında bir değişiklik gerektiğinde tek yerden yapabiliyorsun. Diyelim ki ürünlere discountPercentage alanı eklendi. Eski yöntemde üç farklı sorguyu güncellemeniz gerekirdi. Yeni yöntemde sadece fragment’a bir satır ekliyorsun.
İç İçe Fragment Kullanımı
Fragment’lar diğer fragment’ları da kullanabilir. Bu özellik, karmaşık veri yapılarını yönetirken çok işe yarar.
# İç içe fragment kullanımı
fragment CategoryInfo on Category {
id
name
slug
}
fragment ProductCard on Product {
id
name
price
currency
imageUrl
category {
...CategoryInfo
}
}
fragment ProductDetail on Product {
...ProductCard
description
specifications
reviews {
id
rating
comment
author {
id
name
}
}
}
query GetProductDetail($productId: ID!) {
product(id: $productId) {
...ProductDetail
}
}
Burada ProductDetail, ProductCard‘ı içeriyor, ProductCard da CategoryInfo‘yu içeriyor. Bu katmanlı yapı, veri modelinin hiyerarşisini yansıtıyor. Bir bileşen ProductCard fragment’ını kullanırken, daha detaylı bir sayfa ProductDetail‘ı kullanabilir.
Inline Fragment ve Tip Koşulları
Fragment’ların bir diğer güçlü kullanım şekli de inline fragment‘lardır. Özellikle union type’lar ve interface’lerle çalışırken vazgeçilmezdir.
Diyelim ki bir arama sistemi tasarlıyorsun ve arama sonuçları hem ürün hem de kategori hem de kullanıcı döndürebiliyor:
# Union type ile inline fragment kullanımı
query Search($term: String!) {
search(term: $term) {
... on Product {
id
name
price
imageUrl
}
... on Category {
id
name
productCount
}
... on User {
id
name
email
}
}
}
Bunu named fragment ile de yapabilirsin:
# Named fragment ile union type kullanımı
fragment ProductSearchResult on Product {
id
name
price
imageUrl
category {
name
}
}
fragment CategorySearchResult on Category {
id
name
slug
productCount
}
fragment UserSearchResult on User {
id
name
email
avatarUrl
}
query Search($term: String!) {
search(term: $term) {
...ProductSearchResult
...CategorySearchResult
...UserSearchResult
}
}
Bu yaklaşım, her tip için ayrı dosyalar oluşturmana ve ilgili component ile birlikte tutmana olanak tanır.
Apollo Client ile Fragment Yönetimi
Eğer frontend tarafında Apollo Client kullanıyorsan, fragment yönetimi daha da kritik hale gelir. Apollo, fragment’larla birlikte çok güçlü bir cache mekanizması sunar.
Gerçek bir React + Apollo projesinde fragment’ları nasıl organize edersin:
# fragments/user.graphql dosyası
fragment UserAvatar on User {
id
name
avatarUrl
}
fragment UserProfile on User {
...UserAvatar
email
bio
location
joinedAt
followerCount
followingCount
}
fragment UserCard on User {
...UserAvatar
bio
followerCount
}
# queries/users.graphql dosyası
#import './fragments/user.graphql'
query GetUserProfile($username: String!) {
user(username: $username) {
...UserProfile
posts(first: 10) {
id
title
createdAt
}
}
}
query GetUserFollowers($userId: ID!) {
user(id: $userId) {
followers {
...UserCard
}
}
}
Apollo Client’ın InMemoryCache‘i, fragment’larla tanımlanan nesneleri ID bazında cache’ler. Bu sayede UserProfile sorgusundan gelen bir kullanıcıyı cache’lediğinde, UserCard fragment’ı kullanan başka bir sorgu aynı kullanıcıyı döndürdüğünde cache’den okur, ağ isteği yapmaz.
Fragment Colocation: Component ile Fragment’ı Bir Arada Tutmak
Modern GraphQL geliştirmede popüler hale gelen bir pattern, fragment colocation‘dır. Her component, kendi ihtiyaç duyduğu fragment’ı yanında tanımlar. Relay bu pattern’in öncüsüdür.
# UserCard component'inin fragment'ı - UserCard.graphql
fragment UserCard_user on User {
id
name
avatarUrl
bio
}
# PostList component'inin fragment'ı - PostList.graphql
fragment PostList_post on Post {
id
title
excerpt
publishedAt
author {
...UserCard_user
}
thumbnailUrl
commentCount
likeCount
}
# Ana sayfa sorgusu - HomePage.graphql
#import '../components/UserCard/UserCard.graphql'
#import '../components/PostList/PostList.graphql'
query GetHomePage($userId: ID!) {
currentUser {
...UserCard_user
recommendedPosts {
...PostList_post
}
}
}
Bu yaklaşımın avantajları:
- Bağımsızlık: Bir component’in veri ihtiyacı değiştiğinde sadece kendi fragment’ını günceller
- Keşfedilebilirlik: Component dosyasına bakınca hangi veriyi kullandığını hemen anlarsın
- Refactoring güvenliği: Fragment silindiğinde derleyici hata verir, sessiz hatalar olmaz
- Ekip çalışması: Farklı ekipler kendi component’larını bağımsız geliştirebilir
Fragment ile Direktif Kullanımı
Fragment’lar, @include ve @skip direktifleriyle birlikte kullanılarak koşullu sorgular oluşturabilirsin.
# Direktif ile fragment kullanımı
fragment UserPublicInfo on User {
id
name
avatarUrl
bio
}
fragment UserPrivateInfo on User {
email
phoneNumber
address {
street
city
country
}
paymentMethods {
id
type
lastFourDigits
}
}
query GetUser($userId: ID!, $isOwner: Boolean!) {
user(id: $userId) {
...UserPublicInfo
...UserPrivateInfo @include(if: $isOwner)
}
}
Bu sayede API’den dönen veri miktarını kontrol altında tutabiliyorsun. Profil sayfasını kendi profilemse tam veri çek, başkasının profileyse sadece public veriyi çek. Tek sorguda, temiz şekilde.
Persisted Query ve Fragment Optimizasyonu
Production ortamında performans kritik hale geldiğinde, fragment’larla persisted query kullanımı devreye giriyor. Büyük sorgular ağ üzerinden taşınmak yerine, hash ile ifade edilebilir.
Fragment kullanımının bir diğer optimizasyon etkisi de query complexity hesaplamalarındadır. Bazı GraphQL sunucuları sorgu karmaşıklığına limit koyar. Fragment’lar bu hesabı etkilemez çünkü spread edildiklerinde tek bir alan tanımı gibi işlenir.
# Fragment kullanımıyla query complexity kontrolü
fragment BasicProductInfo on Product {
id
name
price
}
# Bu sorgu, complexity 3 (BasicProductInfo) + 2 (quantity, addedAt) = 5
query GetCartItems {
cart {
items {
product {
...BasicProductInfo
}
quantity
addedAt
}
}
}
Yaygın Hatalar ve Kaçınılması Gerekenler
Fragment kullanırken bazı tuzaklar var. Bunları bilmek, ilerleyen süreçte başını ağrıtmaz.
- Döngüsel fragment referansı: Fragment A, Fragment B’yi içerirse ve Fragment B de Fragment A’yı içerirse, GraphQL derleyicisi hata verir. Bunu yapmak mümkün değildir, zaten mantıksal olarak da doğru değildir.
- Kullanılmayan fragment tanımları: Bir fragment tanımlayıp hiç kullanmazsan bazı araçlar uyarı verir. Production’da gönderilen dökümanda kullanılmayan fragment olması gereksiz boyut artışına neden olur.
- Aşırı granüler fragment: Her iki alan için ayrı fragment oluşturmak da bir aşırılıktır. Fragment, anlamlı bir veri kümesini temsil etmeli.
idvenameiçin ayrı fragment tanımlamak overkill olur.
- Tip uyuşmazlığı: Fragment’ı yanlış tip üzerinde kullanmaya çalışırsan şema doğrulaması hata verir.
Usertipi için tanımlanan fragment’ıProductüzerinde kullanamazsın.
- Fragment isim çakışması: Aynı isimde iki fragment tanımlanamaz. Büyük projelerde
ComponentName_TypeNameconvention’ı kullanmak bu sorunu önler.
Node.js GraphQL Sunucusunda Fragment Desteği
Sunucu tarafında fragment desteği için ekstra bir şey yapman gerekmez. graphql-js kütüphanesi ve ona dayalı tüm framework’ler fragment’ları otomatik olarak işler. Ama fragment tabanlı sorgularda bazı sunucu taraflı optimizasyonlar yapabilirsin:
# Apollo Server ile fragment aware persisted queries konfigürasyonu
# apollo.config.js
module.exports = {
client: {
service: {
name: 'my-service',
url: 'http://localhost:4000/graphql',
},
includes: ['src/**/*.graphql'],
excludes: ['**/__tests__/**'],
},
};
# Fragment validation middleware örneği (Express + graphql-js)
# Fragment içeren sorgularda maksimum derinlik kontrolü
const { parse, validate, specifiedRules } = require('graphql');
function validateFragmentDepth(query, maxDepth = 5) {
const ast = parse(query);
const fragments = {};
ast.definitions.forEach(def => {
if (def.kind === 'FragmentDefinition') {
fragments[def.name.value] = def;
}
});
// Fragment derinlik kontrolü burada yapılır
return validate(schema, ast, specifiedRules);
}
Fragment’ları Dosya Bazında Organize Etme
Proje büyüdükçe fragment’ları nerede tutacağın önemli bir karar. İki yaklaşım var:
Merkezi fragment deposu: Tüm fragment’lar src/graphql/fragments/ altında toplanır. Küçük ve orta ölçekli projelerde iyi çalışır.
Colocated fragment’lar: Her component kendi fragment’ını yanında taşır. Büyük ve ekip bazlı projelerde tercih edilir.
Pratikte çoğu ekip ikisini harmanlayan bir yaklaşım kullanır. Ortak varlıklar (User, Product, Category gibi) merkezi yerde tanımlanır, component’a özgü fragment’lar ise colocated tutulur.
# Önerilen dizin yapısı
src/
graphql/
fragments/
user.graphql # Ortak User fragment'ları
product.graphql # Ortak Product fragment'ları
category.graphql # Ortak Category fragment'ları
queries/
users.graphql
products.graphql
components/
UserCard/
UserCard.jsx
UserCard.graphql # Component'a özgü fragment
ProductDetail/
ProductDetail.jsx
ProductDetail.graphql
Sonuç
GraphQL fragment’ları, API geliştirmede DRY prensibini hayata geçirmenin en etkili yollarından biridir. Başlangıçta küçük bir kolaylık gibi görünse de, proje büyüdükçe değeri katlanarak artar. Tek bir alanda yapılan değişiklik onlarca sorguyu etkilediğinde, fragment kullanmamış olmak ciddi teknik borç bırakır.
Pratik öneriler:
- Fragment isimlerinde
ComponentName_TypeNameconvention’ına uy, özellikle büyük projelerde - Anlamlı veri kümelerini fragment’a al, çok küçük ya da çok büyük fragment’lardan kaçın
- Apollo Client kullanıyorsan fragment colocation pattern’ini benimse
- Union type ve interface kullanıyorsan inline fragment’larla tip güvenliğini garanti altına al
- Fragment’larını
src/graphql/fragments/altında organize et ve import convention’ı belirle
GraphQL öğrenme sürecinde fragment’lar genellikle sonralara bırakılır, ama aslında şema tasarımıyla eş zamanlı düşünülmesi gereken bir konudur. Şemanı tasarlarken “bu veri grubunu kim kullanacak, hangi sorgularda aynı alanlar tekrar edecek?” sorusunu kendine sor. Cevaplar seni doğal olarak fragment’lara götürecektir.
