API servislerinde kullanılmak üzere Facebook tarafından açık kaynak olarak geliştirilmiş, veri sorgulama, değiştirme, abonelik vs. işlevlerini destekleyen, söz dizimi bakımından JSON’a benzeyen fakat özel bir söz dizimine (syntax) sahip sorgulama dili ve yanı sıra bir mimaridir.
Son zamanlarda hayatı biraz yoğun tempoda yaşadığımdan bu yazıyı hazırlamaya/tamamlamaya yeni fırsat bulabildim. GitHub reposunu takip edenler için; 1 ay önce yayınlanmış örnek proje var ama konunun açıklaması arkasından gelmiş oldu 🙂 Evet şimdi gelelim konumuza… Konuya girişi “RestAPI yerine, RestAPI gibi ama daha tatlısı” gibi bir giriş yapabilirdim fakat ben bu hakkımı yazının sonuna doğru pekiştirmek için kullanacağım.
Öncelikle GraphAPI altyapısındaki terimleri kısaca bir tanıyalım.
Types: Veri Aktarım Nesneleri
Queries: Sorgular
Mutations: Değişikliğe Sebep Olan Eylemler
Subscriptions: Abonelikler
Resolvers: İstek Karşılayıcılar
Types (Veri Aktarım Nesneleri)
Şimdi parçaları birleştirmeye başlayalım… Bir API servisinde istek veri paketleri ve dönüş veri paketleri vardır. Hedef servise göndereceğiniz veri paketi ve dönüşünde alacağınız yanıtın veri yapısını bilmemiz gerekiyor ki buna göre kendi tarafınızdaki servis referanslarını oluşturabilelim ya da akış şemalarımızı oluşturabilelim.
İşte GraphAPI’de veri yapımızı (data structure) Types adı verilen nesneler ile deklare ediyoruz. Yani bu Types adını verdiğimiz nesneler bizim DTO’larımızı, OpenAPI (Swagger) şemalarımız diyebiliriz. İstemciler çağrı esnasında ve dönüşünde kullanabilecekleri parametreleri, veri tiplerini bu belirtiğimiz Type sayesinde biliyor olacaklar.
Queries (Sorgular)
Verilere erişilebilmesi yani veri sunabilmemiz için oluşturacağımız sorgular. Bu sorgular bir değişime sebep olmamalı, yalnızca veri okuması yapmalıdır. (Daha teknik olmak gerekirse; buradan gelen istekler işlenirken yalnızca read yetkisine sahip bir veritabanı kullanıcısının bağlantısı kullanılmalıdır.)
Örneğin; ProductQuery adında bir sorgumuz olsun ve bu sorgu ProductQueryType adında bir Type kabul edip, dönüşünde ise ProductType adında bir Type ile dönüş yapsın.
Biz şu anda bir Query oluşturduk ve artık istemcilerimiz ProductQuery olarak belirlediğimiz ve onlarla paylaştığımız ProductQueryType ile istek attıklarında ProductType ile dönüş yapabiliriz. (Bu gelen isteğin nasıl işleneceği konusunu Resolvers altında göreceğiz.)
Mutations (Değişikliğe Sebep Olan Eylemler)
Mutations verilerde bir değişikliğe sebep olan tanımlarımızı içerir. Yani Oluşturma/Düzenleme/Silme eylemlerimizi içerir. (Daha teknik olmak gerekirse; buradan gelen istekler işlenirken write yetkisine sahip bir veritabanı kullanıcısının bağlantısı kullanılmalıdır.)
Örneğin; CreateBrand adında bir Mutation oluşturalım. Bu mutation BrandInputType tipinde veri kabul etsin ve dönüşünde ise BrandType sunsun.
Şu anda bir Mutation oluşturduk ve artık istemcilerimiz CreateBrand olarak belirlediğimiz ve onlarla paylaştığımız BrandInputType ile istek attıklarında BrandType ile dönüş yapabiliriz. (Bu gelen isteğin nasıl işleneceği konusunu Resolvers altında göreceğiz.)
Subscriptions (Abonelikler)
Etkinlikler için abone olabilmemizi sağlayan altyapıdır ve WebSocket üzerinden (haliyle gerçek zamanlı olarak) çalışır.
Örneğin; CreateBrand mutation sonrasında bir event fırlatarak abone olanlara WebSocket üzerinden bu etkinlik ile ilgili bildirim gönderebiliriz (push model). Bu sayede aboneler kendi taraflarında bu bildirimler sonrası aksiyon(lar) alabilirler.
Eğer bildirim göndermeseydik bizim tarafımızdaki değişiklikleri anlayabilmek için sürekli servislerimizden veri çekmeleri gerekirdi (pull model). Bu model hakkında daha ayrıntılı bilgi edinebilmek için öncelikle WebSocket sonrasında WebHook ve son olarak Publish–subscribe pattern inceleyebilirsiniz.
Resolvers: İstek Karşılayıcılar
Queries ve Mutations tanımlarımız için istek aldığımızda bu gelen isteğin nasıl işleneceğini tanımladığımız karşılayıcılara Resolvers denir. Yani şu ana kadar kabul ettiğimiz sorgu ve eylemlerimizi, bunların kullandığı veri yapısını hazırladık. Artık gelen isteği karşılayıp iş kurallarımızı (bussiness logic) uygulayacağız.
Örnek #1: ProductQuery için istek geldiğinde veritabanımıza (okuma yetkisine sahip kullanıcımız ile) bağlanıp sonrasında ProductQueryType içerisinden gelen parametrelere göre veritabanımızda veri filtrelemesi uygularız. Ve elimize gelen verileri ProductType olacak şekilde çıktı olarak sunarız.
Yani dışarıdan ProductQuery geldiğinde ne yapacağımızı tanımlayarak tüm parçaları bir araya getirmiş olduk. Gerekli olan Type, Query ve Resolvers tanımlamalarımızı doğru bir şekilde yapılandırmazsak ilgili sorgunun çalışmasını bekleyemeyiz.
Örnek #2: CreateBrandiçin istek geldiğinde veritabanımıza (yazma yetkisine sahip kullanıcımız ile) bağlanıp sonrasında BrandInputType içerisinden gelen parametreler doğrultusunda yeni bir marka oluşturur ve bunu veritabanıma kaydederiz. Sonrasında eklediğimiz markayı (gerkiyorsa; tekrar çekip) BrandType olacak şekilde çıktı olarak sunarız.
Temel faydalardan bahsetmek gerekirse;
Gereksiz Veri/Trafik Yükü: Basitçe örneklendirmek gerekirse; REST API’da dönüş veri paketinizde (istemcinin ihtiyacı var mı yok mu gözetmeksizin) tüm verileri sunarız. Peki burada sorun nedir? Sorun; ben bir istemci olarak yalnızca a,b,c sütunlarına ihtiyaç duyuyor olabilirim. GraphQL ile ihtiyaç duyduğum alanları belirtir, hatta bu alanları alırken işlevler kullanarak kendi tarafımda kod yazmadan bir çok fayda elde edebilirim. İstek yanıtında yalnızca ihtiyacım olan veriyi aldığımdan ağ (network) trafik yüküm azalacak ve veriyi parse etmem daha kısa sürecek gibi bir çok yan fayda sağlar.
Veri İşleme/Karşılaştırma: REST’te farklı adreslerdeki servislere ayrı ayrı istek atıp birleştirmemiz gerekirken GraphQL ile tek bir HTTP isteği ile n tane farklı sorgu atıp bunların sonuçlarını karşılaştırabilir, arasındaki farkları alabilir hatta dilerseniz dönen veriyi ihtiyaçlarınız doğrultusunda anlamlandırılmış şekilde (aggregations) tek bir veri paketi ile alabilirsiniz. Veriyi anlamlandırmaya minik bir örnek vermek gerekirse; product.desc şeklinde dönen bir veriyi urun.aciklama şeklinde response alabilirsiniz. Bu sayede veriyi aldıktan sonra kendi tarafınızda eşleştirme (mapping) ihtiyacı ortadan kalkar.
Kod örneği incelemek isterseniz .NetCore için github.com/halilsafakkilic/DotNetCore-Graph-API-Playground reposunu inceleyebilirsiniz.
GraphQL ile ilgili bir sonraki yazımda örnek sorgular vs. konulardan bahsediyor olacağım. Yazıyı yayımladığımda bu kısımda bağlantısını bulabileceksiniz.
Mikro servisleriniz arasında ağ sorunları yaşamadığınız, kullanılabilir portlarınızın bol olduğu yeşil günlerde görüşmek dileğiyle…