O que é GraphQL?
November 4, 2022
O que é GraphQL? Quão popular ele é?
GraphQL é uma linguagem de manipulação de consultas orientada a API lançada pelo Facebook em 2015. Em contraste com outros designs de API, o GraphQL permite que os clientes formem declarações de consulta com base em uma estrutura de dados previamente acordada e permite que o servidor analise a declaração e retorne apenas o que é necessário. Dessa forma, o GraphQL oferece riqueza e flexibilidade, evitando a perda de desempenho causada por dados redundantes, o que torna o GraphQL uma excelente escolha para aplicações que precisam lidar com muitos objetos de dados complexos.
Em 2018, o GraphQL lançou uma especificação completa e uma versão estável. No mesmo ano, o Facebook doou o projeto GraphQL para a GraphQL Foundation, sob a Linux Foundation. Desde então, o GraphQL foi adotado em muitos projetos de código aberto e organizações comerciais. Até agora, existem várias implementações principais do lado do cliente do GraphQL no mercado. Implementações do lado do servidor estão disponíveis em todas as principais linguagens de programação do lado do servidor, e até mesmo em linguagens de nicho como D e R.
Alguns cenários reais e desafios para o GraphQL
O exemplo mais conhecido de GraphQL é a API GraphQL do GitHub.
Antes de adotar o GraphQL, o GitHub fornecia uma API REST para expor os ricos dados gerados por milhões de projetos hospedados, que foi tão bem-sucedida que se tornou um modelo para as pessoas seguirem ao projetar APIs REST. No entanto, à medida que o número de objetos de dados cresceu e os campos dentro dos objetos se tornaram maiores, a API REST começou a revelar cada vez mais desvantagens. No lado do servidor, o GitHub teve que definir limites rigorosos na frequência de chamadas para reduzir custos devido à quantidade de dados gerados com cada chamada. No lado do desenvolvedor, eles tiveram que lidar com essa limitação, pois, embora uma única chamada retorne muitos dados, a maior parte deles é inútil. Para obter uma informação específica, os desenvolvedores frequentemente precisam lançar várias consultas e, em seguida, escrever muito código de cola para unir os dados significativos dos resultados das consultas no conteúdo desejado. No processo, eles também têm que lidar com as restrições de "número de chamadas".
Portanto, o GitHub adotou o GraphQL assim que ele foi lançado. O GitHub se tornou o "embaixador" do GraphQL, levando sua aplicação para milhares de desenvolvedores. A API GraphQL é agora a primeira escolha do GitHub. Desde o primeiro anúncio de suporte ao GraphQL, o GitHub publicou vários artigos sobre o GraphQL a cada ano. Para permitir que os desenvolvedores migrem para o GraphQL, o GitHub escreveu uma aplicação de consulta interativa especificamente para esse propósito: https://docs.github.com/en/graphql/overview/explorer. Os desenvolvedores podem aprender como escrever GraphQL através dessa aplicação.
No entanto, o GraphQL não é uma solução milagrosa. Recentemente, o GitHub descontinuou sua própria implementação do GraphQL para a API de pacotes. Muitas pessoas também começaram a discutir algumas das desvantagens do GraphQL. Muitos dos problemas com o GraphQL surgem do fato de que sua estrutura é tão diferente do padrão HTTP que não há uma maneira fácil de mapear alguns dos conceitos do GraphQL para uma estrutura como o caminho/cabeçalho HTTP. Tratar o GraphQL como uma API HTTP normal requer trabalho de desenvolvimento adicional. Como resultado, os desenvolvedores que desejam gerenciar suas próprias APIs GraphQL terão que usar um gateway de API habilitado para GraphQL.
Como o APISIX suporta o GraphQL
Atualmente, o APISIX suporta roteamento dinâmico através de algumas propriedades do GraphQL. Com essa capacidade, podemos aceitar apenas solicitações GraphQL específicas ou ter diferentes GraphQLs encaminhados para diferentes upstreams.
Tomemos a seguinte declaração GraphQL como exemplo:
query getRepo {
owner {
name
}
repo {
created
}
}
O APISIX extrai as seguintes três propriedades do GraphQL para roteamento:
- graphql_operation
- graphql_name
- graphql_root_fields
Na declaração GraphQL acima:
graphql_operation
corresponde aquery
graphql_name
corresponde agetRepo
graphql_root_fields
corresponde a["owner", "repo"]
Vamos criar uma rota para demonstrar as capacidades de roteamento refinado do APISIX para GraphQL:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"methods": ["POST"],
"uri": "/graphql",
"vars": [
["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:2022": 1
}
}
}'
Em seguida, use uma solicitação com uma declaração GraphQL para acessar:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
owner {
name
}
repo {
created
}
}'
HTTP/1.1 200 OK
...
Podemos ver que a solicitação atingiu o upstream, pois a declaração de consulta correspondeu a todas as três condições.
Por outro lado, se acessarmos com uma declaração que não corresponde, por exemplo, o campo owner não está incluído:
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {
created
}
}'
HTTP/1.1 404 Not Found
...
Ela não corresponderá à regra de roteamento correspondente.
Podemos adicionalmente criar uma rota que permita que declarações que não contenham o campo owner sejam roteadas para outro upstream:
curl http://127.0.0.1:9180/apisix/admin/routes/2 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"methods": ["POST"],
"uri": "/graphql",
"vars": [
["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "!", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"192.168.0.1:2022": 1
}
}
}'
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
repo {
created
}
}'
HTTP/1.1 200 OK
...
Perspectivas de suporte futuro do APISIX para o GraphQL
Além do roteamento dinâmico, o APISIX também pode introduzir mais operações baseadas em campos específicos do GraphQL no futuro. Por exemplo, a API GraphQL do GitHub tem uma fórmula específica para limitação de taxa, e podemos aplicar regras semelhantes para converter uma única solicitação GraphQL em um número correspondente de "chamadas virtuais" para realizar a limitação de taxa específica do GraphQL.
Também podemos pensar no problema de uma maneira diferente. A aplicação em si ainda fornece a API REST, e o gateway converte solicitações GraphQL em solicitações REST e respostas REST em respostas GraphQL no nível mais externo. A API GraphQL fornecida dessa forma pode realizar funções como RBAC, limitação de taxa, cache, etc., sem desenvolver plugins especiais. Do ponto de vista técnico, essa ideia não é tão difícil de implementar. Afinal, em 2022, até as APIs REST tendem a fornecer especificações OpenAPI como schema, o que é apenas uma transferência entre o schema GraphQL e o schema OpenAPI, além da filtragem de campos específica do GraphQL. (Claro, devo admitir que não pratiquei isso. Talvez haja desafios em alguns detalhes que ainda precisam ser superados.)
Leitores atentos notarão que as APIs GraphQL convertidas dessa forma só podem operar em um modelo por vez, o que obviamente não atende aos requisitos de flexibilidade do GraphQL e não passa de uma API REST vestida de GraphQL. No entanto, o GraphQL tem um conceito chamado costura de schema que permite que os implementadores combinem vários schemas.
Como exemplo, temos duas APIs, uma chamada GetEvent e outra chamada GetLocation, que retornam os tipos Event e Location, respectivamente.
type Event {
id: string
location_id: string
}
type Location {
id: string
city: string
}
type Query {
GetEvent(id: string): Event
GetLocation(id: string): Location
}
Podemos adicionar uma configuração que combine essas duas APIs em uma nova API chamada GetEventWithLocation, que se parece com isso:
type EventWithLocation {
id: string
location: Location
}
type Query {
GetEventWithLocation(id: string): EventWithLocation
}
A implementação específica da costura é feita pelo gateway. No exemplo acima, o gateway divide a API em duas, chamando GetEvent para obter o location_id e, em seguida, GetLocation para obter os dados combinados.
Em resumo, ao converter REST para GraphQL, cada API REST pode ser transformada em um modelo GraphQL correspondente; e com a ajuda da costura de schema, vários modelos podem ser combinados em uma única API GraphQL. Dessa forma, podemos construir uma API GraphQL rica e flexível sobre a API REST existente e gerenciar plugins específicos na granularidade da API REST. Esse design, incidentalmente, resolve alguns dos problemas de orquestração de API. Como no exemplo acima, pegamos a saída de uma API (Event.location_id) como a entrada de outra API (Location.id).