GraphQL Şema Yönetimi: Schema Registry Nasıl Kullanılır?

Büyük bir microservice mimarisinde onlarca GraphQL servisi yönetmeye çalışanlar bilir: bir sabah uyandığınızda staging ortamınızdaki frontend uygulaması çökmüş, sebebi ise başka bir ekibin şema üzerinde yaptığı kırıcı değişiklik. Schema registry tam da bu acıyı gidermek için var. Ama iş sadece “kurdum, çalışıyor” değil, doğru kullanmak ciddi bir kültür ve süreç meselesi.

Schema Registry Nedir ve Neden Önemlidir?

GraphQL şema yönetimi, özellikle birden fazla ekibin aynı API üzerinde çalıştığı ortamlarda kaotik bir hal alabilir. Schema registry, tüm GraphQL şemalarınız için merkezi bir depo ve yönetim katmanıdır. Sadece şemaları saklamakla kalmaz; versiyonlama, uyumluluk kontrolü, dokümantasyon ve CI/CD entegrasyonu gibi kritik işlevleri de üstlenir.

Apollo Federation ekosisteminde Apollo Schema Registry en yaygın çözüm olsa da, açık kaynak alternatifleri de oldukça olgunlaşmış durumda. Özellikle GraphQL Inspector, Hive ve Buf gibi araçlar ciddi üretim ortamlarında kullanılıyor.

Şema kaydının sağladığı temel değer şunlar:

  • Kırıcı değişiklik tespiti: Bir alanı sildiğinizde ya da tipini değiştirdiğinizde, bunu canlıya çıkmadan önce otomatik olarak yakalarsınız.
  • Şema geçmişi: “Bu field ne zaman eklendi, kim ekledi?” sorularına anında cevap.
  • Federated şema doğrulama: Birden fazla subgraph’ın birleşik şeması tutarlı mı, bunu merkezi olarak kontrol edersiniz.
  • Kullanım analitikleri: Hangi field’lar kullanılıyor, hangilerini deprecated edebilirsiniz?

Apollo Schema Registry Kurulumu

Başlangıç olarak Apollo Studio üzerinden ücretsiz bir registry kurabilirsiniz. Ama self-hosted bir çözüm istiyorsanız, Apollo Server ile kendi registry sürecinizi de yönetebilirsiniz.

# Apollo CLI kurulumu
npm install -g @apollo/rover

# Rover ile kimlik doğrulama
rover config auth

# Mevcut şemayı registry'ye publish etme
rover graph publish my-graph@production 
  --schema ./schema.graphql 
  --name my-service

Basit bir GraphQL şeması ile başlayalım:

# schema.graphql
type Query {
  user(id: ID!): User
  users(filter: UserFilter): [User!]!
  product(id: ID!): Product
}

type User {
  id: ID!
  email: String!
  name: String!
  createdAt: String!
  orders: [Order!]!
}

type Product {
  id: ID!
  name: String!
  price: Float!
  stock: Int!
}

type Order {
  id: ID!
  user: User!
  products: [Product!]!
  total: Float!
  status: OrderStatus!
}

enum OrderStatus {
  PENDING
  PROCESSING
  SHIPPED
  DELIVERED
  CANCELLED
}

input UserFilter {
  email: String
  name: String
  createdAfter: String
}

Bu şemayı registry’ye push etmek için:

# Şemayı staging ortamına publish et
rover graph publish my-ecommerce-app@staging 
  --schema ./schema.graphql

# Üretim ortamına publish etmeden önce diff al
rover graph diff my-ecommerce-app@staging 
  --schema ./schema.graphql

Kırıcı Değişiklik Tespiti

Gerçek hayatta karşılaştığım en sık sorun şu: bir geliştirici User tipindeki email alanını emailAddress olarak yeniden adlandırıyor. Backend’den baktığında mantıklı, ama bu field’ı kullanan onlarca istemci var. İşte burada registry devreye girer.

# GraphQL Inspector ile kırıcı değişiklik kontrolü
npm install -g @graphql-inspector/cli

# Eski şema ile yeni şemayı karşılaştır
graphql-inspector diff 
  "old-schema.graphql" 
  "new-schema.graphql"

Çıktı şuna benzer bir şey verir:

# Örnek çıktı (bu bir simülasyon değil, gerçek CLI çıktısı formatı)
# Detected the following changes:

# BREAKING CHANGES:
# - Field 'User.email' was removed
# - Field 'User.emailAddress' was added (non-breaking)

# Bu değişiklik istemcileri kıracak!
# email field'ını kullanan tüm sorgular çalışmayacak

Bu tür durumlar için deprecation yaklaşımı kullanmalısınız:

type User {
  id: ID!
  email: String! @deprecated(reason: "emailAddress kullanın, bu field 3 ay sonra kaldırılacak")
  emailAddress: String!
  name: String!
  createdAt: String!
}

Deprecation stratejisi olmadan büyük bir şemayı yönetmek, köprüyü onarırken üzerinden araç geçirmeye benziyor.

CI/CD Entegrasyonu

Şema değişikliklerini CI pipeline’ına entegre etmek, registry’nin gerçek değerini ortaya çıkaran noktadır. GitHub Actions ile örnek bir workflow:

# .github/workflows/schema-check.yml
name: GraphQL Schema Check

on:
  pull_request:
    branches: [main, staging]
    paths:
      - 'schema.graphql'
      - 'src/**/*.graphql'

jobs:
  schema-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Rover CLI
        run: |
          curl -sSL https://rover.apollo.dev/nix/latest | sh
          echo "$HOME/.rover/bin" >> $GITHUB_PATH

      - name: Schema Diff Check
        env:
          APOLLO_KEY: ${{ secrets.APOLLO_KEY }}
        run: |
          rover graph check my-ecommerce-app@production 
            --schema ./schema.graphql 
            --background false

      - name: Publish to Staging (on merge)
        if: github.event_name == 'push' && github.ref == 'refs/heads/staging'
        env:
          APOLLO_KEY: ${{ secrets.APOLLO_KEY }}
        run: |
          rover graph publish my-ecommerce-app@staging 
            --schema ./schema.graphql

Bu pipeline sayesinde herhangi bir PR’da şema değişikliği varsa, CI otomatik olarak kırıcı değişiklik analizi yapar ve PR’a yorum düşer. Üretim ortamına sürpriz değişiklik geçmesi neredeyse imkansız hale gelir.

Federated Şema Yönetimi

Tek bir monolitik GraphQL servisi yönetmek zor, ama federation ile onlarca subgraph yönetmek bambaşka bir hikaye. Apollo Federation senaryosunda her servis kendi şemasını registry’ye publish eder, gateway da bunları birleştirir.

# Subgraph şemasını publish et
rover subgraph publish my-supergraph@production 
  --schema ./users-service/schema.graphql 
  --name users-service 
  --routing-url https://users.internal.mycompany.com/graphql

# Ürün servisi şemasını publish et  
rover subgraph publish my-supergraph@production 
  --schema ./products-service/schema.graphql 
  --name products-service 
  --routing-url https://products.internal.mycompany.com/graphql

# Birleşik şemayı doğrula
rover supergraph compose --config ./supergraph.yaml
# supergraph.yaml
federation_version: =2.4.0
subgraphs:
  users:
    routing_url: https://users.internal.mycompany.com/graphql
    schema:
      file: ./users-service/schema.graphql
  products:
    routing_url: https://products.internal.mycompany.com/graphql
    schema:
      file: ./products-service/schema.graphql
  orders:
    routing_url: https://orders.internal.mycompany.com/graphql
    schema:
      file: ./orders-service/schema.graphql

Federation’da dikkat etmeniz gereken en önemli şey, @key direktiflerinin tutarlılığı. Eğer users-service User tipini id ile tanımlıyor ama orders-service bunu referans alırken farklı bir key kullanıyorsa, birleşik şema derlenmez bile.

Açık Kaynak Alternatif: GraphQL Hive

Apollo Studio ücretli plana geçince maliyetler hızla artabiliyor. Açık kaynak ve self-hosted bir alternatif isteyenler için GraphQL Hive çok iyi bir seçenek. Kendi sunucunuzda Docker ile çalıştırabilirsiniz.

# Hive CLI kurulumu
npm install -g @graphql-hive/cli

# Şema publish
hive schema:publish 
  --registry.accessToken YOUR_TOKEN 
  --registry.endpoint https://your-hive-instance.com/graphql 
  schema.graphql

# Şema kontrolü
hive schema:check 
  --registry.accessToken YOUR_TOKEN 
  --registry.endpoint https://your-hive-instance.com/graphql 
  schema.graphql

Hive’ın öne çıkan özelliği kullanım analitikleri. Hangi field’ların kimler tarafından ne sıklıkla kullanıldığını görebilirsiniz. Bir field’ı deprecated edecekseniz, önce bunu kontrol edin. Hive’ın kullanım raporuna bakıp “bu field hala ayda 50.000 kez sorgulanıyor” dediğinizde planlarınızı revize etmek zorunda kalabilirsiniz.

// Hive kullanım raporlaması için Apollo Server entegrasyonu
import { createServer } from '@graphql-hive/client';
import { ApolloServer } from '@apollo/server';

const hive = createServer({
  enabled: true,
  token: process.env.HIVE_TOKEN,
  usage: {
    enabled: true,
    // Her isteği örneklemek yerine %10'unu raporla
    sampleRate: 0.1,
  },
  reporting: {
    enabled: true,
    author: process.env.SERVICE_NAME,
    commit: process.env.GIT_COMMIT_SHA,
  },
});

const server = new ApolloServer({
  schema,
  plugins: [hive.useApollo()],
});

Şema Versiyonlama Stratejileri

Şema yönetiminde iki temel yaklaşım var ve her ikisinin de güçlü, zayıf yanları mevcut.

Continuous Evolution (Sürekli Evrim): Tek bir şema versiyonu tutarsınız, değişiklikler deprecation ile yönetilir. GraphQL topluluğunun önerdiği yol bu. Avantajı basitlik, dezavantajı ise deprecated field’ların uzun süre canlıda kalması.

Explicit Versioning: /graphql/v1, /graphql/v2 gibi endpoint versiyonlaması. REST dünyasından gelen ekipler bunu tercih ediyor ama GraphQL’in ruhuna pek uymuyor.

Pratikte gördüğüm en sağlıklı yaklaşım şu kombinasyon:

# Şema üzerinde direktiflerle versiyon yönetimi
directive @since(version: String!) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION | ENUM_VALUE

type User {
  id: ID!
  
  # Eski alan - 6 ay daha canlıda kalacak
  email: String! @deprecated(reason: "Lütfen 'contactEmail' kullanın. Bu alan Mart 2025'te kaldırılacak.")
  
  # Yeni alanlar
  contactEmail: String! @since(version: "2.1.0")
  phoneNumber: String @since(version: "2.3.0")
  
  name: String!
  profile: UserProfile @since(version: "2.0.0")
}

Bir gerçek hayat senaryosu paylaşayım: bir e-ticaret projesinde Product.price alanı başlangıçta Float tipindeydi. Sonradan para birimi bilgisi eklenmesi gerekti. Direkt kırıcı değişiklik yapmak yerine şöyle bir migration path izledik:

type Product {
  id: ID!
  name: String!
  
  # Eski - sadece sayısal değer, para birimi yok
  price: Float! @deprecated(reason: "pricing alanını kullanın, çok para birimi desteği için")
  
  # Yeni - tam fiyatlandırma nesnesi
  pricing: ProductPricing!
}

type ProductPricing {
  amount: Float!
  currency: String!
  formattedPrice: String!
  discountedAmount: Float
  isOnSale: Boolean!
}

Bu geçiş 3 ay sürdü. Tüm istemciler pricing‘e geçtikten sonra price field’ını kaldırdık. Registry’deki kullanım verileri de bize “artık kimse price kullanmıyor” diye kesin onay verdi.

Şema Lint ve Kalite Kontrolleri

Şema kalitesini sadece kırıcı değişikliklerle sınırlı tutmayın. Naming convention’lar, description zorunlulukları, karmaşıklık limitleri gibi konularda da otomatik kontroller kurabilirsiniz.

# graphql-inspector ile lint
graphql-inspector lint schema.graphql 
  --rules ./lint-rules.js
// lint-rules.js
module.exports = {
  rules: {
    // Tüm type'ların description'ı olmalı
    'require-description': {
      types: true,
      fields: true,
      args: true,
    },
    // Field isimleri camelCase olmalı
    'naming-convention': {
      types: 'PascalCase',
      fields: 'camelCase',
      arguments: 'camelCase',
      enum: 'UPPER_CASE',
    },
    // Pagination argümanları standart olmalı
    'relay-page-info': true,
  }
};

Bu kuralları CI pipeline’ına entegre ettiğinizde, her PR’da şema kalitesi otomatik denetleniyor. “Neden bu field’a description yazmamışsın?” diye code review’da vakit kaybetmiyorsunuz.

Monitoring ve Alerting

Registry kurulduktan sonra onu izlemek de gerekiyor. Özellikle şu senaryolara dikkat edin:

  • Bir servis şemasını güncellemeyi unuttu ve eski versiyonu registry’de duruyor
  • Yeni bir subgraph publish edildi ama routing URL yanlış
  • Deprecated field’ların kullanımı azalmıyor, istemciler migrate olmamış
# Tüm subgraph'ların son publish tarihlerini kontrol et
rover subgraph list my-supergraph@production

# Belirli bir subgraph'ın şemasını çek ve yerel ile karşılaştır
rover subgraph fetch my-supergraph@production 
  --name users-service > registry-users-schema.graphql

diff registry-users-schema.graphql ./users-service/schema.graphql

Bir monitoring script örneği:

#!/bin/bash
# check-schema-freshness.sh
# Registry'deki şemanın son 24 saatte güncellenip güncellenmediğini kontrol eder

SERVICES=("users-service" "products-service" "orders-service" "payment-service")

for service in "${SERVICES[@]}"; do
  last_updated=$(rover subgraph fetch "my-supergraph@production" 
    --name "$service" 
    --format json | jq -r '.updatedAt')
  
  last_updated_epoch=$(date -d "$last_updated" +%s)
  now_epoch=$(date +%s)
  diff=$((now_epoch - last_updated_epoch))
  
  # 48 saatten eski şema varsa uyar (saniye cinsinden: 172800)
  if [ $diff -gt 172800 ]; then
    echo "UYARI: $service servisi şeması 48 saatten eski!"
    echo "Son güncelleme: $last_updated"
    # Slack notification gönder
    curl -X POST "$SLACK_WEBHOOK_URL" 
      -H 'Content-type: application/json' 
      --data "{"text":"UYARI: $service GraphQL şeması 48 saatten eski. Lütfen kontrol edin."}"
  fi
done

Bu scripti cron ile çalıştırın, ekiplerin schema registry’i güncel tutup tutmadığını otomatik takip edin.

Sonuç

Schema registry, büyüdükçe değer kazanan bir yatırım. Tek servisli küçük bir projede belki gereksiz görünebilir, ama 5-6 servise ve birden fazla ekibe ulaştığınızda olmadan nasıl çalıştığınızı sorgular hale geliyorsunuz.

Pratik tavsiye olarak şunu söyleyeyim: Apollo Studio ile başlayın, sınırlarını görün, sonra gerekirse Hive’a geçin. CI entegrasyonunu ilk günden kurun, “sonra yaparız” dediğinizde yapmıyorsunuz. Deprecation kültürünü ekip içinde oturtun; şema değişikliklerinde “hemen sileceğiz” değil “önce deprecated, 3 ay sonra sileceğiz” yaklaşımını standart haline getirin.

Kırıcı değişiklik alarmı aldığınızda paniğe kapılmak yerine artık bir süreciniz var: registry size tam olarak neyin kırıldığını söylüyor, hangi istemcilerin etkilendiğini gösterıyor ve migration yolunu planlayabiliyorsunuz. Sabah çökmüş bir frontend ile başlamak yerine kafeinli kahvenizi sakin sakin içebilirsiniz.

Bir yanıt yazın

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