Introdução ao GraphQL
*1. Visão geral *
GraphQL é uma linguagem de consulta, criada pelo Facebook com o objetivo de criar aplicativos clientes com base em sintaxe intuitiva e flexível, para descrever seus requisitos e interações de dados.
Um dos principais desafios das chamadas REST tradicionais é a incapacidade do cliente de solicitar um conjunto de dados personalizado (limitado ou expandido). Na maioria dos casos, depois que o cliente solicita informações do servidor, ele obtém todos ou nenhum dos campos.
Outra dificuldade é trabalhar e manter vários pontos de extremidade. À medida que a plataforma cresce, consequentemente, o número aumenta. Portanto, os clientes geralmente precisam solicitar dados de diferentes pontos de extremidade.
Ao construir um servidor GraphQL, é necessário apenas um URL para todos os dados que buscam e mutam. Assim, um cliente pode solicitar um conjunto de dados enviando uma string de consulta, descrevendo o que deseja, para um servidor.
===* 2. Nomenclatura básica do GraphQL *
Vamos dar uma olhada na terminologia básica do GraphQL.
-
Consulta: é uma operação somente leitura solicitada a um servidor GraphQL
-
Mutação: é uma operação de leitura e gravação solicitada para um servidor GraphQL
-
Resolver: No GraphQL, o Resolver é responsável por mapear a operação e o código em execução no back-end, responsável por lidar com a solicitação. É análogo ao back-end do MVC em um aplicativo RESTFul
-
Tipo: Um Type define a forma dos dados de resposta que podem ser retornados do servidor GraphQL, incluindo campos que são arestas de outros Types
-
Entrada: como um _Type, _ mas define a forma dos dados de entrada que são enviados para um servidor GraphQL
-
Scalar: é um Type primitivo, como String, Int, Boolean, Float, etc
-
Interface: Uma interface armazenará os nomes dos campos e seus argumentos, para que os objetos GraphQL possam herdar dele, garantindo o uso de campos específicos
-
Esquema: No GraphQL, o Esquema gerencia consultas e mutações, definindo o que pode ser executado no servidor GraphQL
2.1 Carregamento de esquema
Existem duas maneiras de carregar um esquema no servidor GraphQL:
-
usando a Interface Definition Language (IDL) do GraphQL
-
usando uma das linguagens de programação suportadas
Vamos demonstrar um exemplo usando o IDL:
type User {
firstName: String
}
Agora, um exemplo de definição de esquema usando código Java:
GraphQLObjectType userType = newObject()
.name("User")
.field(newFieldDefinition()
.name("firstName")
.type(GraphQLString))
.build();
*3. Idioma de definição da interface *
A linguagem de definição de interface (IDL) ou a linguagem de definição de esquema (SDL) é a maneira mais concisa de especificar um esquema do GraphQL. A sintaxe é bem definida e será adotada na especificação oficial do GraphQL.
Por exemplo, vamos criar um esquema GraphQL para que um usuário/e-mail possa ser especificado assim:
schema {
query: QueryType
}
enum Gender {
MALE
FEMALE
}
type User {
id: String!
firstName: String!
lastName: String!
createdAt: DateTime!
age: Int! @default(value: 0)
gender: [Gender]!
emails: [Email!]! @relation(name: "Emails")
}
type Email {
id: String!
email: String!
default: Int! @default(value: 0)
user: User @relation(name: "Emails")
}
===* 4. GraphQL-java *
GraphQL-java é uma implementação baseada na especificação e na implementação de referência JavaScript. Observe que requer pelo menos o Java 8 para ser executado corretamente.
====* 4.1 Anotações GraphQL-java *
O GraphQL também possibilita o uso de Java anotations para gerar sua definição de esquema sem todo o código padrão criado pelo uso da abordagem IDL tradicional.
====* 4.2 Dependências *
Para criar nosso exemplo, vamos começar por importar a dependência necessária, que depende de https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.graphql-java%22%20AND%20a % 3A% 22graphql-java-notes% 22 módulo [Graphql-java-notes]:
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-annotations</artifactId>
<version>3.0.3</version>
</dependency>
Também estamos implementando uma biblioteca HTTP para facilitar a configuração em nosso aplicativo. Vamos usar Ratpack (embora poderia ser implementado também com Vert.x, Spark, Dropwizard, Spring Boot, etc.).
Vamos também importar a dependência do Ratpack:
<dependency>
<groupId>io.ratpack</groupId>
<artifactId>ratpack-core</artifactId>
<version>1.4.6</version>
</dependency>
====* 4.3 Implementação*
Vamos criar nosso exemplo: uma API simples que fornece um "CRUDL" (Criar, Recuperar, Atualizar, Excluir e Listar) para os usuários. Primeiro, vamos criar nosso User POJO:
@GraphQLName("user")
public class User {
@GraphQLField
private Long id;
@GraphQLField
private String name;
@GraphQLField
private String email;
//getters, setters, constructors, and helper methods omitted
}
Neste POJO, podemos ver a anotação _ @ GraphQLName (“usuário”) , como uma indicação de que essa classe é mapeada pelo GraphQL junto com cada campo anotado com _@GraphQLField.
Em seguida, criaremos a classe UserHandler. Essa classe herda da biblioteca de conectores HTTP escolhida (no nosso caso, Ratpack) um método manipulador, que gerenciará e chamará o recurso Resolver do GraphQL. Assim, redirecionando a solicitação (cargas úteis JSON) para a operação de consulta ou mutação adequada:
@Override
public void handle(Context context) throws Exception {
context.parse(Map.class)
.then(payload -> {
Map<String, Object> parameters = (Map<String, Object>)
payload.get("parameters");
ExecutionResult executionResult = graphql
.execute(payload.get(SchemaUtils.QUERY)
.toString(), null, this, parameters);
Map<String, Object> result = new LinkedHashMap<>();
if (executionResult.getErrors().isEmpty()) {
result.put(SchemaUtils.DATA, executionResult.getData());
} else {
result.put(SchemaUtils.ERRORS, executionResult.getErrors());
LOGGER.warning("Errors: " + executionResult.getErrors());
}
context.render(json(result));
});
}
Agora, a classe que dará suporte às operações de consulta, ou seja, UserQuery. Conforme mencionado, todos os métodos que recuperam dados do servidor para o cliente são gerenciados por esta classe:
@GraphQLName("query")
public class UserQuery {
@GraphQLField
public static User retrieveUser(
DataFetchingEnvironment env,
@NotNull @GraphQLName("id") String id) {
//return user
}
@GraphQLField
public static List<User> listUsers(DataFetchingEnvironment env) {
//return list of users
}
}
Da mesma forma que _UserQuery, _ agora criamos _UserMutation, _ que gerenciará todas as operações que pretendem alterar alguns dados armazenados no lado do servidor:
@GraphQLName("mutation")
public class UserMutation {
@GraphQLField
public static User createUser(
DataFetchingEnvironment env,
@NotNull @GraphQLName("name") String name,
@NotNull @GraphQLName("email") String email) {
//create user information
}
}
Vale notar as anotações nas classes UserQuery e UserMutation: _ @ GraphQLName (“query”) _ e _ @ GraphQLName (“mutation”) ._ Essas anotações são usadas para definir as operações de consulta e mutação, respectivamente.
Com o servidor GraphQL-java capaz de executar as operações de consulta e mutação, podemos usar as seguintes cargas JSON para testar a solicitação do cliente no servidor:
-
Para a operação CREATE:
{
"query": "mutation($name: String! $email: String!){
createUser (name: $name email: $email) { id name email age } }",
"parameters": {
"name": "John",
"email": "[email protected]"
}
}
Como resposta do servidor para esta operação:
{
"data": {
"createUser": {
"id": 1,
"name": "John",
"email": "[email protected]"
}
}
}
-
Para a operação RETRIEVE:
{
"query": "query($id: String!){ retrieveUser (id: $id) {name email} }",
"parameters": {
"id": 1
}
}
Como resposta do servidor para esta operação:
{
"data": {
"retrieveUser": {
"name": "John",
"email": "[email protected]"
}
}
}
O GraphQL fornece recursos que o cliente pode personalizar a resposta. Portanto, na última operação RETRIEVE usada como exemplo, em vez de retornar o nome e o email, podemos, por exemplo, retornar apenas o email:
{
"query": "query($id: String!){ retrieveUser (id: $id) {email} }",
"parameters": {
"id": 1
}
}
Portanto, as informações retornadas do servidor GraphQL retornarão apenas os dados solicitados:
{
"data": {
"retrieveUser": {
"email": "[email protected]"
}
}
}
5. Conclusão
O GraphQL é uma maneira fácil e bastante atraente de minimizar a complexidade entre cliente/servidor como uma abordagem alternativa às APIs REST.
Como sempre, o exemplo está disponível em nosso GitHub repository.