GraphQL nedir? Ne işe yarar? Rest Api’den farkı nedir?

GraphQL nedir?

API sorgulama dilidir.
Facebook, Github, Pinterest, Shopify v.b yoğun istemci yükü olan uygulamaların kullandığı bir dildir.

Ne işe yarar?

GraphQL projesinde bir şema belirlenir. İstemci bu şemaya bağlı kalarak sorgularını yönetir.

  • İstemci sadece kendisine gerekli olan verileri alır.
  • Sayfa oluşum hızına büyük katkı sağlar.
  • İlişkili datalar arasında dilediğinde kendisi bağlayıcı rolü olabilir.
  • RestAPI’lere tek bir endpoint ile erişim sağlar.

Örn :
Bir listeleme işlemi yaptığımızı düşünelim. “Order” diye bir tablomuz, bu tabloya bağlı 25 adet property var. Biz sipariş listesi sayfasımız için REST API ile “order/getlist” adında bir servis yazdık ve sayfaya bu verileri gönderebiliyoruz.

Gösterim yaparken sadece “id, totalPrice” versini gösteriyoruz diyelim. Diğer propertylerin sayfada her hangi bir işlevi yokken onların servisten bana json olarak dönmesi benim sayfa render hızımı etkileyecektir.

Bu yüzden GraphQL sorgusu ile sadece “id,totalPrice” değerlerinin servisten gelmesini sağlayabilirim. Bunu örneklerimizde beraber göreceğiz.

Rest Api’den farkı nedir?

Rest Api vs GraphQL

Rest API’den farkını özetleyen güzel bir görsel buldum. Aslında bu bir çok şeyi özetliyor.

İstemci farklı servislerde yapacağı işlemlerin için endpoint değişikliği yapmak zorunda kalacak. Aslında servis yönetimi yapmak zorunda kalacaktır.

GraphQL istemciye tek bir endpoint vererek bu durumu ortadan kaldırıp bir Gateway görevi görür. Yani istemci “players” arkasında ne var, “teams” hangi servise gidecekti gibi konularla hiç uğraşmadan sorgusunu atarak geliştirmelerini yapar.

Örnek olması açısından .net core ile GraphQL geliştirmesi yaptım.
Bu projede GraphQL dışında IoC(Castle Windsor)’da kullandım. Bunu da tecrübe etmiş olacağız.

Projeyi açtığınızda “Models, Container, Services, Types, Resolvers ve Servers” göreceksiniz. Bunları sırasıyla detaylıca anlatmadan önce GraphQL şema yapısından bahsetmek istiyorum.

GraphQL Schema

GraphQL şeması içerisinde “Types, Queries, Mutations” kullanacağız.
“Subscriptions” web socket(chat v.b) uygulamalar için önem kazanır. Bunu şuan kullanmadığımız bir örnek yapacağız, ileri yazılarımızda değinebiliriz.

Types GraphQL kendi içerisinde kendi belirlediği tiplerde bir şema yapısının oluşturulmasını istiyor. Örneklerimizde en çok göreceğiniz tip “ObjectGraphType” olacaktır. C# üzerinde kullandığınız tüm nesneleri propertyleri GraphQL’in belirlediği tiplere göre tanımlamak zorundayız. Bunu da örneklerimizde göreceğiz.

Queries ise API üzerinde yaptığımız Get işlemleri diyebiliriz. Yani istemci bir listeleme ya da detay sayfası yapıyorsa “Queries” üzerinden sorgu atarak sayfasına verileri çekecektir.

Mutations ise API üzerinde yaptığımız Post/Put işlemleri diyebiliriz. Yani bir istemci kayıt ekleme/düzenleme sayfası yapıyorsa “Mutations” üzerinden sorgu atarak sayfasındaki veriyi API’ye gönderecektir.

Şimdi GitHub üzerinden indirdiğiniz projemiz üzerinden konuşalım.
.net core 3.1
GraphQL 3.3.2
Castle Windsor 5.1.1
GraphQL.Server.Ui.Playground 4.2.0

01.Models

01-Models

Projeyi direkt olarak GraphQL tipleri üzerinden yapmak yerine önlerine DtoModel koyarak katmanları daha anlaşılır hale getirmek istedim.

Projemizde “UserDto, OrderDto ve OrderDetailDto” olarak üç adet model üzerinde çalışacağız.

Burada dikkat edilmesi gereken “IScoppedDependency, ISingletonDependency”. Bizlere IoC kısmında yardımcı olacak. IScopedDependency’den türeyenler gelsin gibi kullanımlarımız olacak. Tam olarak ne işe yaradığını bir sonraki adımda daha detaylı inceleyeceğiz.

02.Container

02-Container

Dependency yönetimi için bir “Container” ihtiyacımız var. Ve bunun için “Castle Windsor” kullanacağız. GraphQL örneklerini incelediğinizde tiplerin, querylerin, mutationların tek tek Startup üzerinde tanımlandığını göreceksiniz.

Büyük bir projede bu yönetilmesi zor bir durum. O yüzden bir normal bir örnek dışında daha gerçekçi direkt olarak büyük projelerde kullanacağız bir yapıda tasarlayalım ve container kullanalım diye bu yapıyı tercih ettim.

Castle Windsor dışında başkası olmaz mı ? Tabi olabilir, bu yüzden bu katman ayrı bir şekilde tasarlandı. Buraya dilediğiniz container kütüphanesini kullanabilirsiniz.(AutoFac,Ninject v.b)

“DependencyInstaller” nesnesinin “IWindsorInstaller” adında bir interface implemente ettiğini göreceksiniz. Burada “IScopedDependency, ISingletonDependency”den türeyenleri container içerisinde kayıt eden bir satır göreceksiniz.

IScopedDependency’den türeyenler “Scoped”, ISignletonDependency’den türeyenlerde “Singletion” yaşam döngüsü ile kayıt edildiğini göreceksiniz.

IoCManager bize kayıt ettiğimiz nesneleri “Resolve” methodu ile belirttiğimiz interface’den somut nesnesi almamızı sağlayacak.

03.Services

03-Services

Servis katmanı için ben bir DB erişimi yada thirdparty bir servis kullanmadım.
Anlık RAM üzerinde veri saklayan bir DummyList ile datalarımı sakladım. Siz burada kendi servis yapınızı kurmalısınız.

Birden fazla Rest API olabilir, hepsinin burada tanımları yapılarak ilgili servislerin aksiyonları burada alınmalıdır. Bu servislerimizin içerisinde interface methodları da var. Biz bunları “IScopedDependency” olarak tanımlayarak IoCManager’dan resolve ederek işlemlerimizi yapacağız.

04.Types

04-Types

GraphQL Type kısmına geçiyoruz. Öncelikle “ISingletonGraphType” önemli, önceki adımlarda bahsettiğimiz “ISingletonDependency”den türetmemiz gerekli. Böylece oda container içerisinde kayıt olacak ve IoCManager’den bize resolve imkanı sunacaktır.

GraphQL örneklerinde göreceğiniz tiplerin tek tek Startup üzerinde kayıt edilmesini engelleyeceğimiz kısımda bize yardımcı olacak.

01.Models bölümünde tanımladığımız Dto nesnelerinden “ObjectGraphType” oluşturuyoruz. Bunların constructure kısmında Dto’dan hangi objeleri GraphQL şemasına ekleyeceğimize karar veriyoruz. Yani DTO’da olan her propertyi ben GraphQL’de göstermek zorunda değilim. Bunun kararını burada verebiliyorum.

Bu tanımlamaları yaparken zorunlu olmayan alanlara da karar veriyorum.

OrderGraphType

OrderGraphType’ı örnek aldığımızda OrderDto üzerinde bulunan tüm değerleri tanımladığımı göreceksiniz. “DiscountPrice” alanı boş geçilebilir yani null geçilebilir bir alan olduğu için “nullable:true” olarak işaretliyorum.

Oluşturduğum OrderGraphType : ObjectGraphType’dan miras alıyor ve generic tip kabul ediyor. Tip bölümünde DTO nesnem olan “OrderDto”yu veriyorum.

OrderDto, OrderDetailDto ve UserDto için sırasıyla bu işlemleri yapıyorum. Daha sonra bunların arasında olacak ilişkiyi kurmam gerekiyor. Bu nedir?

Include terimini özellikle EFCore kullananlar aşinadır. Order üzerinden User’a gitmek istediğimiz durumlar olabilir ya da Order’ın detayları olan OrderDetail listesi çekmek istediğimiz durumlar olabilir. Bunları istemci için şemamıza ekleyiyoruz. O dilerse bu include işlemlerini yaparak istediğini dataları gerektiğinde çekiyor.

Yine OrderGraphType’den gidelim.

OrderGraphType2

IoCManager üzerinde OrderDetailService ve UserService resolve ediyoruz.
OrderGraphType cağırıldığında “user” diyerek UserGraphType, “details” diyerek OrderDetailGraphType’a gitmesini currentContext üzerinden sağlıyoruz.

Details bir liste olduğunu ListGraphType kullanıyoruz, UserGraphType tek bir nesne geri döneceği için direkt olarak UserGraphType kullanıyoruz.

context.Source bize ilgili dto içerisinde tanımladığımız ve doldurduğumuz propertyleri kullanmamıza yardımcı oluyor. Yani Order çektiğimde id ve userId bilgisini direkt olarak kullanarak include yapmış oluyoruz.

Bir diğer kullandığımız GraphQL Type ise “InputObjectGraphType”.
API’lerde yaptığımız post işlemlerinde ne yaparız? Bir input model belirleriz ve post işleminde bu inputModel’i alarak Dto nesnemize mapleyerek kayıt işlemlerimizi yaparız.

GraphQL’de bu tiplere “InputObjectGraphType” deniyor. Bunun için “OrderInputGraphType” örnek alalım.

OrderInputGraphType

Sayfadan “OrderDto” olarak kayıt/güncelleme aksiyonuna parametreyi göndereceğim. “OrderDto” yerine farklı bir input modelde ekleyebilirim.
Bu örnekte bulunan “OrderInputGraphType” sadece insert aksiyonuna hizmet veriyor. O yüzden “OrderDto”da bulunan id değerini burada tanımlamıyorum. Böylece insert işlemi sırasında sadece “userId, discountPrice ve totalPrice” değerlerini alacağımı belirtmiş oluyorum. Aynı zamanda “discountPrice” zorunlu bir alan olmadığı için “nullable:true” olarak belirtiyorum.

05.Resolvers

05-Resolvers

İkiside bir objectGraphType olan query ve mutation nesnelerimizi burada tanımlayacağız.
GraphQLSchema bizim ana şemamız bunun içerisine Query ve Mutation graphType’larını vereceğiz. GraphQLSchema’nın “Schema”dan miras aldığınız göreceksiniz, bu nesne bize GraphQL’den geliyor, bu da bir GraphQLType diyebiliriz.

Query ve Mutation için birer örnek verdikten sonra şemaya nasıl tanımladığımızı detaylıca anlatacağım.

OrderQuery’yi örnek alalım.

OrderQuery

ObjectGraphType’dan miras aldığımızda yine constructure üzerinde tanımlamalarımızı yapıyoruz. Burada aslında şuna karar vermiş oluyoruz, Order üzerinde istemci hangi sorgulara erişebilsin?

“getOrder”: id parametresi alarak bir “OrderGraphType” dönsün.
“getOrders”: parametre almadan tüm “OrderGraphType” listesini dönsün.

İki tane sorgu belirledim, buraya yeni gördüğümüz bir tip daha var. QueryArgutment’de sayfadan bir guid değer beklediğim için “GuidGraphType” tipini kullanıyorum.

Peki bu değer başka ne olabilirdi ?

Guid : GuidGraphType
String : StringGraphType
Int : IntGraphType
Bool : BooleanGraphType

Aslında isimlerindende anlaşıldığı gibi c#’da kullandığımız tiplerin sonuna”GraphType” eklediğimiz bir çok GraphQL tipinin mevcut olduğunu görebiliyoruz.

İstemciden aldığımız inputModel, geriye döndüğümüz resultModel GraphQL tiplerinde olmak zorunda. Özetle biz Dto objelerimizi kendi içimizde kullanacağız ancak GraphQL’e kendi bildiği tipleri söylememiz gerekiyor gibi düşünebiliriz.

Mutation için OrderMutation’ı örnek alalım.

OrderMutation

Query Resolver içerisinde istemcinin gösterim, listeleme v.b sorgularına cevap verdik. Mutation’da ise sayfadan gönderilen inputModel’i alarak servislere iletip kayıt/güncelleme v.b işlemleri yapmayı sağlayacağız.

“insertOrder”: inputModel parametresi alarak order kayıt işlemi gerçekleştir.

OrderInputGraphType bizim tanımladığımız bir tip, “NonNullGraphType” ile bu parametrenin null geçilemez olduğunu belirtliyoruz.

Sayfadan aldığımız nesneyi servisimize gönderirken servisimiz bildiği tipten göndermemiz gerekiyor bu yüzden “OrderDto”ya cast ediyoruz. OrderInputGraphType’ı oluştururnen OrderDto’dan türetmemizin sebebide buydu.

Şimdi bu mutation ve query tanımlamalarımızı şemamıza tanıtalım. OrderQuery’nin “IQueryResolver”, OrderMutation’ın “IMutationResolver”dan implemente olduğunu görmüşsündür.

GraphQLMutation, GraphQLQuery nesneleride bir objectGraphType bunları GraphSchema’ya tanıtarak şemamımızı ortaya çıkartmış oluyoruz.

06.Servers

06.Servers

Son olarak GraphQL Playground Server aşamasına geldik. Bu projemizde “GraphQL.Server.Ui.Playground” nuget paketini kullanıyoruz. Bu paket bize arka planda çalışacak bir GraphQLServer ve bunu istemciden istekleri alacabileceğimiz bir endpoint sağlıyor.

Startup’da kullandığımız iki adet extensions var.

ContainerStartupExtension:
IoCManager’ın içerisinde dependencyleri register etmek için kullanıyoruz.

GraphStartupExtension :
Query, Mutation listesi dışında tüm tipleri (OrderGraphType, OrderInputGraphType v.b) services.AddSignleton olarak tanımlamanız gerekir. Bunu diğer örnekleri incelediyseniz eğer görmüşsündür.

Biz bu tipleri “ISingletonGraphType” altında toplayarak “AddGraphTypes” methodunda tek bir yerden kayıt ettik. Böylece yeni bir tip geldiğinde sadece “ISingletonGraphType” interface implemente etmesi yeterli olacaktır.

Aynı durumlar mutation ve query Resolvers içinde yaşanacağı için onlarada interface belirleyerek “AddGraphSchema” methodu ile tek bir yerde kayıt ettik. Yeni bir query geldiğinde “IQueryResolver”, yeni bir mutation geldiğinde “IMutationResolver” interface implemente etmesi yeterli olacak.

Böylece projemizi geliştirirken hiç bir şekilde sunum katmanı olarak adlandıran bölümüne ellemeden geliştirmelerimizi yapmış olacağız. Bu gerçekten çok değerli bir konu.

Şimdi örnek sorgularımıza göz atalım ve GraphQL’in bize sağladığı faydaları görelim.

SCGraphQL.GraphQLServer set as startup project yaparak f5 ile projemizi ayağa kaldırıyoruz.

“Docs” bölümünde istemcinin ne sorgular atabileceğini görüyoruz. Aynı zamanda “Schema” bölümünde tüm tiplerin hangi değerlerde olduğunu görebiliyoruz.

Böylece istemci hangi soruları atabilirim, sorguda hangi değerlere ulaşabilirim v.b bilgilere ulaşabiliyor.

İlk olarak bir kullanıcı kaydı yapalım.

Samet Çınar User Mutation

Sorgu :

mutation($inputModel: UserInput!) {
  insertUser(inputModel: $inputModel) {
    id
    name
    surname
  }
}

Parametre:

{
   "inputModel":{
      "name":"Samet",
      "surname":"Çınar"
   }
}

Sonuç :

{
  "data": {
    "insertUser": {
      "id": "29424007-e9d8-4131-8147-8bef39077940",
      "name": "Samet",
      "surname": "Çınar"
    }
  },
  "extensions": {}
}

Tüm kullanıcı kayıtlarının listesini alalım.

Sorgu:

query {
  getUsers {
    id
    name
  }
}

Sonuç:

{
  "data": {
    "getUsers": [
      {
        "id": "29424007-e9d8-4131-8147-8bef39077940",
        "name": "Samet"
      },
      {
        "id": "c6f26845-bee1-48fe-9431-59e999735749",
        "name": "SabancıDx"
      }
    ]
  },
  "extensions": {}
}

Sadece “id, name” alanlarını istedim, query’ye “surname” ekleyelim.

query {
  getUsers {
    id
    name
    surname
  }
}

Sonuç :

{
  "data": {
    "getUsers": [
      {
        "id": "29424007-e9d8-4131-8147-8bef39077940",
        "name": "Samet",
        "surname": "Çınar"
      },
      {
        "id": "c6f26845-bee1-48fe-9431-59e999735749",
        "name": "SabancıDx",
        "surname": "Developer"
      }
    ]
  },
  "extensions": {}
}

Şimdi kullanıcıya sipariş ekleyelim.
ID değerleri anlık değişeceği için siz kendi listesinde dönen değerleri kullanmalısınız.

Sorgu:

mutation($inputModel: OrderInput!) {
  insertOrder(inputModel: $inputModel) {
    id
  }
}

Parametre:

{
   "inputModel":{
      "userId":"29424007-e9d8-4131-8147-8bef39077940",
      "discountPrice":10,
      "totalPrice": 120
   }
}

Sonuç:

{
  "data": {
    "insertOrder": {
      "id": "fa80f2c0-87fc-494a-9a98-c0a0bb2fa22a"
    }
  },
  "extensions": {}
}

Şimdi bu siparişlerimize detay ekleyelim(orderDetail).

Sorgu :

mutation($inputModel: OrderDetailInput!) {
  insertOrderDetail(inputModel: $inputModel) {
    id
  }
}

Parametre:

{
   "inputModel":{
      "orderId":"4756edf0-f689-4a07-b053-ea320be2c074",
      "productName":"Test Ürünü",
      "productPrice": 90
   }
}

Sonuç :

{
  "data": {
    "insertOrderDetail": {
      "id": "9cfcd92a-6e73-4c2e-a07d-9431406c2022"
    }
  },
  "extensions": {}
}

Bir çok sipariş oluşturup, bir çok kalem ekledim. Şimdi bunları bir sipariş listesinde birleştirelim. Sipariş listesini alırken siparişin detaylarını ve siparişi veren kullanıcının bilgilerinide alalım.

Sorgu :

query {
  getOrders {
    id
    user {
      id
      name
      surname
    }
    details {
      productName
      productPrice
    }
    discountPrice
    totalPrice
  }
}

Sonuç :

{
  "data": {
    "getOrders": [
      {
        "id": "fa80f2c0-87fc-494a-9a98-c0a0bb2fa22a",
        "user": {
          "id": "29424007-e9d8-4131-8147-8bef39077940",
          "name": "Samet",
          "surname": "Çınar"
        },
        "details": [
          {
            "productName": "Test Ürünü",
            "productPrice": 90
          }
        ],
        "discountPrice": 10,
        "totalPrice": 120
      },
      {
        "id": "4756edf0-f689-4a07-b053-ea320be2c074",
        "user": {
          "id": "c6f26845-bee1-48fe-9431-59e999735749",
          "name": "SabancıDx",
          "surname": "Developer"
        },
        "details": [
          {
            "productName": "Test Ürünü",
            "productPrice": 90
          }
        ],
        "discountPrice": 50,
        "totalPrice": 75
      }
    ]
  },
  "extensions": {}
}

Proje kodlarını indirmek isterseniz :
https://github.com/gsmtcnr/SCGraphQL

Yine görüşmek üzere..

Leave a Reply

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir