👶🏻 GraphQL ile WordPress'i Gençleştirmek
WordPress eski bir CMS'dir: 17 yılı aşkın süre önce icat edilmiş olması nedeniyle, yeniden yazma fırsatı verilseydi farklı biçimde kodlanacak olan PHP koduyla doludur.
GraphQL, verilere erişmek için modern bir arayüzdür. "Arayüz" kelimesine dikkat edin: altta yatan veri sisteminin nasıl uygulandığıyla ilgilenmez; yalnızca verilerin nasıl sunulacağına odaklanır.
Bu ikisini bir araya getirdiğimizde ne olur? WordPress verilerine erişmek için GraphQL arayüzünü nasıl tasarlamalıyız?
Uygulayabileceğimiz birkaç belirgin strateji vardır:
-
Geleneğe saygı göstermek ve yıllar içinde biriken teknik borç dahil WordPress veri modelini olduğu gibi koruyan bir eşleme sağlamak
-
Teknik borcu gidermek ve verileri soyut, WordPress'e zorunlu olarak bağlı olmayan bir şekilde sunan bir arayüz sunmak
Her iki yaklaşımın da avantajları ve dezavantajları vardır; doğrusu ya da yanlışı yoktur. Bu yalnızca bir tercih meselesidir; bir davranışı diğerine önceliklendirmektir.
Gato GraphQL eklentisi için ikinci yaklaşımı seçtim: WordPress'e dayalı ve WordPress için çalışan, ancak WordPress'e bağlı olmayan (örneğin tutarsız isimler ve ilişkileri kaldırarak) bir GraphQL şeması oluşturmaya çalıştım.
Sonuç olarak GraphQL, WordPress'i gençleştiriyor: altta yatan CMS'imiz hâlâ eski PHP koduyla WordPress olsa da, veri katmanı ortak sezgiye dayalı olarak sıfırdan yeniden oluşturulabilir; gelenek değil, sağduyu esas alınır. Veri katmanı ergenlikten çıkıp yeniden yürümeye başlayan bir çocuğa dönüşür.

Sonuç, WordPress veri modelini temsil eden ve iç içe mutations destekleyen bir GraphQL şemasıdır.
Bunun nasıl gerçekleştirildiğine bakalım.
WordPress Veri Modeli
WordPress'in aşağıdaki varlıkları vardır:
- gönderiler (posts)
- sayfalar
- özel gönderiler (custom posts)
- medya öğeleri
- kullanıcılar
- kullanıcı rolleri
- etiketler (tags)
- kategoriler
- yorumlar
- bloklar
- meta özellikler
- diğerleri (seçenekler, eklentiler, temalar vb.)
Bu varlıklar bir hiyerarşiye sahip olabilir. Örneğin, gönderi, sayfa ve medya öğeleri custom post type'larıdır; etiketler ve kategoriler ise birer taksonomiddir.
Tüm varlıklara ait verilerin nasıl depolandığını gösteren WordPress veritabanı diyagramı:

Eşleme, Veritabanı Diyagramının Birebir Kopyası mı?
WordPress veritabanını bir GraphQL şemasına eşlerken yukarıdaki diyagram bire bir esas alınır mı?
Hayır, alınmaz. Veritabanı diyagramı gerçek bir uygulamayı temsil ederken GraphQL, istemciden verilere erişmek için kullanılan bir arayüzdür. Bu ikisi birbirleriyle ilişkilidir ancak farklı olabilirler. GraphQL veritabanıyla ilgilenmez: SQL komutları cinsinden düşünmez, wp_posts veya wp_users adında veritabanı tablolarının var olduğunu bilmez.
Bu nedenle WordPress için GraphQL şemasını oluştururken veritabanı diyagramı hakkında çok fazla endişelenmemize gerek yoktur. Bu, WordPress veri modelindeki teknik borcun bir kısmını gideren bir GraphQL şeması üretebileceğimiz anlamına gelir.
WordPress Veri Modelini GraphQL Şeması Olarak Eşlemek
Eşlemeyi yapalım. Önce, mümkün olduğunca özgün varlıkları tür olarak eşleriz. WordPress veri modelindeki varlık listesinden GraphQL şeması için şu türleri üretiriz:
PostPageMediaUserUserRolePostTagPostCategoryComment
Ardından her türe beklenen tüm alanları ekleriz. Şemayı temsil etmek için SDL'yi (Schema Definition Language) kullanabiliriz. (Bu yalnızca belgeleme amacıyla kullanılır; eklentinin kendisi şemayı kodlamak için SDL kullanmaz: her şey PHP kodudur.)
Bir Post için alanlar (diğerleri arasında):
type Post {
id: ID!
title: String
content: String
excerpt: String
publishedAt: Date!
}Bir User için alanlar (diğerleri arasında):
type User {
id: ID!
name: String
email: String!
}Ayrıca karşılık gelen bağlantıları da oluştururuz; bunlar bir skaler (sayı veya dize gibi) yerine başka bir varlık döndüren alanlardır. Örneğin, bir gönderinin bir yazara sahip olduğunu ve bir kullanıcının gönderilere sahip olduğunu temsil ederiz:
type Post {
author: User!
}
type User {
posts: [Post]
}Alanlar ve bağlantılar argüman da kabul edebilir. Örneğin, Post.date alanının biçimlendirilmesine ve User.posts alanının girişleri aramasına ve sayısını sınırlamasına olanak tanırız:
type Post {
date(format: String): Date!
}
type User {
posts(limit: Int, search: String): [Post]
}WordPress veri modelindeki tüm varlıklar için bu işlemi sürdürürüz. İşimiz bittiğinde, eklentinin menüsünde "Interactive Schema" olarak erişilebilen Voyager istemcisi aracılığıyla görülebilen WordPress GraphQL şemasına ulaşırız:

Bu şemanın WordPress veritabanı diyagramıyla benzerlikleri olduğu gibi pek çok farkı da vardır. Şimdi bunları inceleyelim.
Varlığa Bağlı Olmayan İşlemler Root Alanları Olarak Eşlenir
WordPress veritabanı diyagramı verilerin nasıl depolandığını temsil eder; dolayısıyla bir "başlangıç" yoktur. Ancak GraphQL, veri almak için kullanılan bir arayüz olduğundan, queries çalıştırmak için bir başlangıç aşaması bulunmalıdır.
Bu başlangıç aşaması Root türüdür; daha doğrusu QueryRoot ve MutationRoot türleridir (sırasıyla queries ve mutations için kullanılır).
Bu iki türde, get_posts(), get_users() veya wp_signon() çalıştırıldığında olduğu gibi bir varlığa bağlı olmayan tüm işlemleri eşleriz:
type QueryRoot {
posts: [Post]!
users: [User]!
}
type MutationRoot {
logUserIn(username: String, password: String): User
}Alanların, temsil ettikleri işlemle aynı ad veya imzaya sahip olması gerekmez. Örneğin, logUserIn alanını çağırmak signOn'dan daha uygun kabul edilebilir.
Tüm Mutations MutationRoot Altına Gider
Bazı işlemler bir varlığa bağlıdır; örneğin wp_update_post(), belirli bir gönderi üzerinde uygulanır. GraphQL'in çalışma biçimi gereği, GraphQL şemasındaki karşılık gelen mutation MutationRoot türüne eklenmelidir.
Bu durumda işlem şöyle eşlenir:
type MutationRoot {
updatePost(input: {
postID: ID!,
newTitle: String,
newContent: String
}): Post
}Bu eklenti aynı zamanda iç içe mutations destekler; bu, standart GraphQL davranışı olmadığı için isteğe bağlı (opt-in) bir özellik olarak sunulur. Bu durumda mutations yalnızca MutationRoot altında değil, herhangi bir tür altında da eklenebilir:
type Post {
update(input: {
newTitle: String,
newContent: String
}): Post!
}Custom Post'larla Başa Çıkmak
GraphQL'de tür kalıtımı yoktur. Bu nedenle bir CustomPost türüne sahip olamaz ve Post ile Page'in bunu genişlettiğini bildiremeyiz.
GraphQL bu eksikliği telafi etmek için iki kaynak sunar: arayüzler (interfaces) ve birleşim türleri (union types).
Birincisi için şemada bir CustomPost arayüzü oluştururuz; custom post'tan beklenen tüm alanları bildiririz ve Post ile Page türlerini bu arayüzü uygulayacak şekilde tanımlarız:
interface CustomPost {
title: String
content: String
excerpt: String
date(format: String): Date!
}
type Post implements CustomPost {
title: String
content: String
excerpt: String
date(format: String): Date!
}
type Page implements CustomPost {
title: String
content: String
excerpt: String
date(format: String): Date!
}İkincisi için şemada tüm custom post türlerini döndüren bir CustomPostUnion türü oluştururuz:
union CustomPostUnion = Post | PageVe uygun olduğunda alanların bu türü döndürmesini sağlarız:
type QueryRoot {
customPost(id: ID): CustomPostUnion
customPosts: [CustomPostUnion]!
}
type User {
customPosts: [CustomPostUnion]
}
type Comment {
customPost: CustomPostUnion!
}Görüleceği üzere, GraphQL şemasında ne zaman gönderilerle, ne zaman custom post'larla uğraştığımızı açıkça belirtmemiz gerekir; zira bunlar aynı şey değildir! Bu ikisini birbirinin yerine kullanmak, düzeltebileceğimiz bir WordPress teknik borcudur.
Bu nedenle, bir custom post her zaman Post değil CustomPost olarak adlandırılır; custom post'larla ilgilenen bir alan her zaman posts değil customPosts olarak adlandırılır; ve bir custom post'un ID'sini alan bir alan argümanı, eşlenen WordPress işlevinde böyle çağrılsa bile postID değil customPostID olarak adlandırılır.
Böylece beklenti her zaman açıktır:
User.customPostsalanı, gönderiler ve sayfalar dahil herhangi bir custom post listesi döndürebilirkenUser.postsyalnızca gönderileri döndürürRoot.setFeaturedImageOnCustomPostalanı, herhangi bir custom post'a öne çıkan görsel ekleyebilir; bu nedenlesetFeaturedImageOnPostolarak adlandırılmaz
Etiketleri (ve Kategorileri) Tek Bir Tür Altında Gruplamamak
PostTag türü (aynı şekilde PostCategory) neden bu şekilde adlandırılmıştır; neden yalnızca Tag değil?
Çünkü bu queries çalıştırıldığında (burada bir ürün bir CPT'dir), gönderiler ve ürünler için tags alanının sonuçları her zaman farklı ve örtüşmeyen olacaktır:
query {
posts {
tags {
id
name
}
}
products {
tags {
id
name
}
}
}Gönderilere eklenen etiketler, ürünler için etiketler alınırken görünmez ve bunun tersi de geçerlidir (bir ürün post_tag taksonomisini de kullanmıyorsa; bu durumda PostTag türüyle de temsil edilebilir). Bu durum WordPress'te büyük bir sorun oluşturmaz; çünkü bu öğeler aynı veritabanı tablosunun farklı satırları olarak değerlendirilebilir. Ancak güçlü bir şekilde yazılan GraphQL açısından bu önem taşır.
Bu nedenle, bu varlıkları kendi türleri altında ayrı tutmak ve gönderilere ait etiketlerin PostTag türü altında döndürülmesini sağlamak iyi bir tasarım kararıdır. Özel bir eklenti kendi ürün CPT'sini uyguluyorsa, etiketleri için ProductTag türünü kullanmalıdır.
Medya Öğelerine Kendi Kimliğini Vermek
WordPress'teki medya varlıkları, uygulama açısından elverişli olduğu için custom post type olarak tasarlanmıştır. Ancak GraphQL şeması bu teknik borçtan kaçınabilir ve medya öğelerini custom post olarak değil, ayrı bir varlık olarak modelleyebilir.
Bu, GraphQL şeması için şu kararları beraberinde getirir:
customPostsalanı sorgulandığında medya öğeleri getirilmezMediatürüCustomPostarayüzünü uygulamaz veCustomPostUniontürünün parçası olmazMediatüründe,excerpt,datevestatusgibi bir custom post type'tan beklenen pek çok alan bulunmaz. Bunun yerine yalnızca bir medya öğesinden beklenen alanlar yer alır:
type Media {
id: ID!
src: String!
width: Int
height: Int
}Enum'ları Tanımlamak ve Eşlemek
Bazı durumlarda WordPress, belirli bir kümeden sabit değerler kullanır. Örneğin, bir gönderinin durumu yalnızca "publish", "draft", "pending" veya "trash" olabilir.
GraphQL'de bunları dize yerine enum olarak ele alabilir ve karşılık gelen bir numaralandırma türü oluşturabiliriz. GraphQL standardına göre enum'lar büyük harfle yazılmalıdır:
enum CUSTOM_POST_STATUS {
PUBLISH
DRAFT
PENDING
TRASH
}Ancak bu durumda get_posts( [ "post_status" => "PUBLISH" ] ) çalıştırmak işe yaramayacağından queries WordPress ile doğrudan etkileşim kurmak için kullanılamaz.
Bu nedenle bir uzlaşma olarak bu enum değerlerini küçük harfte tutarız:
enum CUSTOM_POST_STATUS {
publish
draft
pending
trash
}Ek Türleri Eşlemek
Bloklar, wp_posts tablosunda saklandığından (wp_blocks adında bir tablo yoktur) WordPress veritabanı diyagramında doğrudan görünmez; ancak bununla birlikte ayrı bir varlıktır.
Bu nedenle bunları eşlemek için Block türünü tanıtırız:
type Post {
blocks: [Block]
}
type Block {
type: String!
attributes: JSONObject
}