Compreensão do Sistema de Tipos do GraphQL

O autor selecionou o Fundo Livre e de Código Aberto para receber uma doação como parte do programa Escreva para Doações.

Introdução

GraphQL é uma solução moderna para facilitar a comunicação entre um front-end e uma fonte de dados. Todos os detalhes e capacidades de uma implementação GraphQL são descritos no Esquema GraphQL. Para escrever um esquema GraphQL funcional, é necessário entender o Sistema de Tipos GraphQL.

Neste artigo, você aprenderá sobre os tipos GraphQL: os cinco tipos escalares incorporados, Enumerações, os tipos de embrulho List e Non-Null, os tipos Object, e os tipos abstratos Interface e Union que trabalham em conjunto com eles. Você revisará exemplos para cada tipo e aprenderá como usá-los para construir um esquema GraphQL completo.

Pré-requisitos

Para tirar o máximo proveito deste tutorial, você deve ter:

Tipos Escalares

Todos os dados em um esquema GraphQL são finalmente resolvidos em vários tipos escalares, que representam valores primitivos. As respostas do GraphQL podem ser representadas como uma árvore, e os tipos escalares são as folhas nas extremidades da árvore. Pode haver muitos níveis em uma resposta aninhada, mas o último nível sempre será resolvido para um tipo escalar (ou Enum). O GraphQL vem com cinco tipos escalares incorporados: Int, Float, String, Boolean e ID.

Int

Inteiro é um valor numérico de 32 bits com sinal e sem fração. É um inteiro com sinal (positivo ou negativo) que não inclui decimais. O valor máximo de um inteiro de 32 bits com sinal é 2.147.483.647. Este é um dos dois escalares embutidos usados para dados numéricos.

Flutuante

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.

String

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

Um ID é um identificador único. Esse valor é sempre serializado como uma string, mesmo se o ID for numérico. Um tipo ID pode ser comumente representado com um Identificador Único Universal (UUID).

Escalares Personalizados

Além desses escalares integrados, a palavra-chave scalar pode ser usada para definir um escalar personalizado. Você pode usar escalares personalizados para criar tipos que tenham validação adicional no nível do servidor, como Data, Hora ou URL. Aqui está um exemplo definindo um novo tipo Data:

scalar Date

O servidor saberá como lidar com interações com esse novo tipo usando o GraphQLScalarType.

Tipo Enumerado

O tipo Enum, também conhecido como tipo Enumerador, descreve um conjunto de valores possíveis.

Usando o tema da API do Jogo de Fantasia de outros tutoriais na série Como Gerenciar Dados com GraphQL, você pode criar um enum para as Profissões e Espécies dos personagens do jogo com todos os valores que o sistema aceitará para eles. Um Enum é definido com a palavra-chave enum, como abaixo:

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

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

Desta forma, é garantido que o Job de um personagem seja FIGHTER ou WIZARD e nunca possa ser acidentalmente "purple" ou alguma outra sequência aleatória, o que seria possível se você usasse um tipo String em vez de criar um Enum personalizado. Enums são escritos em maiúsculas por convenção.

Enums também podem ser usados como os valores aceitos em argumentos. Por exemplo, você pode criar um Hand enum para indicar se uma arma é de uma mão (como uma espada curta) ou de duas mãos (como um machado pesado) e usá-lo para determinar se uma ou duas podem ser equipadas:

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]
}

O Hand enum foi declarado com os valores SINGLE e DOUBLE, e o argumento no campo weapons tem um valor padrão de SINGLE, o que significa que se nenhum argumento for passado, ele voltará para SINGLE.

Tipo Não-Nulo

Você pode notar que null ou undefined, um tipo comum que muitas linguagens consideram primitivo, está ausente na lista de escalares incorporados. Null existe no GraphQL e representa a ausência de um valor.

Todos os tipos no GraphQL são anuláveis por padrão e, portanto, null é uma resposta válida para qualquer tipo. Para tornar um valor obrigatório, ele deve ser convertido para um tipo GraphQL Non-Null com um ponto de exclamação final. Non-Null é definido como um modificador de tipo, que são tipos usados para modificar o tipo ao qual está se referindo. Como exemplo, String é uma string opcional (ou anulável), e String! é uma string obrigatória (ou Non-Null).

Tipo Lista

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.

Como exemplo, um tipo definido como [Int] será uma coleção de tipos Int, e [String] será uma coleção de tipos String. Non-Null e Lista podem ser usados juntos para tornar um tipo tanto obrigatório quanto definido como Lista, como [String]!.

Tipo Objeto

Se os tipos escalares do GraphQL descrevem as “folhas” no final da resposta hierárquica do GraphQL, então os tipos Objeto descrevem os “ramos” intermediários, e quase tudo em um esquema GraphQL é um tipo de Objeto.

Os objetos consistem em uma lista de campos nomeados (chaves) e o tipo de valor que cada campo irá resolver. Os objetos são definidos com a palavra-chave type. Pelo menos um ou mais campos devem ser definidos, e os campos não podem começar com dois sublinhados (__) para evitar conflitos com o sistema de introspecção do GraphQL.

No exemplo da API do Jogo de Fantasia GraphQL, você poderia criar um Objeto Fighter para representar um tipo de personagem em um jogo:

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

Neste exemplo, o tipo de Objeto Fighter foi declarado e possui quatro campos nomeados:

  • id produz um tipo ID Não-Nulo.
  • name produz um tipo String Não-Nulo.
  • level produz um tipo Int.
  • active produz um tipo Boolean Não-Nulo.

Acima da declaração, você também pode adicionar um comentário usando aspas duplas, como neste exemplo: "Um herói com habilidade de combate direto e força.". Isso aparecerá como a descrição para o tipo.

Neste exemplo, cada campo resolve para um tipo escalar, mas os campos de Objeto também podem resolver para outros tipos de Objeto. Por exemplo, você poderia criar um tipo Weapon, e o esquema do GraphQL pode ser configurado onde o campo weapon no Fighter irá resolver para um Objeto 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
}

Os objetos também podem ser aninhados nos campos de outros objetos.

Tipos de Operações Raiz

Existem três Objetos especiais que servem como pontos de entrada em um esquema GraphQL: Consulta (Query), Mutação (Mutation) e Assinatura (Subscription). Estes são conhecidos como tipos de Operações Raiz e seguem todas as mesmas regras que qualquer outro tipo de Objeto.

A palavra-chave schema representa o ponto de entrada em um esquema GraphQL. Seus tipos raiz de Consulta, Mutação e Assinatura estarão no Objeto raiz schema:

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

O tipo Consulta é obrigatório em qualquer esquema GraphQL e representa uma solicitação de leitura, semelhante a um GET de API REST. O seguinte é um exemplo de um Objeto raiz Consulta que retorna uma Lista de tipos Lutador:

type Query {
  fighters: [Fighter]
}

Mutações representam uma solicitação de escrita, que seria análoga a um POST, PUT ou DELETE em uma API REST. No exemplo a seguir, a Mutation tem um campo addFighter com um argumento nomeado (input):

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

Por fim, uma Assinatura corresponde a um fluxo de eventos, que seria usado em conjunto com um Websocket em um aplicativo web. Na API GraphQL Fantasy, talvez possa ser usada para encontros de batalha aleatórios, como mostrado a seguir:

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

Observe que o ponto de entrada schema muitas vezes é abstraído em algumas implementações GraphQL.

Argumentos do Campo

Os campos de um Objeto GraphQL são essencialmente funções que retornam um valor e podem aceitar argumentos como qualquer função. Os argumentos do campo são definidos pelo nome do argumento seguido pelo tipo. Os argumentos podem ser de qualquer tipo não-Objeto. Neste exemplo, o Objeto Fighter pode ser filtrado pelo campo id (que resolve para um tipo ID não nulo):

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

Este exemplo específico é útil para buscar um único item no armazenamento de dados, mas os argumentos também podem ser usados para filtragem, paginação e outras consultas mais específicas.

Tipo de Interface

Assim como o tipo Objeto, o tipo de Interface abstrato consiste em uma lista de campos nomeados e seus tipos de valor associados. As interfaces têm a mesma aparência e seguem todas as mesmas regras que os Objetos, mas são usadas para definir um subconjunto da implementação de um Objeto.

Até agora, no seu esquema, você tem um objeto Fighter, mas também pode querer criar um Wizard, um Healer e outros objetos que compartilharão muitos dos mesmos campos, mas terão algumas diferenças. Nesse caso, você pode usar uma interface para definir os campos que todos eles têm em comum e criar objetos que são implementações da interface.

No exemplo a seguir, você poderia criar uma interface BaseCharacter usando a palavra-chave interface com todos os campos que todo tipo de personagem possuirá:

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

Cada tipo de personagem terá os campos id, name, level, species e job.

Agora, imagine que você tenha um tipo Fighter e um tipo Wizard que possuem esses campos compartilhados, mas os Fighters usam uma Weapon e os Wizards usam Spells. Você pode usar a palavra-chave implements para delinear cada um como uma implementação de BaseCharacter, o que significa que eles devem ter todos os campos da interface criada:

"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 são ambas implementações válidas da interface BaseCharacter porque possuem o subconjunto necessário de campos.

Union Type

Outro tipo abstrato que pode ser usado com Objetos é o tipo Union. Usando a palavra-chave union, você pode definir um tipo com uma lista de Objetos que são todos válidos como respostas.

Usando as Interfaces criadas na seção anterior, você pode criar uma União Character que define um personagem como um Wizard OU um Fighter:

union Character = Wizard | Fighter

O caractere igual (=) define a definição, e o caractere de barra vertical (|) funciona como a declaração de OR. Note que uma União deve consistir em Objetos ou Interfaces. Tipos escalares não são válidos em uma União.

Agora, se você consultar uma lista de personagens, ela pode usar a União Character e retornar todos os tipos Wizard e Fighter.

Conclusão

Neste tutorial, aprendeste sobre muitos dos tipos que definem o sistema de tipos do GraphQL. Os tipos mais fundamentais são os tipos escalares, que são os valores que atuam como as folhas na árvore do esquema, e consistem em Int, Float, String, Boolean, ID, e qualquer tipo escalar personalizado que uma implementação do GraphQL decida criar. Enums são listas de valores constantes válidos que podem ser usadas quando você precisa de mais controle sobre uma resposta do que simplesmente declará-la como uma String, e também são folhas na árvore do esquema. Tipos de Lista e Não-Nulo são conhecidos como modificadores de tipo, ou tipos de envolvimento, e podem definir outros tipos como coleções ou obrigatórios, respectivamente. Objetos são os ramos da árvore do esquema, e quase tudo em um esquema do GraphQL é um tipo de Objeto, incluindo as entradas query, mutation e subscription. Tipos de Interface e União são tipos abstratos que podem ser úteis na definição de Objetos.

Para aprender mais, você pode praticar criando e modificando um esquema do GraphQL lendo o tutorial Como Configurar um Servidor de API GraphQL em Node.js para ter um ambiente de servidor GraphQL funcional.

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