Comprensione del Sistema di Tipi GraphQL

L’autore ha selezionato il Fondo Gratuito e Open Source per ricevere una donazione come parte del programma Scrivi per le Donazioni.

Introduzione

GraphQL è una soluzione moderna per facilitare la comunicazione tra un frontend e una fonte di dati. Tutti i dettagli e le capacità di un’implementazione GraphQL sono descritti nello Schema GraphQL. Per scrivere uno schema GraphQL funzionante, è necessario comprendere il Sistema di Tipi GraphQL.

In questo articolo, imparerai i tipi GraphQL: i cinque tipi scalari built-in, Enum, i tipi di wrapping List e Non-Null, i tipi Object, e gli astratti Interface e Union che lavorano insieme a essi. Revisionerai esempi per ciascun tipo e imparerai come usarli per costruire uno schema GraphQL completo.

Prerequisiti

Per ottenere il massimo da questo tutorial, dovresti avere:

Tipi Scalari

Tutti i dati in uno schema GraphQL alla fine si risolvono in vari tipi scalars, che rappresentano valori primitivi. Le risposte GraphQL possono essere rappresentate come un albero, e i tipi scalari sono le foglie alla fine dell’albero. Possono esserci molti livelli in una risposta nidificata, ma l’ultimo livello si risolverà sempre in un tipo scalare (o Enum). GraphQL include cinque tipi scalari integrati: Int, Float, String, Boolean e ID.

Int

Intero è un valore numerico con segno su 32 bit non frazionario. È un intero con segno (positivo o negativo) che non include decimali. Il valore massimo di un intero con segno su 32 bit è 2.147.483.647. Questo è uno dei due scalari incorporati utilizzati per i dati numerici.

Float

A Float is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2. This is the other built-in scalar used for numerical data.

Stringa

A String is a UTF-8 character sequence. The String type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.

Booleano

A Boolean is a true or false value.

ID

Un ID è un identificatore univoco. Questo valore viene sempre serializzato come una stringa, anche se l’ID è numerico. Un tipo ID potrebbe essere comunemente rappresentato con un Identificatore Universale Unico (UUID).

Scala Personalizzate

In aggiunta a queste scalari incorporate, la parola chiave scalar può essere utilizzata per definire una scala personalizzata. Puoi utilizzare scalari personalizzati per creare tipi che hanno una validazione aggiuntiva a livello di server, come Date, Time o Url. Ecco un esempio di definizione di un nuovo tipo Date:

scalar Date

Il server saprà come gestire le interazioni con questo nuovo tipo utilizzando il GraphQLScalarType.

Tipo Enumerativo

Il tipo Enum, noto anche come tipo Enumeratore, descrive un insieme di valori possibili.

Utilizzando il tema dell’API del gioco di fantasia da altri tutorial nella serie Come Gestire i Dati con GraphQL, potresti creare un enum per i Lavori e le Specie dei personaggi del gioco con tutti i valori che il sistema accetterà per essi. Un Enum viene definito con la parola chiave enum, come segue:

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

In questo modo, è garantito che il Job di un personaggio sia FIGHTER o WIZARD e non possa mai essere accidentalmente "purple" o qualche altra stringa casuale, cosa che potrebbe accadere se si utilizzasse un tipo String invece di creare un Enum personalizzato. Gli Enum sono scritti in maiuscolo per convenzione.

Gli Enum possono anche essere utilizzati come valori accettati negli argomenti. Ad esempio, potresti creare un Hand enum per indicare se un’arma è a una mano (come una spada corta) o a due mani (come un’ascia pesante), e utilizzarlo per determinare se può essere equipaggiata una o due armi:

enum Hand {
  SINGLE
  DOUBLE
}

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
  hand: Hand
}

type Query {
  weapons(hand: Hand = SINGLE): [Weapon]
}

L’enum Hand è stato dichiarato con i valori SINGLE e DOUBLE, e l’argomento sul campo weapons ha un valore predefinito di SINGLE, il che significa che se non viene passato alcun argomento, allora si utilizzerà il valore SINGLE.

Tipo Non-Null

Potresti notare che null o undefined, un tipo comune che molti linguaggi considerano un primitivo, manca dall’elenco degli scalari incorporati. Null esiste in GraphQL e rappresenta l’assenza di un valore.

Tutti i tipi in GraphQL sono nullabili per impostazione predefinita e quindi `null` è una risposta valida per qualsiasi tipo. Per rendere un valore obbligatorio, deve essere convertito in un tipo GraphQL Non-Null con un punto esclamativo finale. Non-Null è definito come un modificatore di tipo, che sono tipi utilizzati per modificare il tipo a cui si fa riferimento. Come esempio, `String` è una stringa facoltativa (o nullabile), e `String!` è una stringa obbligatoria (o Non-Null).

Tipo Elenco

A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]) becomes a List type, which is a collection that defines the type of each item in a list.

Come esempio, un tipo definito come `[Int]` sarà una raccolta di tipi `Int`, e `[String]` sarà una raccolta di tipi `String`. Non-Null e Elenco possono essere utilizzati insieme per rendere un tipo sia obbligatorio che definito come un Elenco, come ad esempio `[String]!`.

Tipo Oggetto

Se i tipi scalari GraphQL descrivono le “foglie” alla fine della risposta gerarchica GraphQL, allora i tipi Oggetto descrivono gli “intervalli” intermedi e quasi tutto in uno schema GraphQL è un tipo di Oggetto.

Gli oggetti consistono in un elenco di campi denominati (chiavi) e il tipo di valore a cui ogni campo si risolverà. Gli oggetti sono definiti con la parola chiave type. Devono essere definiti almeno uno o più campi, e i campi non possono iniziare con due trattini bassi (__) per evitare conflitti con il sistema di introspezione di GraphQL.

Nell’esempio dell’API di gioco di fantasia GraphQL, potresti creare un oggetto Fighter per rappresentare un tipo di personaggio in un gioco:

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

In questo esempio, il tipo di oggetto Fighter è stato dichiarato e ha quattro campi denominati:

  • id restituisce un tipo ID non nullo.
  • name restituisce un tipo String non nullo.
  • level restituisce un tipo Int.
  • active restituisce un tipo Boolean non nullo.

Prima della dichiarazione, è possibile aggiungere anche un commento usando virgolette doppie, come in questo esempio: "Un eroe con capacità di combattimento diretto e forza.". Questo apparirà come descrizione per il tipo.

In questo esempio, ogni campo si risolve in un tipo scalare, ma i campi degli oggetti possono anche risolversi in altri tipi di oggetti. Ad esempio, potresti creare un tipo Weapon, e lo schema GraphQL può essere impostato in modo che il campo weapon su Fighter si risolva in un oggetto Weapon:

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
}

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
  weapon: Weapon
}

Gli oggetti possono anche essere annidati nei campi di altri oggetti.

Tipi di operazioni di base

Ci sono tre oggetti speciali che fungono da punti di ingresso nello schema di un GraphQL: Query, Mutation e Subscription. Questi sono conosciuti come tipi di operazioni di base e seguono tutte le stesse regole di qualsiasi altro tipo di oggetto.

La parola chiave schema rappresenta il punto di ingresso in uno schema di GraphQL. I tipi di root Query, Mutation e Subscription saranno sull’oggetto root schema:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

Il tipo Query è richiesto in qualsiasi schema di GraphQL e rappresenta una richiesta di lettura, simile a un GET di un’API REST. Di seguito è riportato un esempio di un oggetto root Query che restituisce una lista di tipi Fighter:

type Query {
  fighters: [Fighter]
}

Le Mutations rappresentano una richiesta di scrittura, che sarebbe analoga a un POST, PUT o DELETE in un’API REST. Nell’esempio seguente, la Mutation ha un campo addFighter con un argomento nominato (input):

type Mutation {
  addFighter(input: FighterInput): Fighter
}

Infine, una Subscription corrisponde a un flusso di eventi, che verrebbe utilizzato insieme a un Websocket in un’app web. Nell’API GraphQL Fantasy, potrebbe essere utilizzato per incontri casuali di battaglia, ad esempio:

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

Si noti che il punto di ingresso schema spesso viene astratto in alcune implementazioni di GraphQL.

Argomenti del Campo

I campi di un oggetto GraphQL sono essenzialmente funzioni che restituiscono un valore e possono accettare argomenti come qualsiasi altra funzione. Gli argomenti del campo sono definiti dal nome dell’argomento seguito dal tipo. Gli argomenti possono essere di qualsiasi tipo non oggetto. In questo esempio, l’oggetto Fighter può essere filtrato dal campo id (che restituisce un tipo ID non nullo):

type Query {
  fighter(id: ID!): Fighter
}

Questo esempio specifico è utile per recuperare un singolo elemento dalla memoria dei dati, ma gli argomenti possono anche essere utilizzati per il filtraggio, la paginazione e altre query più specifiche.

Tipo Interfaccia

Come il tipo di oggetto, il tipo di interfaccia astratta consiste in un elenco di campi nominati e dei rispettivi tipi di valore associati. Le interfacce assomigliano e seguono tutte le stesse regole degli oggetti, ma sono utilizzate per definire un sottoinsieme dell’implementazione di un oggetto.

Finora nello schema hai un oggetto Fighter, ma potresti voler creare anche un Wizard, un Healer e altri oggetti che condivideranno molte delle stesse proprietà ma avranno alcune differenze. In questo caso, puoi utilizzare un’Interfaccia per definire le proprietà che tutti hanno in comune e creare oggetti che sono implementazioni dell’Interfaccia.

Nell’esempio seguente, potresti creare un’Interfaccia BaseCharacter utilizzando la parola chiave interface con tutti i campi che ogni tipo di personaggio possiederà:

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

Ogni tipo di personaggio avrà i campi id, name, level, species e job.

Ora, immagina di avere un tipo Fighter e un tipo Wizard che hanno questi campi condivisi, ma i Fighter usano un Weapon e i Wizard usano Spells. Puoi utilizzare la parola chiave implements per delineare ciascuno come un’implementazione di BaseCharacter, il che significa che devono avere tutti i campi dell’Interfaccia creata:

"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  weapon: Weapon
}

"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  spells: [Spell]
}

Fighter e Wizard sono entrambe implementazioni valide dell’Interfaccia BaseCharacter perché hanno il sottoinsieme richiesto di campi.

Tipo Unione

Un altro tipo astratto che può essere utilizzato con gli oggetti è il tipo Union. Utilizzando la parola chiave union, puoi definire un tipo con una lista di oggetti che sono tutti validi come risposte.

Utilizzando le interfacce create nella sezione precedente, puoi creare un’Unione di Character che definisce un personaggio come Wizard O Fighter:

union Character = Wizard | Fighter

Il carattere uguale (=) imposta la definizione, e il carattere pipe (|) funziona come l’operatore OR. Nota che un’Unione deve essere costituita da oggetti o interfacce. I tipi scalari non sono validi in un’Unione.

Ora, se richiedi una lista di personaggi, potrebbe utilizzare l’Unione di Character e restituire tutti i tipi Wizard e Fighter.

Conclusione

Nel tutorial, hai appreso molti dei tipi che definiscono il sistema di tipi di GraphQL. I tipi più fondamentali sono i tipi scalari, che sono i valori che agiscono come le foglie nell’albero dello schema, e comprendono Int, Float, String, Boolean, ID, e qualsiasi tipo scalare personalizzato che un’implementazione GraphQL decida di creare. Gli Enums sono elenchi di valori costanti validi che possono essere utilizzati quando è necessario avere più controllo su una risposta rispetto a dichiararla semplicemente come String, e sono anch’essi foglie nell’albero dello schema. I tipi List e Non-Null sono noti come modificatori di tipo, o tipi di wrappatura, e possono definire altri tipi come collezioni o obbligatori, rispettivamente. Gli Oggetti sono i rami dell’albero dello schema, e praticamente tutto in uno schema GraphQL è un tipo di Oggetto, inclusi i punti di ingresso query, mutation, e subscription. I tipi di Interfaccia e Unione sono tipi astratti che possono essere utili nella definizione degli Oggetti.

Per approfondire, puoi esercitarti nella creazione e modifica di uno schema GraphQL leggendo il tutorial Come configurare un server API GraphQL in Node.js per avere un ambiente di server GraphQL funzionante.

Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system