Agora, o GraphQL é, sem exagero, o último pio do modo de TI. E se você ainda não sabe que tipo de tecnologia é, como usá-la e por que pode ser útil para você, o artigo que estamos publicando hoje foi escrito para você. Aqui, abordaremos os conceitos básicos do GraphQL usando um exemplo de implementação de esquema de dados para a API de uma empresa de pipoca. Em particular, vamos falar sobre tipos de dados, consultas e mutações.

O que é o GraphQL?
GraphQL é uma linguagem de consulta usada por aplicativos clientes para trabalhar com dados. O GraphQL está associado a um conceito como "esquema" - é isso que permite organizar a criação, leitura, atualização e exclusão de dados em sua aplicação (ou seja, temos quatro funções básicas usadas ao trabalhar com data warehouses, que geralmente são chamadas pelo acrônimo CRUD - criar, ler, atualizar, excluir).
Foi dito acima que o GraphQL é usado para trabalhar com dados em "sua aplicação" e não "em seu banco de dados". O fato é que o GraphQL é um sistema independente das fontes de dados, ou seja, não importa onde está organizado para organizar seu trabalho.
Se você procurar, sem saber nada sobre o GraphQL, o nome dessa tecnologia, pode parecer que estamos diante de algo muito complicado e confuso. O nome da tecnologia tem a palavra "Gráfico". Isso significa que, para dominá-lo, você precisa aprender a trabalhar com bancos de dados de gráficos? E o fato de o nome conter "QL" (que pode significar "linguagem de consulta", ou seja, "linguagem de consulta"), significa que aqueles que desejam usar o GraphQL terão que aprender uma linguagem de programação completamente nova?
Esses medos não são inteiramente justificados. Para tranquilizá-lo - esta é a verdade cruel sobre essa tecnologia: são apenas solicitações
GET
ou
POST
embelezadas. Embora o GraphQL, em geral, introduz alguns novos conceitos relacionados à organização e interação de dados, os mecanismos internos dessa tecnologia se baseiam nas boas e antigas solicitações HTTP.
Repensando a tecnologia REST
A flexibilidade é o que diferencia a tecnologia GraphQL da conhecida tecnologia REST. Ao usar o REST, se tudo for feito corretamente, os terminais geralmente são criados levando em consideração as características de um determinado tipo de dado de aplicativo ou recurso.
Por exemplo, ao executar uma solicitação
GET
para o terminal
/api/v1/flavors
espera-se que ele envie uma resposta parecida com esta:
[ { "id": 1, "name": "The Lazy Person's Movie Theater", "description": "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }, { "id": 2, "name": "What's Wrong With You Caramel", "description": "You're a crazy person that likes sweet popcorn. Congratulations." }, { "id": 3, "name": "Gnarly Chili Lime", "description": "The kind of popcorn you make when you need a good smack in the face."} ]
Não há nada de catastroficamente errado com essa resposta, mas vamos pensar na interface do usuário, ou melhor, em como pretendemos consumir esses dados.
Se quisermos exibir uma lista simples na interface que contenha apenas os nomes dos tipos disponíveis de pipoca (e nada mais), essa lista poderá se parecer com a mostrada abaixo.
Lista de tipos de pipocaPode-se ver que aqui estamos em uma situação difícil. Podemos decidir não usar o campo de
description
, mas vamos nos sentar e fingir que não enviamos esse campo para o cliente? O que mais podemos fazer? E quando, depois de alguns meses, eles nos perguntarem por que o aplicativo é tão lento para os usuários, basta deixar o cara e não encontrar mais a gerência da empresa para a qual fizemos esse aplicativo.
De fato, o fato de o servidor enviar dados desnecessários em resposta a uma solicitação do cliente não é totalmente nossa culpa. O REST é um mecanismo de aquisição de dados que pode ser comparado a um restaurante no qual o garçom pergunta ao visitante: “O que você quer?”. E, não prestando atenção especial a seus desejos, ele diz: “Trarei o que temos” .
Se deixarmos de lado as piadas, em aplicações reais isso pode levar a situações problemáticas. Por exemplo, podemos exibir várias informações adicionais sobre cada tipo de pipoca, como informações de preço, informações sobre o fabricante ou informações nutricionais (“Vegan Popcorn!”). Ao mesmo tempo, pontos de extremidade REST inflexíveis dificultam a obtenção de dados específicos sobre tipos específicos de pipoca, o que leva a uma carga excessivamente alta nos sistemas e ao fato de que as soluções resultantes estão longe daquelas pelas quais os desenvolvedores podem se orgulhar.
Como a tecnologia GraphQL melhora para que a tecnologia REST foi usada
Uma análise superficial da situação descrita acima pode parecer que somos apenas um problema menor. "O que há de errado em enviar dados desnecessários ao cliente?" Para entender até que ponto “dados desnecessários” podem ser um grande problema, lembre-se de que o GraphQL foi desenvolvido pelo Facebook. Essa empresa precisa atender a milhões de solicitações por segundo.
O que isso significa? E o fato de que, com esses volumes, tudo é importante.
O GraphQL, se continuarmos a analogia com um restaurante, em vez de "carregar" para o visitante "o que é", traz exatamente o que o visitante pede.
Podemos obter uma resposta do GraphQL que se concentra no contexto em que os dados são usados. Nesse caso, não precisamos adicionar pontos de acesso "únicos" ao sistema, executar muitas solicitações ou escrever estruturas condicionais de vários andares.
Como o GraphQL funciona?
Como já dissemos, o GraphQL conta com solicitações simples de
GET
ou
POST
para transmitir dados ao cliente e recebê-los. Se considerarmos essa idéia em mais detalhes, verifica-se que existem dois tipos de consultas no GraphQL. O primeiro tipo inclui solicitações de leitura de dados, que na terminologia do GraphQL são chamadas simplesmente de consultas e se referem à letra R (leitura, leitura) da sigla CRUD. As consultas do segundo tipo são solicitações de modificação de dados, chamadas mutações no GraphQL. Eles se relacionam com as caixas de eixo C, U e D da sigla CRUD, ou seja, elas são usadas para criar, criar, atualizar e excluir registros.
Todos esses pedidos e mutações são enviados para o URL do servidor GraphQL, que, por exemplo, pode parecer
https://myapp.com/graphql
, na forma de pedidos
GET
ou
POST
. Falaremos mais sobre isso abaixo.
Consultas GraphQL
As consultas do GraphQL são entidades que representam uma solicitação ao servidor para receber determinados dados. Por exemplo, temos uma certa interface de usuário que queremos preencher com dados. Para esses dados, recorremos ao servidor, executando a solicitação. Ao usar APIs REST tradicionais, nossa solicitação assume a forma de uma solicitação GET. Ao trabalhar com o GraphQL, uma nova sintaxe de consulta é usada:
{ flavors { name } }
Isso é JSON? Ou um objeto JavaScript? Nem um nem o outro. Como já dissemos, em nome da tecnologia GraphQL, as duas últimas letras, QL, significam "linguagem de consulta", ou seja, a linguagem de consulta. Este é, literalmente, um novo idioma para escrever solicitações de dados. Tudo isso soa como uma descrição de algo bastante complicado, mas na verdade não há nada complicado aqui. Vamos analisar a consulta acima:
{ // , . }
Todas as solicitações começam com uma "solicitação raiz" e o que você precisa obter durante a execução da solicitação é chamado de campo. Para evitar confusão, é melhor chamar essas entidades de "campos de consulta no esquema". Se esse nome lhe parecer incompreensível - espere um pouco - abaixo, falaremos mais sobre o esquema. Aqui, na consulta raiz, solicitamos o campo
flavors
.
{ flavors { // , flavor. } }
Ao solicitar um determinado campo, também devemos indicar os campos aninhados que precisam ser recebidos para cada objeto que vem em resposta à solicitação (mesmo que se espere que apenas um objeto venha em resposta à solicitação).
{ flavors { name } }
Qual será o resultado? Depois de enviarmos essa solicitação ao servidor GraphQL, obteremos uma resposta organizada e bem-formada, como a seguir:
{ "data": { "flavors": [ { "name": "The Lazy Person's Movie Theater" }, { "name": "What's Wrong With You Caramel" }, { "name": "Gnarly Chili Lime" } ] } }
Observe que não há nada supérfluo. Para esclarecer, aqui está outra solicitação que é executada para obter dados em outra página do aplicativo:
{ flavors { id name description } }
Em resposta a esta solicitação, obtemos o seguinte:
{ "data": { "flavors": [ { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }, { "id": 2, "name": "What's Wrong With You Caramel", description: "You're a crazy person that likes sweet popcorn. Congratulations." }, { "id": 3, "name": "Gnarly Chili Lime", description: "A friend told me this would taste good. It didn't. It burned my kernels. I haven't had the heart to tell him." } ] } }
Como você pode ver, o GraphQL é uma tecnologia muito poderosa. Voltamos para o mesmo terminal e as respostas para os pedidos correspondem exatamente ao que é necessário para preencher a página a partir da qual esses pedidos são executados.
Se precisarmos obter apenas um objeto de
flavor
, podemos tirar vantagem do fato de o GraphQL poder trabalhar com argumentos:
{ flavors(id: "1") { id name description } }
Aqui, definimos rigidamente o identificador específico (
id
) do objeto no código, as informações sobre as quais precisamos, mas, nesses casos, podemos usar identificadores dinâmicos:
query getFlavor($id: ID) { flavors(id: $id) { id name description } }
Aqui, na primeira linha,
getFlavor
um nome à solicitação (o nome é escolhido arbitrariamente,
getFlavor
pode ser substituído por algo como
pizza
e a solicitação permanecerá operacional) e declaramos as variáveis que a solicitação espera. Nesse caso, supõe-se que o identificador (
id
) do
ID
tipo escalar seja passado para a solicitação (falaremos sobre os tipos abaixo).
Independentemente de um
id
estático ou dinâmico
id
usado ao executar uma solicitação, veja como será a resposta a uma solicitação semelhante:
{ "data": { "flavors": [ { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" } ] } }
Como você pode ver, tudo é organizado de maneira muito conveniente. Você provavelmente está começando a pensar em usar o GraphQL em seu próprio projeto. E, embora o que já falamos pareça maravilhoso, a beleza do GraphQL realmente se manifesta onde funciona com campos aninhados. Suponha que em nosso esquema haja outro campo chamado
nutrition
que contenha informações sobre o valor nutricional de diferentes tipos de pipoca:
{ flavors { id name nutrition { calories fat sodium } } }
Pode parecer que em nosso data warehouse, cada objeto de
flavor
contenha um objeto de
nutrition
aninhado. Mas isso não é inteiramente verdade. Usando o GraphQL, você pode combinar chamadas para fontes de dados independentes, mas conectadas, em uma consulta, o que permite obter respostas que oferecem a conveniência de trabalhar com dados incorporados sem a necessidade de desnormalizar o banco de dados:
{ "data": { "flavors": [ { "id": 1, "name": "The Lazy Person's Movie Theater", "nutrition": { "calories": 500, "fat": 12, "sodium": 1000 } }, ... ] } }
Isso pode aumentar significativamente a produtividade do programador e a velocidade do sistema.
Até agora, falamos sobre pedidos de leitura. E as solicitações de atualização de dados? Usá-los nos dá a mesma conveniência?
Mutações GraphQL
Enquanto as consultas do GraphQL carregam dados, as mutações são responsáveis por fazer alterações nos dados. Mutações podem ser usadas na forma do mecanismo RPC (Remote Procedure Call) básico para resolver várias tarefas, como enviar dados do usuário para uma API de terceiros.
Ao descrever mutações, é usada uma sintaxe semelhante à que usamos ao gerar consultas:
mutation updateFlavor($id: ID!, $name: String, $description: String) { updateFlavor(id: $id, name: $name, description: $description) { id name description } }
Aqui declaramos a mutação
updateFlavor
, especificando algumas variáveis -
id
,
name
e
description
. Agindo de acordo com o mesmo esquema usado para descrever as consultas, "elaboramos" campos variáveis (mutação raiz) usando a palavra-chave
mutation
, seguida por um nome descrevendo a mutação e um conjunto de variáveis necessárias para formar a solicitação de alteração de dados correspondente.
Essas variáveis incluem o que estamos tentando mudar ou que mutação queremos causar. Observe também que após a mutação, podemos solicitar o retorno de alguns campos.
Nesse caso, precisamos obter, após alterar o registro, os campos
id
,
name
e
description
. Isso pode ser útil ao desenvolver algo como interfaces otimistas, eliminando a necessidade de atender a uma solicitação para receber dados alterados após alterá-los.
Projetando um esquema e conectando-o a um servidor GraphQL
Até agora, falamos sobre como o GraphQL funciona no cliente e como eles executam consultas. Agora vamos falar sobre como responder a esses pedidos.
Servidor GraphQL
Para executar uma consulta GraphQL, você precisa de um servidor GraphQL para o qual possa enviar essa consulta. Um servidor GraphQL é um servidor HTTP comum (se você escreve em JavaScript, pode ser um servidor criado usando Express ou Hapi), ao qual um diagrama GraphQL está anexado.
import express from 'express' import graphqlHTTP from 'express-graphql' import schema from './schema' const app = express() app.use('/graphql', graphqlHTTP({ schema: schema, graphiql: true })) app.listen(4000)
Ao “ingressar” em um esquema, queremos dizer um mecanismo que transmite solicitações recebidas do cliente pelo esquema e retorna respostas a ele. É como um filtro de ar através do qual o ar entra na sala.
O processo de "filtragem" está associado a solicitações ou mutações enviadas pelo cliente ao servidor. As consultas e mutações são resolvidas usando funções relacionadas aos campos definidos na consulta raiz ou na mutação raiz do esquema.
A descrição acima é um exemplo de estrutura de servidor HTTP criada usando a biblioteca Express JavaScript. Usando a função
express-graphql
do Facebook, "anexamos" o esquema (presume-se que ele esteja descrito em um arquivo separado) e executamos o servidor na porta 4000. Ou seja, os clientes, falando sobre o uso local desse servidor, poderão enviar solicitações via endereço
http://localhost:4000/graphql
.
▍ Tipos e resolvedores de dados
Para garantir a operação do servidor GraphQL, você precisa preparar o esquema e anexá-lo a ele.
Lembre-se de que falamos sobre declarar campos em uma consulta raiz ou em uma mutação raiz acima.
import gql from 'graphql-tag' import mongodb from '/path/to/mongodb' // - . , `mongodb` MongoDB. const schema = { typeDefs: gql` type Nutrition { flavorId: ID calories: Int fat: Int sodium: Int } type Flavor { id: ID name: String description: String nutrition: Nutrition } type Query { flavors(id: ID): [Flavor] } type Mutation { updateFlavor(id: ID!, name: String, description: String): Flavor } `, resolvers: { Query: { flavors: (parent, args) => { // , args , { id: '1' } return mongodb.collection('flavors').find(args).toArray() }, }, Mutation: { updateFlavor: (parent, args) => { // , args { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' } // . mongodb.collection('flavors').update(args) // flavor . return mongodb.collection('flavors').findOne(args.id) }, }, Flavor: { nutrition: (parent) => { return mongodb.collection('nutrition').findOne({ flavorId: parent.id, }) } }, }, } export default schema
A definição de campos em um esquema GraphQL consiste em duas partes - de declarações de tipo (
typeDefs
) e
resolver
. A
typeDefs
contém declarações de tipo para os dados usados no aplicativo. Por exemplo, falamos anteriormente sobre uma solicitação para obter uma lista de objetos de
flavor
do servidor. Para fazer uma solicitação semelhante ao nosso servidor, você deve executar as três etapas a seguir:
- Informe ao esquema a aparência dos dados do objeto de
flavor
(no exemplo acima, ele se parece com um anúncio do type Flavor
). - Declare o campo no campo raiz do
type Query
(esta é a propriedade flavors
do type Query
Valor da type Query
). - Declare uma função do reconhecedor de objeto
resolvers.Query
gravada de acordo com os campos declarados no campo raiz do type Query
.
Agora vamos prestar atenção ao
typeDefs
. Aqui, fornecemos as informações do esquema sobre a forma dos nossos dados. Em outras palavras, informamos ao GraphQL as várias propriedades que podem estar contidas nas entidades do tipo correspondente.
type Flavor { id: ID name: String description: String nutrition: Nutrition }
Uma declaração de
type Flavor
indica que um objeto de
flavor
pode conter um campo de
ID
do tipo
ID
, um campo de
name
do tipo
String
, um campo de
description
do tipo
String
e um campo de
nutrition
do tipo
Nutrition
.
No caso da
nutrition
usamos aqui o nome de um tipo diferente declarado em
typeDefs
. Aqui, o
type Nutrition
construção
type Nutrition
descreve a forma de dados nutricionais da pipoca.
Preste atenção ao fato de que aqui, como no início deste material, estamos falando de um "aplicativo" e não de um "banco de dados". No exemplo acima, supõe-se que tenhamos um banco de dados, mas os dados no aplicativo podem vir de qualquer fonte. Pode até ser uma API de terceiros ou um arquivo estático.
Assim como fizemos na declaração de
type Flavor
, aqui indicamos os nomes dos campos que estarão contidos nos objetos de
nutrition
, usando, como os tipos de dados desses campos (propriedades), o que no GraphQL é chamado de tipos de dados escalares. No momento da redação deste artigo, o GraphQL suportava
5 tipos de dados escalares integrados :
Int
: número inteiro de 32 bits assinado.Float
: um número de ponto flutuante de precisão dupla com um sinal.String
: uma sequência de caracteres codificados em UTF-8.Boolean
: Booleano true
ou false
.ID
: um identificador exclusivo usado frequentemente para carregar objetos repetidamente ou como uma chave no cache. Os valores do tipo ID
serializados da mesma maneira que as cadeias, no entanto, uma indicação de que um tipo de ID
possui um valor é enfatizada pelo fato de que esse valor não se destina a ser mostrado às pessoas, mas para uso em programas.
Além desses tipos escalares, também podemos atribuir propriedades aos tipos que nos definimos. Foi exatamente isso que fizemos ao atribuir a propriedade
nutrition
descrita no
type Flavor
, tipo de construção
Nutrition
.
type Query { flavors(id: ID): [Flavor] }
No
type Query
construção
type Query
, que descreve o tipo raiz de
Query
(a "consulta raiz" de que falamos anteriormente), declaramos o nome do campo que pode ser solicitado. Ao declarar esse campo, além disso, juntamente com o tipo de dados que esperamos retornar, especificamos os argumentos que podem surgir na solicitação.
Neste exemplo, esperamos um possível recebimento do argumento
id
de um
ID
tipo escalar. Em resposta a essa solicitação, espera-se uma matriz de objetos cujo dispositivo se assemelha a um dispositivo do tipo
Flavor
.
▍Conectando o reconhecedor de consultas
,
type Query
field
, , -.
— , GraphQL, , «».
resolvers
,
Query
, ,
flavors
, .
flavors
,
type Query
.
typeDefs: gql`…`, resolvers: { Query: { flavors: (parent, args) => { // , args { id: '1' } return mongodb.collection('flavors').find(args).toArray() }, }, … },
- .
parent
— , ,
args
, .
context
, . «» ( — , ).
, , . GraphQL « » . , , .
GraphQL , , . JSON-, JSON-, ( GraphQL ).
-
flavors
MongoDB,
args
( )
.find()
, , .
▍
-, GraphQL, , , ,
nutrition
. , ,
Nutrition
, , , ,
flavor
. , / .
GraphQL ,
type Flavor
nutrition
type Nutrition
, . , ,
flavor
.
typeDefs: gql` type Nutrition { flavorId: ID calories: Int fat: Int sodium: Int } type Flavor { […] nutrition: Nutrition } type Query {…} type Mutation {…} `, resolvers: { Query: { flavors: (parent, args) => {…}, }, Mutation: {…}, Flavor: { nutrition: (parent) => { return mongodb.collection('nutrition').findOne({ flavorId: parent.id, }) } }, },
resolvers
, ,
Query
,
Mutation
Flavor
. ,
typeDefs
.
Flavors
, ,
nutrition
-. ,
Flavor
. , : « ,
nutrition
,
type Flavor
».
MongoDB, ,
parent
, -. ,
parent
, , ,
flavors
. ,
flavor
, :
{ flavors { id name nutrition { calories } } }
flavor
,
flavors
,
nutrition
,
parent
. , , , MongoDB,
parent.id
,
id
flavor
, .
parent.id
,
nutrition
flavorId
,
flavor
.
▍
, , . , .
type Mutation
, ,
updateFlavor
, , .
type Mutation { updateFlavor(id: ID!, name: String, description: String): Flavor }
: « ,
updateFlavor
id
ID
( ,
!
, GraphQL , ),
name
String
description
String
». , ,
Flavor
( — ,
id
,
name
,
description
, , ,
nutrition
).
{ typeDefs: gql`…`, resolvers: { Mutation: { updateFlavor: (parent, args) => { // , args { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' } // . mongodb.collection('flavors').update( { id: args.id }, { $set: { ...args, }, }, ) // flavor . return mongodb.collection('flavors').findOne(args.id) }, }, }, }
-
updateFlavor
, : , , — ,
flavor
.
, ,
flavor
. ?
, , . ,
flavor
, .
args
? , . , , , 100% , . , , , , , .
GraphQL?
, , , , , GraphQL-API.
, GraphQL , . , . , . , , , GraphQL REST . , , , GraphQL.
▍ ,
, HTTP-, , , , — . GraphQL , , , , ( ).
, , ( — ), GraphQL .
▍ , ,
, , « ». , , , . . GraphQL .
▍ ,
REST API, : , . , -, iOS Android, API . , , , , « » .
, , , HTTP, API (, , ).
▍ GraphQL — ? REST API GraphQL?
, . . , , GraphQL . GraphQL, . , , , . , , .
, GraphQL , , , . GraphQL , Apollo Relay, .
GraphQL — , , .
graphql
(
express-graphql
, ) — . , GraphQL - . , -, , , , .
Sumário
, GraphQL , . GraphQL , , , . , , , , GraphQL.
, : GraphQL . GraphQL . , GraphQL, , , , , , , .
— , GraphQL — , , . GraphQL , . , GraphQL — , , , . . , , , , , , GraphQL.
Caros leitores! GraphQL — , .
