Introdução ao GraphQL e Spring Boot
1. Introdução
GraphQL é um conceito relativamente novo do Facebook que é cobrado como uma alternativa ao REST para APIs da web.
Este artigo fornece uma introdução à configuração de um servidor GraphQL usando o Spring Boot para que ele possa ser adicionado aos aplicativos existentes ou usado em novos.
2. O que é GraphQL?
As APIs REST tradicionais funcionam com o conceito de Recursos que o servidor gerencia. Esses recursos podem ser manipulados de algumas maneiras padrão, seguindo os vários verbos HTTP. Isso funciona muito bem desde que nossa API se encaixe no conceito de recurso, mas desmorona rapidamente quando precisamos nos desviar dele.
Isso também sofre quando o cliente precisa de dados de vários recursos ao mesmo tempo. Por exemplo, solicitando uma postagem no blog e os comentários. Normalmente, isso é resolvido com o cliente fazendo várias solicitações ou com o servidor fornecendo dados extras que nem sempre são necessários, levando a tamanhos de resposta maiores.
GraphQL offers a solution to both of these problems. Ele permite que o cliente especifique exatamente quais dados são desejados, incluindo a navegação em recursos filho em uma única solicitação, e permite várias consultas em uma única solicitação.
Também funciona de uma maneira muito mais RPC, usando consultas e mutações nomeadas, em vez de um conjunto obrigatório de ações padrão. This works to put the control where it belongs, with the API developer specifying what is possible, and the API consumer what is desired.
Por exemplo, um blog pode permitir a seguinte consulta:
query {
recentPosts(count: 10, offset: 0) {
id
title
category
author {
id
name
thumbnail
}
}
}
Esta consulta irá:
-
solicite as dez postagens mais recentes
-
para cada postagem, solicite o ID, título e categoria
-
para cada postagem, solicite ao autor, retornando o ID, nome e miniatura
Em uma API REST tradicional, isso precisa de 11 solicitações - 1 para as postagens e 10 para os autores - ou precisa incluir os detalhes do autor nos detalhes da postagem.
2.1. Esquemas GraphQL
O servidor GraphQL expõe um esquema que descreve a API. Esse esquema é composto de definições de tipo. Cada tipo possui um ou mais campos, cada um com zero ou mais argumentos e retorna um tipo específico.
O gráfico é composto da maneira como esses campos são aninhados entre si. Observe que não há necessidade de o gráfico ser acíclico - os ciclos são perfeitamente aceitáveis - mas ele é direcionado. Ou seja, o cliente pode ir de um campo para seus filhos, mas não pode voltar automaticamente para o pai, a menos que o esquema defina isso explicitamente.
Um exemplo de esquema do GraphQL para um blog pode conter as seguintes definições, descrevendo uma postagem, um autor da postagem e uma consulta raiz para obter as postagens mais recentes no blog.
type Post {
id: ID!
title: String!
text: String!
category: String
author: Author!
}
type Author {
id: ID!
name: String!
thumbnail: String
posts: [Post]!
}
# The Root Query for the application
type Query {
recentPosts(count: Int, offset: Int): [Post]!
}
# The Root Mutation for the application
type Mutation {
writePost(title: String!, text: String!, category: String) : Post!
}
O "!" no final de alguns nomes indica que este é um tipo não anulável. Qualquer tipo que não possua isso pode ser nulo na resposta do servidor. O serviço GraphQL lida com isso corretamente, permitindo solicitar campos filho de tipos anuláveis com segurança.
O Serviço GraphQL também expõe o próprio esquema usando um conjunto padrão de campos, permitindo que qualquer cliente consulte a definição do esquema com antecedência.
Isso pode permitir que o cliente detecte automaticamente quando o esquema é alterado e permite clientes que se adaptam dinamicamente à maneira como o esquema funciona. Um exemplo incrivelmente útil disso é a ferramenta GraphiQL - discutida mais adiante - que nos permite interagir com qualquer API GraphQL.
3. Apresentando GraphQL Spring Boot Starter
The Spring Boot GraphQL Starter offers a fantastic way to get a GraphQL server running in a very short time. Combinado com a bibliotecaGraphQL Java Tools, precisamos apenas escrever o código necessário para nosso serviço.
3.1. Configurando o serviço
Tudo o que precisamos para que isso funcione são as dependências corretas:
com.graphql-java
graphql-spring-boot-starter
5.0.2
com.graphql-java
graphql-java-tools
5.2.4
O Spring Boot irá selecioná-los automaticamente e configurar os manipuladores apropriados para funcionar automaticamente.
Por padrão, isso irá expor o serviço GraphQL no endpoint/graphql do nosso aplicativo e aceitará solicitações POST contendo a carga útil GraphQL. Este endpoint pode ser personalizado em nosso arquivoapplication.properties, se necessário.
3.2. Escrevendo o esquema
A biblioteca GraphQL Tools trabalha processando arquivos de esquema GraphQL para construir a estrutura correta e, em seguida, liga beans especiais a essa estrutura. The Spring Boot GraphQL starter automatically finds these schema files.
Esses arquivos precisam ser salvos com a extensão “.graphqls” e podem estar presentes em qualquer lugar no caminho de classe. Também podemos ter quantos desses arquivos forem desejados, para que possamos dividir o esquema em módulos, conforme desejado.
O único requisito é que deve haver exatamente uma consulta raiz e até uma mutação raiz. Isso não pode ser dividido entre arquivos, ao contrário do restante do esquema. Essa é uma limitação da própria definição do esquema GraphQL, e não da implementação Java.
3.3. Resolvedor de consulta raiz
The root query needs to have special beans defined in the Spring context to handle the various fields in this root query. Diferente da definição do esquema, não há restrição de que exista apenas um único bean Spring para os campos de consulta raiz.
Os únicos requisitos são que os beans implementemGraphQLQueryResolver e que cada campo na consulta raiz do esquema tenha um método em uma dessas classes com o mesmo nome.
public class Query implements GraphQLQueryResolver {
private PostDao postDao;
public List getRecentPosts(int count, int offset) {
return postsDao.getRecentPosts(count, offset);
}
}
Os nomes do método devem ser um dos seguintes, nesta ordem:
-
-
é
- apenas se o campo for do tipoBoolean -
obter
O método deve ter parâmetros que correspondam a quaisquer parâmetros no esquema GraphQL e pode, opcionalmente, receber um parâmetro final do tipoDataFetchingEnvironment.
O método também deve retornar o tipo de retorno correto para o tipo no esquema GraphQL, como estamos prestes a ver. Qualquer tipo simples -String, Int, List, etc. - pode ser usado com os tipos Java equivalentes, e o sistema apenas os mapeia automaticamente.
Acima definiu o métodogetRecentPosts que será usado para lidar com qualquer consulta GraphQL para o camporecentPosts no esquema definido anteriormente.
3.4. Usando Feijões para Representar Tipos
Every complex type in the GraphQL server is represented by a Java bean - seja carregado a partir da consulta raiz ou de qualquer outro lugar na estrutura. A mesma classe Java deve sempre representar o mesmo tipo de GraphQL, mas o nome da classe não é necessário.
Fields inside the Java bean will directly map onto fields in the GraphQL response based on the name of the field.
public class Post {
private String id;
private String title;
private String category;
private String authorId;
}
Quaisquer campos ou métodos no bean Java que não sejam mapeados para o esquema GraphQL serão ignorados, mas não causarão problemas. Isso é importante para os resolvedores de campo funcionarem.
Por exemplo, o campoauthorId aqui não corresponde a nada em nosso esquema que definimos anteriormente, mas estará disponível para uso na próxima etapa.
3.5. Resolvedores de campo para valores complexos
Às vezes, o valor de um campo não é trivial para carregar. Isso pode envolver pesquisas de banco de dados, cálculos complexos ou qualquer outra coisa. GraphQL Tools has a concept of a field resolver that is used for this purpose. Esses são beans Spring que podem fornecer valores no lugar do bean de dados.
O resolvedor de campo é qualquer bean no contexto Spring que tem o mesmo nome do bean de dados, com o sufixoResolver, e que implementa a interfaceGraphQLResolver. Os métodos no bean do resolvedor de campos seguem todas as mesmas regras do bean de dados, mas também são fornecidos o próprio bean de dados como primeiro parâmetro.
Se um resolvedor de campos e o bean de dados tiverem métodos para o mesmo campo GraphQL, o resolvedor de campos terá precedência.
public class PostResolver implements GraphQLResolver {
private AuthorDao authorDao;
public Author getAuthor(Post post) {
return authorDao.getAuthorById(post.getAuthorId());
}
}
O fato de esses resolvedores de campo serem carregados no contexto do Spring é importante. Isso permite que eles trabalhem com outros beans gerenciados do Spring - por exemplo, DAOs.
É importante ressaltar queif the client does not request a field, then the GraphQL Server will never do the work to retrieve it. Isso significa que se um cliente recuperar um Post e não perguntar pelo Autor, o métodogetAuthor() acima nunca será executado e a chamada DAO nunca será feita.
3.6. Valores anuláveis
O esquema do GraphQL tem o conceito de que alguns tipos são anuláveis e outros não.
Isso pode ser tratado no código Java diretamente usando valores nulos, mas igualmente, o novo tipoOptional do Java 8 pode ser usado diretamente aqui para tipos anuláveis e o sistema fará a coisa correta com os valores.
Isso é muito útil, pois significa que nosso código Java é obviamente o mesmo que o esquema GraphQL das definições de método.
3.7. Mutações
Até agora, tudo o que fizemos foi sobre recuperar dados do servidor. O GraphQL também tem a capacidade de atualizar os dados armazenados no servidor, por meio de mutações.
Do ponto de vista do código, não há razão para que uma consulta não possa alterar os dados no servidor. Poderíamos facilmente escrever resolvedores de consulta que aceitem argumentos, salvem novos dados e retornem essas alterações. Fazer isso causará efeitos colaterais surpreendentes para os clientes da API e é considerado uma má prática.
Em vez disso,Mutations should be used to inform the client that this will cause a change to the data being stored.
As mutações são definidas no código Java usando classes que implementamGraphQLMutationResolver em vez deGraphQLQueryResolver.
Caso contrário, todas as mesmas regras se aplicam às consultas. O valor de retorno de um campo Mutação é tratado exatamente da mesma forma que no campo Consulta, permitindo que valores aninhados também sejam recuperados.
public class Mutation implements GraphQLMutationResolver {
private PostDao postDao;
public Post writePost(String title, String text, String category) {
return postDao.savePost(title, text, category);
}
}
4. Apresentando GraphiQL
O GraphQL também possui uma ferramenta complementar chamada GraphiQL. Essa é uma interface do usuário capaz de se comunicar com qualquer servidor GraphQL e executar consultas e mutações nele. Uma versão para download existe como um aplicativo Electron e pode ser recuperada emhere.
Também é possível incluir a versão baseada na Web do GraphiQL em nosso aplicativo automaticamente, adicionando a dependência do GraphiQL Spring Boot Starter:
com.graphql-java
graphiql-spring-boot-starter
5.0.2
Isso só funcionará se estivermos hospedando nossa API GraphQL no endpoint padrão de/graphql, portanto, o aplicativo independente será necessário se esse não for o caso.
5. Sumário
O GraphQL é uma nova tecnologia muito interessante que pode revolucionar potencialmente a maneira como as APIs da Web são desenvolvidas.
A combinação das bibliotecas Spring Boot GraphQL Starter e GraphQL Java Tools facilita incrivelmente a adição dessa tecnologia a qualquer aplicativo Spring Boot novo ou existente.
Trechos de código podem ser encontradosover on GitHub.