GraphQL ve ASP.NET Core Web API: Restful Dünyadan Çıkış Yapmak

Onur KARAKUŞ
17 min readJan 3, 2024

--

Günümüzde, web tabanlı uygulamaların hızla artan karmaşıklığı, geliştiricilere esnek, verimli ve optimize edilmiş bir veri iletişim protokolü arayışını beraberinde getirmiştir. Bu noktada karşımıza çıkan GraphQL, RESTful servislerin sınırlamalarını aşarak, veri taleplerini daha özelleştirilebilir ve etkili bir şekilde yönetme vaadi sunan bir teknolojidir.

Bu makalede, GraphQL’in doğuşundan günümüze olan evrimini inceleyecek, GraphQL dilinin temellerini öğrenecek, REST API ile olan temel farkları ele alacak ve ASP.NET Core Web API üzerinde nasıl uygulandığını göreceğiz. Ayrıca, gerçek dünya performansını test ederek GraphQL ve RESTful servisler arasında bir benchmark yaparak, hangi durumda hangi teknolojinin tercih edilmesi gerektiğini tartışacağız.

Haydi, GraphQL dünyasına birlikte bir yolculuğa çıkalım!

Bölüm 1: GraphQL Veri Sorgulama Dilinin Yolculuğu ve Gelişimi

GraphQL, 2012 yılında Facebook tarafından başlatılan ve 2015 yılında açık kaynak olarak yayınlanan bir veri sorgulama dilidir. Temel amacı, istemcilerin ihtiyaç duyduğu veriyi daha etkili bir şekilde alabilmelerini sağlamaktır. Geleneksel RESTful API’lerin katı veri yapısına bir alternatif olarak doğan GraphQL, bir dizi avantajı beraberinde getirir.

GraphQL özelliklerine bakacak olursak;

  • Esnek Veri Sorgulama:
    GraphQL, istemcilere ihtiyaçlarına uygun şekilde veri sorgulama esnekliği tanır. İstemciler, istedikleri alanları ve veri türlerini belirterek gereksiz veri transferini önleyebilirler.
  • Tek Endpoint:
    GraphQL, sadece tek bir endpoint üzerinden çalışır. Bu, birkaç farklı REST endpoint’i yerine tek bir endpoint ile tüm veri taleplerini karşılamayı mümkün kılar, bu da API’nin daha tutarlı ve basit olmasını sağlar.
  • Şema (Schema) Tanımı:
    GraphQL, bir şema tanımına dayanır. Şema, API’nin hangi verileri ve operasyonları desteklediğini belirler. Bu sayede, geliştiricilere API’nin kullanımı konusunda net bir rehberlik sağlanır.
  • İstek ve Yanıt:
    GraphQL’de istek ve yanıt aynı yapıda olup, istemci sadece istediği veriyi belirterek API’den isteğini gerçekleştirir. Bu, veri transferini optimize eder ve istemcinin ihtiyaç duyduğu veriyi almasını kolaylaştırır.
  • İlişkisel Veritabanları ile Uyum:
    GraphQL, ilişkisel veritabanları ve diğer veri kaynakları ile uyumlu bir şekilde çalışabilir. Veri modelini değiştirmeksizin, istemcilerin ihtiyaçlarına göre veri çekmelerine olanak tanır.
  • Gelişmiş Yetenekler:
    GraphQL, özelleştirilebilir derleme (query execution) ve doğrusal bir şekilde büyüme potansiyeli gibi gelişmiş yeteneklere sahiptir. Bu özellikleri, büyük ve karmaşık uygulamalarda etkili bir şekilde kullanılabilmesini sağlar.

GraphQL, özellikle modern web uygulamalarının ihtiyaçlarını karşılamak üzere tasarlanmış bir teknolojidir. İstemcilere daha fazla kontrol ve esneklik sağlayarak, veri alışverişini daha verimli hale getirir.

GraphQL’in Gelişim Süreci:

2012: Başlangıç

GraphQL’in gelişimi, Facebook’un büyük ve karmaşık veri ihtiyaçlarına yönelik bir çözüm arayışıyla başlar. Facebook, çeşitli istemcilere hizmet verirken, bu istemcilerin farklı veri talepleri geleneksel RESTful API’lerle yönetilemeyen bir karmaşıklığa yol açtı. Bu ihtiyaçları karşılamak için GraphQL geliştirilmeye başlandı.

2012–2015: Gelişim Aşamaları

Facebook, içeride kullanılmak üzere GraphQL’i geliştirdi ve bu süreçte özellikle performans ve esneklik konularına odaklandı. 2015 yılında ise GraphQL, geliştiricilerin kullanımına açıldı.

2015 Sonrası: Topluluk Katılımı

2018 yılında, Linux Vakfı’nın bir parçası olarak GraphQL Foundation kuruldu. Bu adım, GraphQL’in daha geniş bir topluluk ve endüstri desteği kazanmasına yardımcı oldu.

GraphQL, sadece Facebook’un değil, birçok büyük şirketin de ilgisini çekti. GitHub, Shopify, Twitter gibi birçok şirket, GraphQL’i kendi sistemlerinde kullanmaya başladı. GraphQL, günümüzde modern API geliştirmenin vazgeçilmez bir aracı haline gelmiştir.

Bölüm 2: REST API ve GraphQL Karşılaştırması

Her iki teknolojinin temel özelliklerini inceledikten sonra, REST API ve GraphQL'in avantajları ve dezavantajlarına odaklanarak karşılaştırmaya başlayabiliriz.

Temel Prensipler:

  • Durumsuzluk (Statelessness) :
    REST, her isteğin önceki isteğin durumunu bilmemesini savunur, bu da ölçeklenebilirlik ve bakım kolaylığı sağlar.
  • Kaynaklar ve Temsil Durumu (Resources and Representation State) :
    REST, kaynak odaklıdır ve her kaynak tekil bir URI ile tanımlanır. İstemci, bu kaynakları sorgulayarak veya değiştirerek işlem yapar.
  • Temel İşlemler (CRUD) :
    REST, temel CRUD işlemlerini destekler ve HTTP metodları (GET, POST, PUT, DELETE) bu işlemleri temsil eder.
  • Temel Medya Türleri (Media Types) :
    REST, farklı temsil durumlarını ifade etmek için farklı medya türlerini kullanır, örneğin, JSON veya XML formatları.

REST API Avantajları:

  • Bağımsızlık ve Uygulanabilirlik :
    REST, dil bağımsızdır ve farklı platformlarda (örneğin, web tarayıcıları, mobil cihazlar) kullanılabilir. Bu özellik, uygulama geliştirmeyi ve entegrasyonu kolaylaştırarak esnekliği artırır.
  • Ölçeklenebilirlik :
    RESTful servisler, durumsuz olmaları sayesinde ölçeklenebilirlik avantajı sunar. Her istek birbirinden bağımsızdır, bu da büyük ölçekli sistemlerin sorunsuz çalışmasını sağlar.
  • İnsanlar Tarafından Okunabilir :
    RESTful API’ler, URL’lerin ve HTTP metotlarının anlaşılabilir olması sayesinde insanlar tarafından da kolayca okunabilir ve anlaşılabilir. Bu özellik, belgelendirme süreçlerini ve geliştirici iletişimini kolaylaştırır.
  • Geniş Kullanım Desteği :
    REST, HTTP standartlarına dayandığı için, mevcut altyapıların (web tarayıcıları, HTTP protokolünü destekleyen cihazlar) kullanımına uygun olarak geliştirilmiştir. Bu da yaygın kullanım ve adaptasyon için avantaj sağlar.
  • Caching Desteği :
    REST, HTTP protokolünün sağladığı önbellekleme mekanizmalarını kullanarak performansı artırabilir. Önbellek, sık kullanılan kaynakları depolayarak tekrarlı istekleri azaltır, böylece sistem verimliliğini optimize eder.
  • Geniş Topluluk Desteği :
    REST, uzun yıllardır kullanılan bir yaklaşım olduğu için geniş bir geliştirici topluluğu tarafından desteklenir. Bu durum, belgelerin ve kaynakların bolca bulunabilirliği anlamına gelir, yeni geliştiricilerin hızla bilgi edinmelerine yardımcı olur.

Dezavantajlar:

  • Durum Bilgisi Tutulamaz (Statelessness) :
    REST mimarisi durum bilgisini saklamaz. Bu durum, bazı senaryolarda ek durum yönetimi gereksinimine ve performans sorunlarına yol açabilir.
  • Overfetching ve Underfetching Sorunları :
    Overfetching, istemcinin ihtiyacından fazla veri almasına neden olurken; underfetching, istemcinin ihtiyacından az veri almasına sebep olabilir. RESTful servislerde bu sorunlar, gereksiz veri transferine veya eksik veri alımına yol açabilir.
  • Güvenlik Zorlukları :
    REST, bazı güvenlik konularını varsayılan olarak ele almaz. Oturum yönetimi ve yetkilendirme gibi konularda ek önlemler alınması gerekebilir.
  • Esnek Olmayan Veri Yapısı :
    RESTful servislerde, veri yapısının sık sık değişmesi mümkün değildir. Sunucu tarafındaki değişikliklerin istemciyi etkileyebilmesi için belirli durumlar dışında, esnek bir veri yapılarına sahip olunamaz.
  • Belirsiz Standartlar :
    REST, belirli bir standarda bağlı olarak geliştirilmiş olsa da, bu standart bazı durumlarda belirsiz olabilir. Özellikle büyük ve karmaşık sistemlerde, API tasarımında standartlaşmış bir yaklaşımın eksikliği ortaya çıkabilir.
  • GraphQL’e Göre Veri Talep Esnekliği :
    RESTful servisler, her kaynağın farklı endpoint’lere sahip olması nedeniyle istemcinin belirli bir kaynağa ulaşabilmesi için çok sayıda istek yapmasını gerektirebilir. Bu durum özellikle mobil uygulamalarda ve düşük bant genişliğine sahip ortamlarda performans sorunlarına yol açabilir.
  • Dokümantasyon ve Keşif Zorlukları :
    RESTful servislerin doğasında belgeleme ve API keşfi zor olabilir. İstemciler, hangi kaynağa ve endpoint’e hangi parametrelerle ulaşmaları gerektiğini öğrenmek için dokümantasyona ihtiyaç duyarlar.

REST API’nin bu dezavantajları, özellikle büyük ve karmaşık sistemlerde bazı sınırlamalar ve kullanım zorlukları oluşturur.

Şimdi de GraphQL için temel prensiplere, avantaj ve dezavantajlara bakalım.

Temel Prensipler :

  • Esnek Veri Sorgulama :
    GraphQL, istemcilere ihtiyaçlarına uygun şekilde veri sorgulama esnekliği sunar. İstemciler, istedikleri alanları ve veri türlerini belirterek, gereksiz veri transferini engelleyebilirler.
  • Tek Endpoint :
    GraphQL, sadece tek bir endpoint üzerinden çalışır. Bu, birkaç farklı REST endpoint’i yerine tek bir endpoint ile tüm veri taleplerini karşılamayı mümkün kılar. Bu da API’nin daha tutarlı ve basit olmasını sağlar.
  • Şema (Schema) Tanımı :
    GraphQL, bir şema (schema) tanımına dayanır. Şema, API’nin hangi verileri ve operasyonları desteklediğini belirler. Geliştiricilere API’nin kullanımı konusunda net bir rehberlik sağlar.
  • İstek ve Yanıt :
    GraphQL’de istek ve yanıt aynı yapıdadır. İstemci, sadece istediği veriyi belirterek API’den isteğini gerçekleştirir. Bu, veri transferini optimize eder ve istemcinin ihtiyaç duyduğu veriyi almasını kolaylaştırır.
  • İlişkisel Veritabanları ile Uyum :
    GraphQL, ilişkisel veritabanları ve diğer veri kaynakları ile uyumlu bir şekilde çalışabilir. Veri modelini değiştirmeksizin, istemcilerin ihtiyaçlarına göre veri çekmelerine olanak tanır.

Avantajlar :

  • Esnek Veri Talebi :
    İstemciler, sadece ihtiyaçları olan veriyi alabilirler. Bu, gereksiz veri transferini önler ve istemcilere daha iyi performans sağlar.
  • Tek Endpoint ve Kolay Bakım :
    GraphQL, tek bir endpoint kullanarak tüm veri taleplerini karşılar. Bu, API'nin daha tutarlı ve basit olmasını sağlar.
  • Hızlı Geliştirme :
    GraphQL, geliştiricilere hızlı bir şekilde prototip oluşturma ve yeni özellikleri entegre etme esnekliği sağlar.
  • Mobil Uygulama Performansı :
    Mobil uygulamalarda, sadece istenilen verinin alınması sayesinde ağ trafiği ve veri transferi azalır, bu da performans artışına katkıda bulunur.
  • Dokümantasyon ve Keşif Kolaylığı :
    GraphQL, içsel dokümantasyon ve keşif için güçlü bir zemin sunar. Geliştiriciler, GraphQL üzerinden mevcut şemayı sorgulayarak API’nin kullanımını öğrenebilirler.

Dezavantajlar :

  • Eğitim ve Adaptasyon Zorlukları:
    GraphQL’in öğrenilmesi ve uygulanması, özellikle yeni başlayanlar için bir öğrenme eğrisi oluşturabilir.
  • Gereksiz Derinlik Problemi:
    İstemciler, istedikleri veriyi alabilmek için gereksiz derinlikte sorgular yapabilirler, bu da performans sorunlarına yol açabilir.
  • Bağlam Değişiklikleri :
    GraphQL, veri yapısındaki değişikliklerin istemciyi etkileyebileceği bir yapıya sahiptir. Bu, sıklıkla güncellenen istemciler için uygunsuz olabilir.
  • Cache Yönetimi:
    GraphQL, önbellek yönetimini otomatik olarak sağlamaz, bu nedenle istemcilerin gereksiz veri transferini önleme konusunda daha fazla sorumluluk alması gerekir.
  • Sorgu Güvenliği :
    Özellikle genel API’lerde, kötü niyetli kullanıcıların aşırı veri talepleri yapmasını önlemek için sorgu güvenliği konusunda ek önlemler almak gerekebilir.

Genel olarak incelediğimiz zaman her iki teknolojinin de avantaj ve dezavantajları mevcut. Burada önemli nokta uygulamanın ve geliştirme ekibinin durumuna göre gerekli olan teknolojinin seçilmesidir.

Biz iki teknolojinin arasındaki farkları incelemeye devam edelim.

GraphQL ve RESTful API’lar arasındaki temel farklardan biri, veri talep yönetimi ve esneklik konularında ortaya çıkar. Bu iki yaklaşım arasındaki mimari farklara beraber bakalım.

GraphQL’de Esnek Veri Talebi :

  • Tek Endpoint ve Alan (Field) Seçimi :
    GraphQL, tek bir endpoint kullanır ve istemciler, sadece ihtiyaç duydukları alanları seçerek veri talebinde bulunabilirler. Bu özellik, gereksiz veri transferini önler ve istemcinin yalnızca istediği veriyi almasını mümkün kılar. Tek bir endpoint kullanımı, API’nin daha tutarlı ve basit olmasını sağlar, ayrıca istemcilerin farklı veri taleplerini aynı endpoint üzerinden gerçekleştirmelerine olanak tanır.
  • Nested Fields (İç İçe Alanlar) :
    GraphQL, istemcilere iç içe alanları (nested fields) kullanma esnekliği tanır. İstemci, bir kaynağın içindeki belirli alanları talep edebilir ve bu alanlar da içinde başka alanları içerebilir. Bu özellik, istemcilere yalnızca ihtiyaç duydukları veriyi alabilme ve gereksiz veri transferini önleme imkanı sağlar. İstemciler, istedikleri veriyi hiyerarşik bir şekilde belirleyerek, API’den daha özelleştirilmiş ve etkili yanıtlar alabilirler.
  • Fragment Kullanımı :
    Fragmentlar, GraphQL’de tekrar kullanılabilir alan tanımlarıdır. İstemciler, fragmentları kullanarak farklı sorgularda aynı alanları defalarca tanımlamak zorunda kalmazlar. Bu, sorguların daha temiz, düzenli ve bakımı kolay hale gelmesini sağlar. Ayrıca, fragmentlar, özellikle büyük ve karmaşık sorgularda, istemcilere daha etkili bir veri alışverişi deneyimi sunar.
  • Değişkenler ve Parametreler :
    GraphQL, sorgulara değişkenler ve parametreler eklemek için esnek bir yapı sunar. Bu özellik, aynı sorguyu farklı durumlar için kullanmayı kolaylaştırır. İstemciler, sorgularını dinamikleştirebilir ve farklı parametre değerleri ile aynı sorguyu kullanarak çeşitli senaryolara uyum sağlayabilirler. Bu da API ile etkileşimde daha fazla kontrol ve özelleştirme imkanı sunar.

RESTful API’de Sınırlı Veri Talebi :

  • Birden Çok Endpoint :
    RESTful API’lar genellikle birden çok endpoint kullanır ve her bir endpoint belirli bir veri kümesini temsil eder. İstemciler, ihtiyaçlarına göre farklı endpoint’lere giderek veri talebinde bulunmak zorundadırlar. Bu durum, istemcilerin farklı veri kaynaklarına ulaşmak için birden fazla HTTP isteği göndermelerine neden olabilir ve bu da gereksiz veri transferine yol açabilir.
  • Sabit Alanlar :
    RESTful API’lar genellikle sabit alanlar (fixed fields) sunar. İstemciler, belirli bir kaynağın tüm alanlarını almak zorunda kalabilirler, bu da gereksiz veri transferine neden olabilir.
  • Birden Fazla İstek :
    İstemciler, belirli bir veri kümesini almak için birden fazla REST endpoint’ine istek göndermek zorunda kalabilirler. Bu durum, ağ trafiğini artırabilir.
  • Veri Talebi İçin Ekstra İsteğe İhtiyaç :
    RESTful API’lar genellikle bir kaynağın belirli bir alt kümesini almak için ekstra parametreler veya endpoint’ler kullanmazlar. İstemciler, genellikle tam veri setini almak zorundadırlar.

Bölüm 3: GraphQL Dilinin Temelleri

Alanlar (Fields) :

Alanlar (Fields), bir sorgu veya mutasyon içinde talep edilen veri parçacıklarını temsil eden temel yapı taşlarıdır. Bir GraphQL şemasında tanımlanan alanlar, belirli bir veri tipine ait özellikleri ifade eder. GraphQL’de bir istemci, bir sorgu gönderirken sadece ihtiyaç duyduğu alanları talep edebilir. Bu, aşırı veya eksik veri transferini önler ve istemcinin tam olarak istediği veriyi almasına olanak tanır.

query {
users{
id
login
avatar_url
}
}

İstenilirse alanlar iç içe de kullanılabilir.

query {
getUser(id: "123") {
name
posts {
title
content
}
}
}

Değişkenler (Variables) :

Değişkenler (Variables), sorgulara veya mutasyonlara dinamik değerler eklemek için kullanılan bir mekanizmadır. Değişkenler, sorguların ve mutasyonların daha geniş bir yelpazede kullanılmasını sağlar ve aynı sorguyu farklı değerlerle tekrar kullanmak için kullanışlıdır.

GraphQL sorgularında değişken tanımlamak için genellikle bir anahtar kelime olan $ işareti kullanılır. Değişken tanımlanırken, değişkenin adı, tipi ve varsayılan bir değer (opsiyonel) belirtilebilir..

Yönergeler (Directives) :

Yönergeler (Directives), sorguların ve mutasyonların davranışını kontrol etmek için kullanılan bir yapıdır. Yönergeler, sorgu veya mutasyonun belirli koşullara bağlı olarak nasıl işlem göreceğini belirleme esnekliği sağlar.

Kullanılan iki önemli yönerge bulunmaktadır. Bunlar;

  1. @include(if: Boolean) : Doğru (true) değeri gelmesi durumunda alanı dahil eder.
  2. @skip(if: Boolean) : Doğru (true) değer gelmesi durumunda alanı dahil etmeden geçer.

Takma Adlar (Aliases) :

Takma adlar (Aliases), sorgulama işlemi sırasında alana verilen ismi değiştirmek için kullanılan bir yapıdır. Alias’lar, özellikle aynı alanın farklı adlar altında birden çok kez talep edilmesi durumunda veya sorguların daha anlaşılır hale getirilmesi amacıyla kullanılır.

Alias’lar aynı zamanda GraphQL sunucularının sorguları optimize etmesine de yardımcı olabilir. Birden çok kez aynı alanı talep eden sorgular, sunucunun gereksiz hesaplamaları önlemesine ve daha etkili bir şekilde veri sağlamasına olanak tanır.

Parçalar (Fragments) :

Parçalar (Fragments), sorgular içinde tekrar kullanılabilir ve modüler alan tanımlarıdır. Fragmentlar, belirli bir veri tipine ait alanları tanımlamak ve bu alanları farklı sorgularda tekrar kullanmak için kullanılır. Bu, sorguların daha düzenli, okunabilir ve bakımı kolay hale gelmesini sağlar.

Mutasyonlar (Mutations) :

Mutasyonlar (Mutations), veri değişikliklerini gerçekleştirmek için kullanılan işlemleri temsil eder. Genellikle veri eklemek, güncellemek veya silmek için kullanılırlar. Mutasyonlar, sorgular gibi birinci sınıf nesnelerdir ve belirli bir mutasyonun sonucunu talep etmek mümkündür.

Mutasyonlar, veri değişiklikleri için kullanıldığından, genellikle CRUD (Create, Read, Update, Delete) işlemlerini temsil ederler. Bu sayede, bir GraphQL API’si üzerinden veritabanındaki bilgileri değiştirme ve güncelleme işlemleri gerçekleştirilebilir.

Bölüm 4: GraphQL ile ASP.NET Core Web API

Temel bilgilerin ve karşılaştırmaların üzerinden geçtiğimize göre biraz da GraphQL’in nasıl kullanıldığına bakalım. Burada amacımız bir Asp.net Core Web Api oluşturarak GraphQL entegrasyonunu yapmak.

Senaryomuzda yazar ve kitap bilgilerini yöneten bir api geliştireceğiz. Bu api üzerinden kullanıcılar yazar bilgileri, yazarlar ile ilgili kitap bilgilerine GraphQL kullanarak erişeceklerdi.

Bir DB bağlantısı oluşturmadan amacına yönelik bir api geliştireceğimiz için bilgileri in-memory olarak tutacağız. Projemizi oluşturmakla başlayabiliriz.

İlk önce “AuthorsAndBooksGraphQL” isminde bir ASP.NET Core Web Api projesi oluşturuyoruz.

Models isminde bir klasör oluşturarak aşağıdaki sınıfları ekleyelim.

namespace AuthorsAndBooksGraphQL.Models;

public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public List<Book> Books { get; set; }
}
namespace AuthorsAndBooksGraphQL.Models;

public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
}

Şimdi de Repository ve Service sınıflarımızı oluşturalım.

Abstractions isminde bir klasör açarak içerisine Repositories ve Services klasörlerini ekliyoruz.

Repositories klasörünün içerisine IGenericRepository.cs ara yüzümüzü ve diğer repolar için kullanacağımız ara yüzeri ekliyoruz. Yine Services klasörüne ise IAuthorService.cs ve IBookService.cs ara yüzlerimizi ekliyoruz.

namespace AuthorsAndBooksGraphQL.Abstractions.Repositories;

public interface IGenericRepository<T> where T : class
{
Task<List<T>> GetAllAsync();
Task<T> GetByIdAsync(int id);
Task<T> AddAsync(T entity);
}
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Abstractions.Repositories;

public interface IAuthorRepository: IGenericRepository<Author>
{

}
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Abstractions.Repositories;

public interface IBookRepository: IGenericRepository<Book>
{

}
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Abstractions.Services;

public interface IAuthorService
{
Task<List<Author>> GetAllAuthorsAsync();
Task<Author> GetAuthorByIdAsync(int id);
Task<Author> AddAuthorAsync(Author entity);
}
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Abstractions.Services;

public interface IBookService
{
Task<Book> AddBookAsync(Book entity);
Task<List<Book>> GetAllBooksAsync();
Task<List<Book>> GetAllBooksByAuthorAsync(int authorId);
Task<Book> GetBookByIdAsync(int id);
}

Son durumda projemizin yapısı aşağıdaki gibi olacaktır.

Şimdi Repositories ve Services isminde iki klasör daha açarak önce Respository sonra da Service sınıflarımızı ekleyelim.

using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Repositories;

public class AuthorRepository : IAuthorRepository
{
private List<Author> _authors;

public AuthorRepository()
{
_authors = new List<Author>
{
new() {
Id = 1,
Name = "Stephen Edwin",
LastName = "King"
},
new() {
Id = 1,
Name = "John Ronald Reuel",
LastName = "Tolkien"
},
};
}

public Task<Author> AddAsync(Author entity)
{
entity.Id = _authors.Count + 1;
_authors.Add(entity);

return Task.FromResult(entity);
}

public Task<List<Author>> GetAllAsync()
{
return Task.FromResult(_authors);
}

public Task<Author> GetByIdAsync(int id)
{
return Task.FromResult(_authors.Find(a => a.Id == id));
}
}
using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Repositories;

public class BookRepository : IBookRepository
{
private List<Book> _books;

public BookRepository()
{
_books = new List<Book>
{
new() { Id = 1, Title = "The Shining (Medyum)", AuthorId = 1 },
new() { Id = 2, Title = "Doctor Sleep (Doktor Uyku)", AuthorId = 1 },
new() { Id = 3, Title = "Hobbit", AuthorId = 2 },
new() { Id = 4, Title = "Lord Of The Rings (Yüzüklerin Efendisi)", AuthorId = 2 }
};
}

public Task<Book> AddAsync(Book entity)
{
entity.Id = _books.Count + 1;
_books.Add(entity);

return Task.FromResult(entity);
}

public Task<List<Book>> GetAllAsync()
{
return Task.FromResult(_books);
}

public Task<Book> GetByIdAsync(int id)
{
return Task.FromResult(_books.Find(a => a.Id == id));
}
}

Servis sınıflarımız da aşağıdaki gibi olacaklar.

using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Services;

public class AuthorService : IAuthorService
{
readonly IAuthorRepository _authorRepository;

public AuthorService(IAuthorRepository authorRespository)
{
_authorRepository = authorRespository;
}

public async Task<Author> AddAuthorAsync(Author entity)
{
return await _authorRepository.AddAsync(entity);
}

public async Task<List<Author>> GetAllAuthorsAsync()
{
return await _authorRepository.GetAllAsync();
}

public async Task<Author> GetAuthorByIdAsync(int id)
{
return await _authorRepository.GetByIdAsync(id);
}
}
using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;

namespace AuthorsAndBooksGraphQL.Services;

public class BookService : IBookService
{
readonly IBookRepository _bookRepository;

public BookService(IBookRepository bookRepository)
{
_bookRepository = bookRepository;
}
public async Task<Book> AddBookAsync(Book entity)
{
return await _bookRepository.AddAsync(entity);
}

public async Task<List<Book>> GetAllBooksAsync()
{
return await _bookRepository.GetAllAsync();
}

public async Task<List<Book>> GetAllBooksByAuthorAsync(int authorId)
{
return (await _bookRepository.GetAllAsync())
.Where(p=>p.AuthorId == authorId).ToList();
}

public async Task<Book> GetBookByIdAsync(int id)
{
return await _bookRepository.GetByIdAsync(id);
}
}

Şimdi de Contoller sınıflarımızı ekleyelim. BookController.cs ve AuthorController.cs sınıflarımız da aşağıdaki gibi olacaklar.

using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;
using Microsoft.AspNetCore.Mvc;

namespace AuthorsAndBooksGraphQL.Controllers;

[Route("api/[controller]")]
[ApiController]
public class BookController : ControllerBase
{
readonly IBookService _bookService;

public BookController(IBookService bookService)
{
_bookService = bookService;
}

[HttpGet("all-books")]
public async Task<IActionResult> GetAllBooksAsync()
{
return Ok(await _bookService.GetAllBooksAsync());
}

[HttpGet("book-by-id/{id}")]
public async Task<IActionResult> GetBookByIdAsync(int id)
{
return Ok(await _bookService.GetBookByIdAsync(id));
}

[HttpPost("add-book")]
public async Task<IActionResult> AddBookAsync(Book bookDto)
{
return Ok(await _bookService.AddBookAsync(bookDto));
}
}
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;
using Microsoft.AspNetCore.Mvc;

namespace AuthorsAndBooksGraphQL.Controllers;

[Route("api/[controller]")]
[ApiController]
public class AuthorController : ControllerBase
{
readonly IAuthorService _authorService;

public AuthorController(IAuthorService authorService)
{
_authorService = authorService;
}

[HttpGet("all-authors")]
public async Task<IActionResult> GetAllAuthorsAsync()
{
return Ok(await _authorService.GetAllAuthorsAsync());
}

[HttpGet("author-by-id/{id}")]
public async Task<IActionResult> GetAuthorByIdAsync(int id)
{
return Ok(await _authorService.GetAuthorByIdAsync(id));
}

[HttpPost("add-author")]
public async Task<IActionResult> AddAuthorAsync(Author authorDto)
{
return Ok(await _authorService.AddAuthorAsync(authorDto));
}
}

Şimdi de Program.cs dosyasındaki güncellemelerimizi yapalım. Burada oluşturduğumuz Repository ve Service sınıflarını tanımlayacağız.

using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Repositories;
using AuthorsAndBooksGraphQL.Services;

namespace AuthorsAndBooksGraphQL;

public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IAuthorService, AuthorService>();
builder.Services.AddScoped<IBookService, BookService>();
builder.Services.AddScoped<IAuthorRepository, AuthorRepository>();
builder.Services.AddScoped<IBookRepository, BookRepository>();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseAuthorization();


app.MapControllers();

app.Run();
}
}

Uygulamamızın son hali aşağıdaki gibi olacaktır.

Uygulamamızı test ettiğimizde Swagger üzerinden son durum aşağıdaki gibi olacaktır.

Şimdi GraphQL yapısının uygulamamıza uyarlama aşamasına geçebiliriz.

İlk önce GraphQL paketlerimizi ekleyerek başlayalım. ASP.NET Core için farklı paketler kullanabilirsiniz bu örnek için aşağıdaki paketleri kullanacağız. Projemize eklemiş olduğumuz paketler aşağıdaki gibi olacaktır.

<PackageReference Include="GraphQL" Version="7.7.2" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="7.7.2" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.6.0" />
<PackageReference Include="GraphQL.Server.Ui.Altair" Version="7.6.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.7.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

Yapacağımız işlemler aşağıdaki gibi olacak.

GraphQL Türlerinin Oluşturulması.
Burada GraphQL tarafından uygulamamız içindeki tür bilgilerinin kullanılması için tür sınıflarımızı oluşturacağız. GraphQL.NET bu konuda bizlere kendi türlerimizi düzenlemek için sınıflar sunmaktadır.

Genel olarak GraphQL.NET içerisinde kullanılan tür bilgileri aşağıdaki gibidir.

ObjectGraph<T> : GraphQL türlerini tanımlamak için kullanılır.

ListGraphy<T> : GraphQL içinde tanımlanacak olan liste türleri için kullanılır.

ComplexGraphType<T> : Daha önce hazırladığımız GraphQL türlerini kullanarak oluşturabileceğimiz yeni kompleks türler için kullanılmaktadır.

EnumerationGraphyType : GraphQL’in enum türlerini tanımlamak için kullanılmaktadır.

GraphQL Sorgu Nesnesinin Oluşturulması.
Sorgu nesneleri bizim hazırladığımız sorgu tanımlarının GraphQL üzerinden yayınlanarak, kullanıcıların bu sorguları kullanması için eklenecektir.

GraphQL Şemasının Oluşturulması.
Şema nesneleri GraphQL yapısı içerisinde kullanacağımız tür, sorgu, mutasyon ve aboneliklerimizi yayınlayabilmek için kullanacağımız yapıyı oluşturacaktır.

Şimdi, Schema isminde bir klasör oluşturarak GraphQL için gerekli olan varlıklarımızı oluşturalım. İlk olarak tür bilgilerimizi oluşturacağız. Bunun için Schema klasörü altında Types isminde bir klasör oluşturarak AuthorType.cs ve BookType.cs adında iki adet sınıf oluşturup ve aşağıdaki gibi düzenleyelim.

using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;
using GraphQL.Types;

namespace AuthorsAndBooksGraphQL.Schema.Types;

public class AuthorType : ObjectGraphType<Author>
{
public AuthorType(IBookService bookService)
{
Field(x => x.Id).Description("Yazar Kayıt No");
Field(x => x.Name).Description("Yazar Adı");
Field(x => x.LastName).Description("Yazar Soyadı");
Field(x => x.Books, type: typeof(ListGraphType<BookType>))
.Description("Yazarın Kitapları")
.ResolveAsync(async context => await bookService.GetAllBooksByAuthorAsync(context.Source.Id));
}
}
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Models;
using GraphQL.Types;

namespace AuthorsAndBooksGraphQL.Schema.Types;

public class BookType : ObjectGraphType<Book>
{
public BookType(IAuthorService authorService)
{
Field(x => x.Id).Description("Kitap Kayıt No");
Field(x => x.Title).Description("Kitap Başlık");
Field(x => x.AuthorId).Description("Yazar Kayıt No");
Field<AuthorType>("author")
.Description("Yazar")
.ResolveAsync(async context => await authorService.GetAuthorByIdAsync(context.Source.AuthorId));
}
}

Ardından sorgularımızı oluşturacağız. Bunun için de Schema klasörünün altında Query isminde bir klasör oluşturarak Query.cs isminde bir sınıf oluşturuyoruz ve aşağıdaki gibi düzenliyoruz.

using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Schema.Types;
using GraphQL;
using GraphQL.Types;

namespace AuthorsAndBooksGraphQL.Schema.Queries;

public class Query : ObjectGraphType<object>
{
public Query(IAuthorService authorService, IBookService bookService)
{
Name = "AuthorsAndBooksQuery";

Field<ListGraphType<AuthorType>>("authors").ResolveAsync(async context => await authorService.GetAllAuthorsAsync());

Field<AuthorType>("author").Arguments(
new QueryArguments(
new QueryArgument<NonNullGraphType<IntGraphType>> { Name = "id" }))
.ResolveAsync(async context => await authorService.GetAuthorByIdAsync(context.GetArgument<int>("id")));

Field<ListGraphType<BookType>>("books").ResolveAsync(async context => await bookService.GetAllBooksAsync());

Field<AuthorType>("book").Arguments(
new QueryArguments(
new QueryArgument<NonNullGraphType<IntGraphType>> { Name = "id" }))
.ResolveAsync(async context => await bookService.GetBookByIdAsync(context.GetArgument<int>("id")));
}
}

Koddan da anlaşılacağı gibi sorgumuza ilk olarak biri isim tanımladık. Ardından sorgu içinde olacak alan bilgilerini alanlar ile birlikte tanımlamamız gerekiyor.

Her alan tanımına da bir isim ve bilgileri nasıl ve nereden alacağını belirtiyoruz. burada “authors” isimli alan bilgisi authorService içerisindeki GetAllAuthorsAsync metodunun kullanarak bilgileri alacak ve GraphQL de bizim istediğimiz alanlara göre sorgu sonucu bize verecek. Parametre alarak çalışacak olan alanlarımız da kod içerisinde mevcut. Burada da Arguments tanımlarını yaparak alnıacak olan parametre bilgisini ve yine alınan parametrenin bilgileri getireceği metoda geçişini kod üzerinden görebilirsiniz.

Diğer alan tanımlarımızı da yaptıktan sonra şema tanımımızı yapabiliriz.

AuthorsSchema.cs isimli sınıfımızı tanımlayalım. Sınıfımızı Schema isimli klasörün içinde oluşturuyoruz.

using AuthorsAndBooksGraphQL.Schema.Queries;
using GraphQL.Types;

namespace AuthorsAndBooksGraphQL.Schema;

public class AuthorsSchema : GraphQL.Types.Schema
{
public AuthorsSchema(IServiceProvider serviceProvider) : base(serviceProvider)
{
Description = "Author And Book Schema";
Query = serviceProvider.GetRequiredService<Query>();
}
}

Şema tanımı içerisinde ise isimlendirmeden ayrı olarak çalışacak olan sorgu nesnemizi tanımlıyoruz.

Şimdi de Program.cs sınıfımızda GraphQL ve şema eklemelerimizi yaparak son duruma getiriyoruz.


using AuthorsAndBooksGraphQL.Abstractions.Repositories;
using AuthorsAndBooksGraphQL.Abstractions.Services;
using AuthorsAndBooksGraphQL.Repositories;
using AuthorsAndBooksGraphQL.Schema;
using AuthorsAndBooksGraphQL.Services;
using GraphQL;
using GraphQL.MicrosoftDI;
using GraphQL.Types;

namespace AuthorsAndBooksGraphQL;

public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddScoped<IAuthorService, AuthorService>();
builder.Services.AddScoped<IBookService, BookService>();
builder.Services.AddScoped<IAuthorRepository, AuthorRepository>();
builder.Services.AddScoped<IBookRepository, BookRepository>();

builder.Services.AddScoped<ISchema, AuthorsSchema>(services => new AuthorsSchema(new SelfActivatingServiceProvider(services)));

builder.Services.AddGraphQL(options => options.ConfigureExecution((opt, next) =>
{
opt.EnableMetrics = true;
return next(opt);
}).AddSystemTextJson());


builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseGraphQLAltair();
}

app.UseAuthorization();

app.UseGraphQL<ISchema>();

app.MapControllers();

app.Run();
}
}

AuthorsSchema nesnemizi Services içerisine Scoped olarak ekledik. Burada önemli kısımlardan bir tanesi app.UseGraphQLAltair(); satırı ile yaptığımız tanım. Bu tanım uygulamamız içinde bir Altair playground oluşturmamızı sağlayacak ve GraphQL sorgularımızı bu arayüz üzerinden test edebileceğiz.

Bu test uygulamasına ulaşmak için uygulamamız çalıştıktan sonra http://localhost:port/ui/altair adresini kullanacağız.

Son güncellemelerimizi de yaptıktan sonra artık uygulamamızı çalıştırabiliriz ve yukarıdaki adres ile Altair ara yüzünü açabiliriz.

Uygulamamız açıldıktan ve belirttiğim adresi açtıktan sonra aşağıdaki şekilde bir ekran ile karşılaşacaksınız.

Üst kısımda sorguların alınacağı adresi görebilirsiniz. Altair aracı ile bu adres üzerinden sorgulamalarımızı yapabileceğiz. Sağ kısımda bulunan Docs bağlantısını açtığımız zaman kod tarafında tanımladığımız sorguları görebilirsiniz.

Sorguların sağ tarafında bulunan AddQuery butonu ile uygun olan sorguyu açabiliriz. Örnek bir sorgumuz aşağıdaki gibi olacak.

{
authors {
id
name
lastName
books {
id
title
authorId
}
}
}

Send Request butonu ile sorgumuz çalıştırdığımız zaman gelen sonuçları da Result penceresinde görebiliriz.

Query penceresinde istediğimiz alanları güncelleyerek sorgunun nasıl cevap döndüğü konusunda bilgi sahibi olabilirsiniz. Aşağıdaki sorgu ile de istediğimiz bir Id ile o Id’ye sahip yazar bilgisini ve kitaplarının getirme imkanına sahibiz.

{
author(id: 1) {
id
name
lastName
books {
id
title
authorId
}
}
}

Evet, bu yazıda temel olarak GraphQL nedir? Nasıl kullanılır? gibi sorulara yanıt bulmaya çalıştık. Umarım sizler için yararlı bir yazı olmuştur.

Yazı sırasında oluşturduğumuz projeye aşağıdaki bağlantıdan ulaşabilirsiniz.

Bir sonraki yazıda görüşmek üzere.

--

--