Mapeando o schema GraphQL para seu site, tema ou plugin WordPress
Então você decidiu começar a usar GraphQL no seu site WordPress existente. Ótimo! Seja para funcionalidades novas ou existentes, o GraphQL precisará interagir com a camada de dados subjacente, para a qual você precisará mapear o modelo de dados da sua aplicação (seja código PHP personalizado no seu site WordPress, no seu tema ou no seu plugin) para o schema GraphQL.
Como o mapeamento deve ser feito? Precisa ser feito de uma vez? Deve ser uma réplica exata do modelo de dados existente? E quanto a corrigir um nome inadequado no processo? E em relação à dívida técnica, ela deve ser mantida ou resolvida?
Vamos explorar algumas estratégias para mapear o modelo de dados de uma aplicação WordPress existente em um schema GraphQL.
Mapeie o schema no seu próprio ritmo
Adicionar GraphQL a uma aplicação não é tudo ou nada. A mesma aplicação pode ser alimentada por várias APIs simultaneamente, caso em que o GraphQL coexistirá com outras APIs pelo tempo que for necessário. Por exemplo, podemos manter as funcionalidades existentes alimentadas por REST e incorporar o GraphQL apenas para todas as novas funcionalidades.
Se você quiser fazer uma migração completa para GraphQL, ela não precisa acontecer de uma vez só. As funcionalidades existentes podem ser migradas lenta mas progressivamente para GraphQL, até que um dia o GraphQL se torne a única API da aplicação.
Portanto, mesmo que você possa criar o schema GraphQL completo já no primeiro dia, não é obrigado a fazê-lo: a qualquer momento, apenas as entidades exigidas pelas funcionalidades precisam estar presentes no schema (por meio de seus tipos, campos e interfaces). Você pode mapeá-las aos poucos, de forma progressiva.
Não deixe a interface carregar o peso da implementação
O servidor GraphQL implementará a lógica de acesso aos dados da aplicação. Ele fará isso chamando funcionalidades do WordPress, como chamar get_posts para recuperar dados de posts. Nessa camada, há código PHP para satisfazer os resolvers.
Um schema GraphQL, porém, é uma interface: ele declara os contratos para acessar dados na API. Não se preocupa com detalhes de implementação: não sabe nada sobre WordPress, sobre a função get_posts, a tabela de banco de dados wp_posts ou queries SQL.
Sendo assim, devemos evitar ao máximo vazar informações entre as camadas.
Isso é importante porque o modelo de dados frequentemente será contaminado por sua implementação. O WordPress fornece um exemplo claro disso com o CPT "attachment", para representar arquivos de mídia como imagens.
Por ser um Custom Post Type, uma imagem é tratada como um post. Assim, podemos ser tentados a representar arquivos de mídia usando o tipo Post, que contém estes campos:
type Post {
id: ID!
title: String
content: String
excerpt: String
}Mas isso pode não ser adequado para a aplicação. O significado do campo "content" é claro para um post, mas não para uma imagem. Muito provavelmente, ele não deveria estar lá.
Uma imagem foi modelada como um CPT no WordPress por conveniência, para que pudesse reutilizar a lógica existente e ser armazenada na tabela wp_posts já existente.
No entanto, conveniente não significa adequado, e pode eventualmente levar a dívida técnica (ou seja, código deficiente que não pode ser corrigido sem gerar uma breaking change, e por isso é mantido na aplicação por mais tempo do que deveria).
Na medida do possível, não queremos manter dívida técnica em nossa aplicação. Sempre que houver oportunidade, devemos corrigi-la. Mapear o modelo de dados para o schema GraphQL oferece essa oportunidade, permitindo-nos corrigir o problema na camada de interface de dados.
(A dívida técnica ainda persistirá no nível da aplicação, então não estamos resolvendo o problema completamente, mas o atenuamos dentro dos nossos meios.)
Vamos colocar essa ideia em prática. Em vez de ter um tipo Post representando arquivos de mídia, faz mais sentido ter um tipo Media, contendo apenas as propriedades que de fato fazem sentido para uma entidade de imagem:
type Media {
id: ID!
src: String!
width: Int
height: Int
}Por baixo dos panos, no nível de implementação, o field resolver ainda executará a função get_posts para resolver entradas do tipo Media, mas isso não é problema do schema GraphQL.
Desacople o schema GraphQL do diagrama do banco de dados
O WordPress é implementado sobre este diagrama entidade-relacionamento de banco de dados:

Precisamos basear o schema GraphQL no diagrama do banco de dados, mas não devemos tentar criar uma réplica 1 a 1. Isso porque tanto o schema GraphQL quanto o diagrama do banco de dados são construídos com determinadas precondições ou limitações que não se aplicarão ao outro.
A seção anterior demonstra um exemplo, onde a tabela wp_posts armazena os dados do CPT de imagem, mas no GraphQL haverá dois tipos distintos, Post e Media.
Vamos considerar outro exemplo: categorias. No WordPress, um post pode ter uma categoria (ou mais), e qualquer CPT também pode criar sua própria categoria. Por exemplo, um CPT chamado "event" terá um "event_category".
Tanto as categorias de posts quanto as categorias de eventos são armazenadas na tabela wp_terms. Isso facilita para o WordPress buscar linhas de um ou outro tipo de categoria ao executar a query SQL.
Por isso, podemos ser tentados a mapear categorias via tipo Category, referenciado tanto por posts quanto por eventos:
type Category {
id: ID!
name: String!
}
type Post {
categories: [Category]!
}
type Event {
categories: [Category]!
}No entanto, um post sempre conterá categorias de posts, e um evento sempre conterá categorias de eventos. Os dados desses dois tipos de categoria podem ser armazenados na mesma tabela do banco de dados, mas não serão misturados no nível da aplicação. Categoria de post e categoria de evento são duas entidades distintas.
O GraphQL possui um sistema de tipos estático. Para aproveitar ao máximo o GraphQL, diferentes entidades no nível da aplicação devem ser modeladas usando tipos diferentes no schema GraphQL.
Neste caso, ao mapear categorias no schema GraphQL, devemos criar um tipo diferente para cada uma delas: PostCategory e EventCategory. Então, o tipo Post só referenciará PostCategory, e o tipo Event só referenciará EventCategory:
type PostCategory {
id: ID!
name: String!
}
type Post {
categories: [PostCategory]!
}
type EventCategory {
id: ID!
name: String!
}
type Event {
categories: [EventCategory]!
}Se ainda quisermos ter uma entidade no schema que abranja todas as categorias, isso pode ser alcançado por meio de uma interface Category:
interface Category {
name: String!
}
type PostCategory implements Category {
id: ID!
name: String!
}
type EventCategory implements Category {
id: ID!
name: String!
}Dessa forma, os usuários que acessam a API terão uma compreensão clara de quais dados serão recuperados, independentemente de como estão mapeados no diagrama do banco de dados e de como estão armazenados no banco de dados.
Depois de termos o schema GraphQL final, podemos perceber que seu formato se assemelhará de alguma forma ao diagrama do banco de dados do WordPress, mas será claramente diferente dele:

Adapte a nomenclatura dos campos seguindo a tipagem estática
Os campos devem, tanto quanto possível, respeitar a mesma nomenclatura que têm na aplicação.
Por exemplo, podemos criar um post com a função wp_insert_post, e o post tem as propriedades "title" e "content". Esses nomes também são bons para o schema GraphQL (mesmo que possam precisar de pequenas modificações), portanto devemos mantê-los:
type MutationRoot {
insertPost(title: String, content: String): Post
}
type Post {
id: ID!
title: String
content: String
}Mas esse nem sempre é o caso. Como vimos anteriormente, custom posts devem ser desacoplados em suas próprias entidades. Assim, enquanto a função get_posts recupera uma lista de qualquer CPT, um campo equivalente posts no tipo raiz do schema recuperará apenas entidades do tipo Post, mas não Page (que também é um CPT):
type QueryRoot {
posts: [Post]!
}Então, como obtemos a lista de todos os posts e páginas? Por meio de outro campo, customPosts, que recupera as entidades de qualquer CPT mapeado sob o tipo union CustomPostUnion:
union CustomPostUnion = Post | Page
type QueryRoot {
customPosts: [CustomPostUnion]!
}A lição importante é esta: a nomenclatura que escolhemos para o schema GraphQL deve ser adaptada ao tipo da entidade recuperada. E devido aos tipos fortes do GraphQL, esse tipo pode ser diferente nas camadas da aplicação e da API.
Neste caso, enquanto no WordPress um "post" pode significar qualquer "custom post type", no GraphQL um "post" é necessariamente um Post. Se um campo recupera custom posts, então o campo no schema GraphQL deve ser chamado de customPosts, não posts. Da mesma forma, se um input recebe um ID para um custom post, deve ser chamado de customPostID, não postID.

Esta lição se aplica a comentários, por exemplo. Um comentário pode ser adicionado a qualquer CPT, não apenas a posts. Portanto, o tipo Comment deve deixar isso claro, contendo o campo customPost (e não post):
type Comment {
id: ID!
customPost: CustomPostUnion!
}Converta valores de string predefinidos em enums, usando maiúsculas quando possível
Os tipos de enumeração são, por convenção, definidos em maiúsculas. Por exemplo, a documentação do graphql.org fornece este exemplo:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}Sempre que precisarmos criar um novo tipo enum, devemos usar maiúsculas para suas constantes definidas. No entanto, ao migrar o modelo de dados da aplicação, podemos encontrar determinados conjuntos de valores predefinidos que podemos mapear via enum, mas cujos valores são strings em minúsculas.
Para dar um exemplo, posts no WordPress têm uma propriedade "status", contendo um dos seguintes valores:
"publish""pending""draft""trash"
Ao mapear essa propriedade no schema, o campo Post.status poderia retornar um String, assim:
type Post {
status: String!
}No entanto, como o status será necessariamente um desses valores predefinidos, e nenhum outro, preferimos mapeá-lo como um enum:
enum Status {
PUBLISH
DRAFT
PENDING
TRASH
}
type Post {
status: Status!
}Agora, podemos ter um problema: o enum PUBLISH será convertido para o valor de string "PUBLISH" na aplicação, e não "publish".
Usando um valor em maiúsculas em vez do esperado em minúsculas, a lógica na aplicação pode ser comprometida. De fato, executar o seguinte código no WordPress não funciona:
// Isso recuperará todos os posts, não apenas os publicados
$published_posts = get_posts([
"post_status" => "PUBLISH",
]);Nesse caso, podemos considerar trocar a convenção pela praticidade, ainda usando um enum para mapear as constantes, mas em minúsculas:
enum Status {
publish
draft
pending
trash
}Em outras palavras, podemos encontrar um meio-termo entre ser rigoroso e ser prático. Devemos usar boas práticas ao construir o schema GraphQL, mas nos permitir desviar delas quando fizer sentido.