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. id ve name için ayrı fragment tanımlamak overkill olur.
  • Tip uyuşmazlığı: Fragment’ı yanlış tip üzerinde kullanmaya çalışırsan şema doğrulaması hata verir. User tipi için tanımlanan fragment’ı Product üzerinde kullanamazsın.
  • Fragment isim çakışması: Aynı isimde iki fragment tanımlanamaz. Büyük projelerde ComponentName_TypeName convention’ı 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_TypeName convention’ı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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir