GraphQL ve REST - Bir GraphQL Eğitimi

Yayınlanan: 2022-03-11

Mahalledeki yeni çocuğu duymuş olabilirsiniz: GraphQL. Değilse, GraphQL, tek kelimeyle, API'leri getirmenin yeni bir yolu, REST'e bir alternatiftir. Facebook'ta dahili bir proje olarak başladı ve açık kaynaklı olduğu için çok fazla ilgi gördü.

Bu makalenin amacı, GraphQL için kararınızı vermiş olsanız da, denemeye hazır olsanız da, REST'ten GraphQL'e kolay bir geçiş yapmanıza yardımcı olmaktır. Önceden GraphQL bilgisi gerekmez, ancak makaleyi anlamak için REST API'lerine biraz aşinalık gerekir.

GraphQL ve REST - Bir GraphQL Eğitimi

Makalenin ilk kısmı, GraphQL'in REST'ten daha üstün olduğunu düşünmemin üç nedenini vererek başlayacak. İkinci kısım, arka ucunuza bir GraphQL uç noktasının nasıl ekleneceğine dair bir eğitimdir.

Graphql vs. REST: Neden REST'i Bırakın?

GraphQL'in ihtiyaçlarınıza uygun olup olmadığı konusunda hala tereddüt ediyorsanız, burada “REST ve GraphQL” hakkında oldukça kapsamlı ve objektif bir genel bakış verilmiştir. Ancak, GraphQL'i kullanmak için en önemli üç nedenim için okumaya devam edin.

Sebep 1: Ağ Performansı

Arka uçta ad, soyad, e-posta ve diğer 10 alan içeren bir kullanıcı kaynağınız olduğunu varsayalım. İstemcide, genellikle bunlardan sadece birkaçına ihtiyacınız vardır.

/users uç noktasında bir REST çağrısı yapmak, kullanıcının tüm alanlarını size geri verir ve istemci yalnızca ihtiyaç duyduğu alanları kullanır. Açıkça, mobil istemcilerde dikkate alınabilecek bazı veri aktarım israfı var.

GraphQL varsayılan olarak mümkün olan en küçük verileri getirir. Kullanıcılarınızın yalnızca ad ve soyadlarına ihtiyacınız varsa, bunu sorgunuzda belirtirsiniz.

Aşağıdaki arabirim, GraphQL için bir API gezgini gibi olan GraphiQL olarak adlandırılır. Bu makalenin amacı için küçük bir proje oluşturdum. Kod GitHub'da barındırılıyor ve ikinci bölümde inceleyeceğiz.

Arayüzün sol bölmesinde sorgu bulunur. Burada tüm kullanıcıları alıyoruz - GET /users ile REST yapardık - ve sadece adlarını ve soyadlarını alıyoruz.

Sorgu

 query { users { firstname lastname } }

Sonuç

 { "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }

E-postaları da almak istiyorsak, “soyadı”nın altına bir “e-posta” satırı eklemek işe yarayabilirdi.

Bazı REST arka uçları, kısmi kaynakları döndürmek için /users?fields=firstname,lastname gibi seçenekler sunar. Değeri ne olursa olsun, Google bunu önerir. Ancak, varsayılan olarak uygulanmaz ve özellikle diğer sorgu parametrelerini girdiğinizde isteği zor okunabilir hale getirir:

  • &status=active kullanıcıları filtrelemek için aktif
  • Kullanıcıları oluşturma tarihlerine göre sıralamak için &sort=createdAat
  • &sortDirection=desc çünkü belli ki buna ihtiyacın var
  • &include=projects içerecek projeler

Bu sorgu parametreleri, bir sorgu dilini taklit etmek için REST API'sine eklenen yamalardır. GraphQL, her şeyden önce, istekleri kısa ve net hale getiren bir sorgulama dilidir.

Sebep 2: "Include vs. Endpoint" Tasarım Seçimi

Basit bir proje yönetim aracı oluşturmak istediğimizi düşünelim. Üç kaynağımız var: kullanıcılar, projeler ve görevler. Ayrıca kaynaklar arasında aşağıdaki ilişkileri de tanımlarız:

kaynaklar arasındaki ilişkiler

İşte dünyaya maruz bıraktığımız son noktalardan bazıları:

uç nokta Tanım
GET /users Tüm kullanıcıları listele
GET /users/:id Kimliği olan tek kullanıcıyı alın: id
GET /users/:id/projects Bir kullanıcının tüm projelerini alın

Uç noktalar basit, kolay okunabilir ve iyi organize edilmiş.

İsteklerimiz daha karmaşık hale geldiğinde işler daha da zorlaşıyor. GET /users/:id/projects uç noktasını ele alalım: Diyelim ki ana sayfada yalnızca projelerin başlıklarını göstermek istiyorum, ancak birden fazla REST çağrısı yapmadan panoda projeler+görevler göstermek istiyorum. Aramalıyım:

  • Ana sayfa için GET /users/:id/projects alın.
  • GET /users/:id/projects?include=tasks , böylece arka uç tüm ilgili görevleri ekler.

Bunun çalışması için ?include=... sorgu parametreleri eklemek yaygın bir uygulamadır ve hatta JSON API belirtimi tarafından önerilir. ?include=tasks gibi sorgu parametreleri hala okunabilir, ancak çok geçmeden ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author .

Bu durumda, bunu yapmak için bir /projects bitiş noktası oluşturmak daha akıllıca olur mu? /projects?userId=:id&include=tasks gibi bir şey, çünkü dahil etmemiz gereken bir ilişki düzeyi daha az mı? Veya, aslında bir /tasks?userId=:id uç noktası da işe yarayabilir. Bu, çoka çok ilişkimiz varsa daha da karmaşık bir tasarım seçimi olabilir.

GraphQL her yerde include yaklaşımını kullanır. Bu, ilişkileri güçlü ve tutarlı hale getirmek için sözdizimi yapar.

Burada, kimliği 1 olan kullanıcıdan tüm projeleri ve görevleri almanın bir örneği verilmiştir.

Sorgu

 { user(id: 1) { projects { name tasks { description } } } }

Sonuç

 { "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }

Gördüğünüz gibi, sorgu sözdizimi kolayca okunabilir. Daha derine inip görevleri, yorumları, resimleri ve yazarları dahil etmek isteseydik, API'mizi nasıl düzenleyeceğimizi iki kez düşünmezdik. GraphQL, karmaşık nesneleri getirmeyi kolaylaştırır.

Sebep 3: Farklı Müşteri Türlerini Yönetmek

Bir arka uç oluştururken, her zaman API'yi mümkün olduğunca tüm istemciler tarafından yaygın olarak kullanılabilir hale getirmeye çalışarak başlarız. Yine de müşteriler her zaman daha az aramak ve daha fazlasını getirmek ister. Derin içerme, kısmi kaynaklar ve filtreleme ile web ve mobil istemciler tarafından yapılan istekler birbirinden çok farklı olabilir.

REST ile birkaç çözüm var. Özel bir uç nokta (yani, bir takma ad uç noktası, ör. /mobile_user ), özel bir temsil ( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json ) veya hatta bir istemci oluşturabiliriz -spesifik API (Netflix'in bir zamanlar yaptığı gibi). Üçü de arka uç geliştirme ekibinden ekstra çaba gerektirir.

GraphQL, istemciye daha fazla güç verir. İstemcinin karmaşık isteklere ihtiyacı varsa, ilgili sorguları kendisi oluşturacaktır. Her istemci aynı API'yi farklı şekilde kullanabilir.

GraphQL'ye Nasıl Başlanır?

Bugün “GraphQL vs. REST” hakkındaki çoğu tartışmada, insanlar ikisinden birini seçmeleri gerektiğini düşünüyorlar. Bu kesinlikle doğru değil.

Modern uygulamalar genellikle birkaç API ortaya çıkaran birkaç farklı hizmet kullanır. Aslında GraphQL'i tüm bu hizmetler için bir ağ geçidi veya sarmalayıcı olarak düşünebiliriz. Tüm istemciler GraphQL uç noktasına ulaşır ve bu uç nokta veritabanı katmanına, ElasticSearch veya Sendgrid gibi harici bir hizmete veya diğer REST uç noktalarına ulaşır.

GraphQL ile REST uç noktalarının karşılaştırmaları

Her ikisini de kullanmanın ikinci bir yolu, REST API'nizde ayrı bir /graphql bitiş noktasına sahip olmaktır. Bu, özellikle REST API'nize ulaşan çok sayıda istemciniz varsa, ancak mevcut altyapıdan ödün vermeden GraphQL'yi denemek istiyorsanız kullanışlıdır. Ve bugün araştırdığımız çözüm de bu.

Daha önce de söylediğim gibi, bu öğreticiyi GitHub'da bulunan küçük bir örnek proje ile göstereceğim. Kullanıcılar, projeler ve görevlerle basitleştirilmiş bir proje yönetim aracıdır.

Bu proje için kullanılan teknolojiler, web sunucusu için Node.js ve Express, ilişkisel veritabanı olarak SQLite ve ORM olarak Sequelize'dir. Üç model (kullanıcı, proje ve görev) models klasöründe tanımlanmıştır. REST uç noktaları /api/users , /api/projects ve /api/tasks dünyaya sunulur ve rest klasöründe tanımlanır.

GraphQL'nin herhangi bir programlama dili kullanılarak her tür arka uç ve veritabanına kurulabileceğini unutmayın. Burada kullanılan teknolojiler, basitlik ve okunabilirlik adına seçilmiştir.

Amacımız, REST uç noktalarını kaldırmadan bir /graphql uç noktası oluşturmaktır. GraphQL uç noktası, verileri almak için doğrudan ORM veritabanına çarpacak ve böylece REST mantığından tamamen bağımsız olacaktır.

Türler

Veri modeli, GraphQL'de kesin olarak yazılan türlerle temsil edilir. Modelleriniz ve GraphQL türleri arasında 1'e 1 eşleme olmalıdır. User tipimiz şöyle olacaktır:

 type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }

Sorguları

Sorgular , GraphQL API'nizde hangi sorguları çalıştırabileceğinizi tanımlar. Kural olarak, mevcut tüm sorguları içeren bir RootQuery olmalıdır. Ayrıca her sorgunun REST eşdeğerini de belirttim:

 type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }

mutasyonlar

Sorgular GET istekleri ise, mutasyonlar POST / PATCH / PUT / DELETE istekleri olarak görülebilir (aslında bunlar sorguların senkronize versiyonlarıdır).

Geleneksel olarak, tüm mutasyonlarımızı bir RootMutation :

 type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }

Burada UserInput , ProjectInput ve TaskInput olarak adlandırılan yeni türleri tanıttığımızı unutmayın. Bu, kaynakları oluşturmak ve güncellemek için bir girdi veri modeli oluşturmak için REST ile de yaygın bir uygulamadır. Burada, UserInput , id ve projects alanları olmayan User tipimizdir ve type yerine anahtar kelime input dikkat edin:

 input UserInput { firstname: String lastname: String email: String }

Şema

Türler, sorgular ve mutasyonlarla, GraphQL uç noktasının dünyaya gösterdiği GraphQL şemasını tanımlarız:

 schema { query: RootQuery mutation: RootMutation }

Bu şema güçlü bir şekilde yazılmıştır ve GraphiQL'de bu kullanışlı otomatik tamamlamalara sahip olmamızı sağlayan şeydir.

Çözümleyiciler

Artık genel şemaya sahip olduğumuza göre, bu sorguların/mutasyonların her biri istendiğinde GraphQL'e ne yapacağını söylemenin zamanı geldi. Çözümleyiciler zor işi yapar; örneğin şunları yapabilirler:

  • Dahili bir REST uç noktasına ulaşın
  • Bir mikro hizmeti arayın
  • CRUD işlemlerini yapmak için veritabanı katmanına basın

Örnek uygulamamızda üçüncü seçeneği seçiyoruz. Çözücüler dosyamıza bir göz atalım:

 const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }

Bu, GraphQL'de user(id: ID!) sorgusu isteniyorsa, veritabanından bir Sequelize ORM işlevi olan User.findById() işlevini döndürdüğümüz anlamına gelir.

İstekte diğer modellere katılmaya ne dersiniz? Pekala, daha fazla çözümleyici tanımlamamız gerekiyor:

 User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */

Dolayısıyla, GraphQL'de bir User türünde projects alanını istediğimizde, bu birleştirme veritabanı sorgusuna eklenecektir.

Ve son olarak, mutasyonlar için çözümleyiciler:

 RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }

Burada bununla oynayabilirsiniz. Sunucudaki verileri temiz tutmak adına, çözümleyicileri mutasyonlar için devre dışı bıraktım, bu da mutasyonların veritabanında herhangi bir oluşturma, güncelleme veya silme işlemi yapmayacağı (ve böylece arabirimde null döndürmesi) anlamına geliyor.

Sorgu

 query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }

Sonuç

 { "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }

Mevcut uygulamanız için tüm türleri, sorguları ve çözümleyicileri yeniden yazmak biraz zaman alabilir. Ancak, size yardımcı olacak birçok araç var. Örneğin, çözümleyiciler de dahil olmak üzere bir SQL şemasını GraphQL şemasına çeviren araçlar vardır!

Her Şeyi Bir Araya Getirmek

İyi tanımlanmış bir şema ve şemanın her sorgusunda ne yapılacağına ilişkin çözümleyicilerle, arka uçumuza bir /graphql uç noktası bağlayabiliriz:

 // Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));

Ve arka uçta güzel görünümlü bir GraphiQL arayüzüne sahip olabiliriz. GraphiQL olmadan bir istekte bulunmak için, isteğin URL'sini kopyalamanız ve cURL, AJAX ile veya doğrudan tarayıcıda çalıştırmanız yeterlidir. Elbette, bu sorguları oluşturmanıza yardımcı olacak bazı GraphQL istemcileri vardır. Bazı örnekler için aşağıya bakın.

Sıradaki ne?

Bu makalenin amacı, size GraphQL'in nasıl göründüğüne dair bir fikir vermek ve size REST altyapınızı atmadan GraphQL'i denemenin kesinlikle mümkün olduğunu göstermektir. GraphQL'nin ihtiyaçlarınıza uygun olup olmadığını anlamanın en iyi yolu, onu kendiniz denemektir. Umarım bu makale sizi dalışa götürür.

Gerçek zamanlı güncellemeler, sunucu tarafı toplu işleme, kimlik doğrulama, yetkilendirme, istemci tarafı önbelleğe alma, dosya yükleme vb. gibi bu makalede bahsetmediğimiz birçok özellik var. Bu özellikler hakkında bilgi edinmek için mükemmel bir kaynak GraphQL Nasıl Yapılır.

Aşağıda diğer bazı yararlı kaynaklar bulunmaktadır:

Sunucu Tarafı Aracı Tanım
graphql-js GraphQL'nin referans uygulaması. Bir sunucu oluşturmak için express-graphql ile kullanabilirsiniz.
graphql-server Apollo ekibi tarafından oluşturulan hepsi bir arada GraphQL sunucusu.
Diğer platformlar için uygulamalar Ruby, PHP, vb.
İstemci Tarafı Aracı Tanım
Röle React'i GraphQL ile bağlamak için bir çerçeve.
apollo-müşteri. React, Angular 2 ve diğer ön uç çerçeveler için bağlamaları olan bir GraphQL istemcisi.

Sonuç olarak, GraphQL'in yutturmacadan daha fazlası olduğuna inanıyorum. Henüz yarın REST'in yerini almayacak, ancak gerçek bir soruna performanslı bir çözüm sunuyor. Nispeten yeni ve en iyi uygulamalar hala gelişiyor, ancak kesinlikle önümüzdeki birkaç yıl içinde adını duyacağımız bir teknoloji.