Introdução ao GraphQL

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:

  1. usando a Interface Definition Language (IDL) do GraphQL

  2. 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.