Spring Boot + GraphQL + Mysql

1. Introdução

GraphQL é uma linguagem de consulta criada pelo Facebook em 2012 e lançada publicamente em 2015. É considerada uma alternativa para arquiteturas REST, além de oferecer um serviço runtime para rodar comandos e consumir uma API

2. Configurações

2.1 Tabelas do Banco:

CREATE TABLE `empregado` (
`id` bigint(20) NOT NULL,
`idade` int(11) NOT NULL,
`nascimento` datetime NOT NULL,
`nome` varchar(255) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `projeto` (
`id` bigint(20) NOT NULL,
`codigo_servico` int(11) NOT NULL,
`prazo` datetime NOT NULL,
`nome` varchar(255) NOT NULL,
`responsavel` varchar(255) NOT NULL,
`empregado_id` bigint(20) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
ALTER TABLE `empregado`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `projeto`
--
ALTER TABLE `projeto`
ADD PRIMARY KEY (`id`),
ADD KEY `FK13mauwmrbr69rgwkfkg5ss2an` (`empregado_id`);

2.2 Spring initializr

https://start.spring.io/

2.3 Maven — pom.xml

Além das dependências do spring boot, adicione as libs do graphQL e do graphiQL (vamos falar mais sobre ele depois).

<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-tools -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-spring-boot-starter -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphiql-spring-boot-starter -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>3.6.0</version>
</dependency>

2.4 Application.properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/meu_banco
spring.datasource.username=root
spring.datasource.password=MinhaSenha
spring.jpa.properties.hibernate.format_sql = true
spring.jpa.show-sql=true
server.port=8090
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
graphql.servlet.mapping=/graphql

3. Classes do Spring

3.1 Models

Obs: Lombok é bacana mas o GraphQL não reconhece os gets, então será necessário ter os gets explicitamente.

@Entity
public class Empregado {

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;

@Column(name = "nome", nullable = false)
private String nome;

@Column(name = "idade", nullable = false)
private Integer idade;

@Column(name = "nascimento", nullable = false)
private Date nascimento;

@OneToMany(mappedBy="empregado", fetch = FetchType.EAGER)
private List<Projeto> projetos;
    //gets e sets
}
@Entity
public class Projeto {

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;

@Column(name = "nome", nullable = false)
private String nome;

@Column(name = "codigo_servico", nullable = false)
private Integer codigoServico;

@Column(name = "responsavel", nullable = false)
private String responsavel;

@Column(name = "prazo", nullable = false)
private Date prazo;

@ManyToOne
@JoinColumn(name = "empregado_id", nullable = false, updatable = false)
private Empregado empregado;
    //gets e sets
}

3.2 Repositório

Para fins de estudo e ser um exemplo resumido, eu não fiz os services.

//TODO Façam os services

@Repository
public interface EmpregadoRepository extends JpaRepository<Empregado, Long> {

}
@Repository
public interface ProjetoRepository extends JpaRepository<Projeto, Long> {

}

4. E finalmente as classes do GraphQL

4.1 Query

Fazendo uma analogia com o REST, Query seria como o GET. Com o tipo Query você deverá apenas fazer consultas e não alterar o estado de nenhum objeto do servidor.

public class Query implements GraphQLQueryResolver {

private EmpregadoRepository empregadoRepository;
private ProjetoRepository projetoRepository;

public Query(EmpregadoRepository empregadoRepository, ProjetoRepository projetoRepository) {
this.empregadoRepository = empregadoRepository;
this.projetoRepository = projetoRepository;
}

public List<Empregado> obterEmpregados() {
return empregadoRepository.findAll();
}

public long contarEmpregados() {
return empregadoRepository.count();
}

public Empregado obterEmpregadoPorId(Long id) {
return empregadoRepository.findById(id).orElseThrow(null);
}

}

4.2 Mutation

Ainda comparando ao REST, Mutation seria como os métodos PUT, POST e DELETE.

public class Mutation implements GraphQLMutationResolver {

private EmpregadoRepository empregadoRepository;
private ProjetoRepository projetoRepository;

public Mutation(EmpregadoRepository empregadoRepository, ProjetoRepository projetoRepository) {
this.empregadoRepository = empregadoRepository;
this.projetoRepository = projetoRepository;
}

public Empregado novoEmpregado(String nome, Integer idade) {
Empregado empregado = new Empregado(nome, idade, new Date());
empregadoRepository.save(empregado);
return empregado;
}

public boolean deletarEmpregado(Long id) {
empregadoRepository.deleteById(id);
return true;
}

}

4.3 Alterando o Application

@SpringBootApplication
public class EstudosGraphqlApplication {

public static void main(String[] args) {
SpringApplication.run(EstudosGraphqlApplication.class, args);
}

@Bean
public Query query(EmpregadoRepository empregadoRepository, ProjetoRepository projetoRepository) {
return new Query(empregadoRepository, projetoRepository);
}

@Bean
public Mutation mutation(EmpregadoRepository empregadoRepository, ProjetoRepository projetoRepository) {
return new Mutation(empregadoRepository, projetoRepository);
}
}

4.4 Arquivos *.graphqls

Você deverá mapear seus models nesse arquivo com a extensão *.graphqls como o exemplo abaixo e colocá-los dentro da pasta /resources.

schema {
query: Query
}

type Empregado {
id: ID!
nome: String!
idade: Int!
nascimento: Long
projetos: [Projeto]!
}

type Projeto {
id: ID!
nome: String!
codigoServico: Int!
responsavel: String!
prazo: Long!
}

type Query {
obterEmpregados: [Empregado]!
contarEmpregados: Long!
obterEmpregadoPorId(id: Long) : Empregado!
}

type Mutation {
novoEmpregado(nome: String!, idade: Int!) : Empregado
}

GraphQL tem esses tipos por default: Int, Float, String, Boolean and ID

Veja outros tipos aqui:

4. Testando com o GraphiQL

Ao subir o projeto, um serviço /graphiql estará disponível para testar sua API.

Consultando apenas alguns dados dos Empregados
Empregados com dados de projetos
Inserindo um novo Empregado e obtendo os dados que eu quero da resposta

5. Como chamar uma API graphql do meu sistema?

Essa formatação usada para consultar e inserir dados no graphiQL será enviada para API no payload de uma requisição POST.

6. Fim?

Quase. GraphQL simplifica muito o desenvolvimento das APIs, te reduz a um único endpoint, dá liberdade ao cliente para pedir a requisição da maneira que desejar mas nem tudo são flores.

Http Codes: Todas as requisições, mesmo as que derem erro (exceção do erro 500) vem com status code 200. Mudar sua API já existente significa também mudar todo o tratamento de erros dos clientes que você já tem.

Duplicar Schemas: Caso você tenha que separar seu mapeamento em muitos arquivos *.graphqls, você poderá ter que duplicar mapeamentos e isso nunca é bacana de manter. Existem algumas opções mas no fim sempre haverão duplicados.

Error Handling: Não é muito trivial o tratamento de erros usando o GraphQL. Vi alguns exemplos do que fazer mas nada que não seja muito verboso.

7. E o Futuro?

Facebook, Elo, Spotify e muitos outros players grandes usam GraphQL e acredito que ela ainda vai evoluir pra ajustar algumas das lacunas que citei. Enquanto isso não acontece, existe uma outra alternativa, o restQL de uma empresa brasileira, que vem com novas funções que não existem no GraphQL como caching nativo no browser.