Concetti, idee, strategie
Concetti, idee, strategieMappare lo schema GraphQL per il tuo sito, tema o plugin WordPress

Mappare lo schema GraphQL per il tuo sito, tema o plugin WordPress

Hai quindi deciso di iniziare a usare GraphQL per il tuo sito WordPress esistente. Ottimo! Che venga utilizzato per nuove funzionalità o per funzionalità esistenti, GraphQL dovrà interagire con il livello dati sottostante, per il quale dovrai mappare il modello di dati della tua applicazione (che si tratti di codice PHP personalizzato nel tuo sito WordPress, nel tuo tema o nel tuo plugin) nello schema GraphQL.

Come deve essere effettuato il mapping? Deve essere realizzato tutto in una volta? Deve essere una replica esatta del modello di dati esistente? E per quanto riguarda la correzione di un nome inappropriato durante il processo? E riguardo al debito tecnico, bisogna conservarlo o occuparsene?

Esploriamo alcune strategie per mappare il modello di dati di un'applicazione WordPress esistente in uno schema GraphQL.

Mappa lo schema al tuo ritmo

Aggiungere GraphQL a un'applicazione non è tutto o niente. La stessa applicazione potrebbe essere alimentata da più API simultaneamente, nel qual caso GraphQL coesisterà con altre API finché necessario. Ad esempio, potremmo conservare le funzionalità esistenti alimentate da REST e incorporare GraphQL solo per tutte le nuove funzionalità.

Se desideri effettuare una migrazione completa verso GraphQL, ciò non deve necessariamente avvenire tutto in una volta. Le funzionalità esistenti potrebbero essere migrate lentamente ma costantemente verso GraphQL, fino al giorno in cui GraphQL diventerà l'unica API dell'applicazione.

Pertanto, anche se puoi creare lo schema GraphQL completo già dal primo giorno, non sei obbligato a farlo: in qualsiasi momento, solo le entità richieste dalle funzionalità devono essere presenti nello schema (tramite i loro tipi, campi e interfacce). Puoi mapparle man mano, in modo progressivo.

Non lasciare che l'interfaccia porti il peso dell'implementazione

Il server GraphQL implementerà la logica di accesso ai dati dell'applicazione. Lo farà chiamando funzionalità di WordPress, come chiamare get_posts per recuperare i dati dei post. A questo livello, c'è codice PHP per soddisfare i resolver.

Uno schema GraphQL, tuttavia, è un'interfaccia: dichiara i contratti per accedere ai dati nell'API. Non si preoccupa dei dettagli di implementazione: non sa nulla di WordPress, né della funzione get_posts, della tabella di database wp_posts o delle query SQL.

In quanto tale, dovremmo evitare il più possibile di far trapelare informazioni tra i livelli.

Questo è importante, perché il modello di dati sarà spesso intaccato dalla sua implementazione. WordPress ne fornisce un esempio chiaro con il CPT "attachment", per rappresentare file multimediali come le immagini.

Poiché si tratta di un Custom Post Type, un'immagine viene trattata come un post. Potremmo quindi essere tentati di rappresentare i file multimediali utilizzando il tipo Post, che contiene questi campi:

type Post {
  id: ID!
  title: String
  content: String
  excerpt: String
}

Ma questo potrebbe non essere appropriato per l'applicazione. Il significato del campo "content" è chiaro per un post, ma non lo è per un'immagine. Molto probabilmente non dovrebbe trovarsi lì.

Un'immagine è stata modellata come un CPT in WordPress perché era comodo, così poteva riutilizzare la logica esistente ed essere memorizzata nella tabella wp_posts esistente.

Tuttavia, comodo non significa appropriato e può alla fine portare a un debito tecnico (cioè codice deficitario che non può essere corretto senza produrre un cambiamento dirompente, e che viene quindi conservato nell'applicazione più a lungo di quanto dovrebbe).

Per quanto possibile, non vogliamo conservare il debito tecnico nella nostra applicazione. Ogni volta che se ne presenta l'occasione, dovremmo correggerlo. Il mapping del modello di dati verso lo schema GraphQL offre proprio tale opportunità, permettendoci di correggere il problema a livello del livello di interfaccia dei dati.

(Il debito tecnico persisterà comunque a livello dell'applicazione, quindi non risolviamo completamente il problema, ma lo attenuiamo nei limiti dei nostri mezzi.)

Mettiamo in pratica questa idea. Invece di avere un tipo Post che rappresenta i file multimediali, è più sensato avere un tipo Media, contenente solo le proprietà che hanno senso per un'entità immagine:

type Media {
  id: ID!
  src: String!
  width: Int
  height: Int
}

Sotto il cofano, a livello di implementazione, il field resolver continuerà a eseguire la funzione get_posts per risolvere le voci di tipo Media, ma questo non riguarda lo schema GraphQL.

Disaccoppia lo schema GraphQL dal diagramma del database

WordPress è implementato su questo diagramma entità-relazione del database:

Diagramma entità-relazione del database in WordPress

Dobbiamo basare lo schema GraphQL sul diagramma del database, ma non dovremmo tentare di creare una replica 1 a 1. Questo perché sia lo schema GraphQL sia il diagramma del database sono costruiti con determinate precondizioni o limitazioni, che non si applicheranno all'altro.

La sezione precedente ne dimostra un esempio, in cui la tabella wp_posts memorizza i dati del CPT immagine, ma in GraphQL ci saranno due tipi distinti, Post e Media.

Consideriamo un altro esempio: le categorie. In WordPress, un post può avere una categoria (o più), e qualsiasi CPT può anche creare la propria categoria. Ad esempio, un CPT chiamato "event" avrà un "event_category".

Sia le categorie dei post sia le categorie degli eventi sono memorizzate nella tabella wp_terms. Questo facilita il recupero da parte di WordPress delle righe dell'uno o dell'altro tipo di categoria durante l'esecuzione della query SQL.

Potremmo quindi essere tentati di mappare le categorie tramite il tipo Category, referenziato sia dai post sia dagli eventi:

type Category {
  id: ID!
  name: String!
}
 
type Post {
  categories: [Category]!
}
 
type Event {
  categories: [Category]!
}

Tuttavia, un post conterrà sempre categorie di post, e un evento conterrà sempre categorie di eventi. I dati di questi due tipi di categoria possono essere memorizzati nella stessa tabella di database, ma non saranno mescolati a livello dell'applicazione. Categoria di post e categoria di evento sono due entità distinte.

GraphQL possiede un sistema di tipi statico. Per sfruttare al massimo GraphQL, le diverse entità a livello dell'applicazione devono essere modellate utilizzando tipi diversi nello schema GraphQL.

In questo caso, durante il mapping delle categorie nello schema GraphQL, dovremmo creare un tipo diverso per ciascuna di esse: PostCategory ed EventCategory. Allora, il tipo Post referenzierà solo PostCategory, e il tipo Event referenzierà solo EventCategory:

type PostCategory {
  id: ID!
  name: String!
}
 
type Post {
  categories: [PostCategory]!
}
 
type EventCategory {
  id: ID!
  name: String!
}
 
type Event {
  categories: [EventCategory]!
}

Se vogliamo comunque avere un'entità nello schema che comprenda tutte le categorie, ciò può essere realizzato tramite un'interfaccia Category:

interface Category {
  name: String!
}
 
type PostCategory implements Category {
  id: ID!
  name: String!
}
 
type EventCategory implements Category {
  id: ID!
  name: String!
}

In questo modo, gli utenti che accedono all'API avranno una chiara comprensione dei dati che verranno recuperati, indipendentemente da come sono mappati nel diagramma del database e da come sono memorizzati nel database.

Una volta ottenuto lo schema GraphQL finale, possiamo constatare che la sua forma assomiglierà in qualche modo al diagramma del database di WordPress, pur essendone chiaramente diversa:

Schema GraphQL

Adattare la denominazione dei campi, seguendo la tipizzazione statica

I campi dovrebbero, per quanto possibile, rispettare la stessa denominazione che hanno nell'applicazione.

Ad esempio, possiamo creare un post con la funzione wp_insert_post, e il post ha le proprietà "title" e "content". Questi nomi vanno bene anche per lo schema GraphQL (anche se possono richiedere lievi modifiche), quindi dovremmo conservarli:

type MutationRoot {
  insertPost(title: String, content: String): Post
}
 
type Post {
  id: ID!
  title: String
  content: String
}

Ma non è necessariamente sempre così. Come abbiamo visto in precedenza, i post personalizzati devono essere disaccoppiati nelle proprie entità. Così, mentre la funzione get_posts recupera un elenco di qualsiasi CPT, un campo equivalente posts nel tipo radice dello schema recupererà solo entità di tipo Post, ma non Page (che è anch'esso un CPT):

type QueryRoot {
  posts: [Post]!
}

Allora, come otteniamo l'elenco di tutti i post e di tutte le pagine? Tramite un altro campo, customPosts, che recupera le entità di qualsiasi CPT mappato sotto il tipo union CustomPostUnion:

union CustomPostUnion = Post | Page
 
type QueryRoot {
  customPosts: [CustomPostUnion]!
}

La lezione importante è questa: la denominazione che scegliamo per lo schema GraphQL deve essere adattata al tipo dell'entità recuperata. E a causa dei tipi forti di GraphQL, tale tipo può essere diverso a livello dell'applicazione e a livello dell'API.

In questo caso, mentre in WordPress un "post" potrebbe indicare qualsiasi "custom post type", in GraphQL un "post" è necessariamente un Post. Se un campo recupera post personalizzati, allora il campo nello schema GraphQL deve chiamarsi customPosts, non posts. Allo stesso modo, se un input riceve un ID per un post personalizzato, deve chiamarsi customPostID, non postID.

Mapping per il campo customPosts

Questa lezione si applica ai commenti, ad esempio. Un commento può essere aggiunto a qualsiasi CPT, non solo ai post. Allora, il tipo Comment deve precisarlo chiaramente, contenendo il campo customPost (e non post):

type Comment {
  id: ID!
  customPost: CustomPostUnion!
}

Convertire i valori stringa predefiniti in enum, usando le maiuscole se possibile

I tipi di enumerazione sono, per convenzione, definiti in maiuscolo. Ad esempio, la documentazione di graphql.org fornisce questo esempio:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

Ogni volta che dobbiamo creare un nuovo tipo enum, dovremmo usare le maiuscole per le sue costanti definite. Tuttavia, durante la migrazione del modello di dati dall'applicazione, possiamo incontrare determinati insiemi di valori predefiniti che possiamo mappare tramite un enum, ma i cui valori sono stringhe in minuscolo.

Per fornire un esempio, i post in WordPress hanno una proprietà "status", contenente uno dei seguenti valori:

  • "publish"
  • "pending"
  • "draft"
  • "trash"

Durante il mapping di questa proprietà nello schema, il campo Post.status potrebbe restituire un String, in questo modo:

type Post {
  status: String!
}

Tuttavia, poiché lo status sarà necessariamente uno di questi valori predefiniti, e nessun altro, preferiamo mapparlo come un enum:

enum Status {
  PUBLISH
  DRAFT
  PENDING
  TRASH
}
 
type Post {
  status: Status!
}

Ora, potremmo avere un problema: l'enum PUBLISH verrà convertito nel valore stringa "PUBLISH" nell'applicazione, e non "publish".

Utilizzando un valore in maiuscolo invece di quello in minuscolo previsto, la logica nell'applicazione può essere compromessa. Infatti, eseguire il seguente codice in WordPress non funziona:

// Questo recupererà tutti i post, non solo quelli pubblicati
$published_posts = get_posts([
  "post_status" => "PUBLISH",
]);

In questo caso, possiamo considerare di scambiare la convenzione con la comodità, utilizzando comunque un enum per mappare le costanti, ma in minuscolo:

enum Status {
  publish
  draft
  pending
  trash
}

In altre parole, possiamo trovare una via di mezzo tra l'essere rigorosi e l'essere pratici. Dovremmo utilizzare le best practice durante la costruzione dello schema GraphQL, ma permetterci di discostarcene ogni volta che ha senso.