GraphQL com Spring Boot e DGS
Introdução ao DGS (Domain Graph Service)
É um projeto open source desenvolvido pela Netflix, que visa facilitar a implementação de GraphQL em um aplicativo Spring Boot, onde a estrutura do DGS é baseada em anotações do Spring Boot.
Quando o projeto DGS começou?
O projeto DGS inicio em 2019 na Netflix, quando equipes internas começaram a desenvolver vários serviços de GraphQL. No final de 2020 a Netflix decidiu abrir o código fonte, para construir uma comunidade ao ser redor.
DGS está pronto para produção?
Sim! A Netflix usa a estrutura há mais de um ano e meio em diferentes partes da organização, inclusive em grande escala.
Demonstração
Crie um novo projeto Spring Boot
A estrutura do DGS como falamos anteriormente é baseada no Spring Boot, então vamos começar criando um novo aplicativo Spring Boot. Para isso vamos utilizar o Spring Initializr.
A única dependência necessária é a Spring Web.

Após efetuar o download do aplicativo, descompacte o arquivo e abra o projeto em sua IDE.
Adicionando a dependência do DGS
Adicione a seguinte dependência à configuração do Maven.
<dependency>
<groupId>com.netflix.graphql.dgs</groupId>
<artifactId>graphql-dgs-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
O recurso de federação da biblioteca não se encontra no repositório central do Maven. Caso você queira utilizar este recurso, é necessário configurar o repositório jcenter
no Maven. Para que todos os seus projetos possam realizar downloads no repositório jcenter
, você deve criar um ou editar o arquivo settings.xml
que fica no diretório .m2
, esse é o diretório de artefatos do Maven, em todos os sistemas operacionais este diretório oculto fica no diretório raiz do usuário.
<settings xsi:schemaLocation='http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd'
xmlns='http://maven.apache.org/SETTINGS/1.0.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<profiles>
<profile>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray</name>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray-plugins</name>
<url>https://jcenter.bintray.com</url>
</pluginRepository>
</pluginRepositories>
<id>bintray</id>
</profile>
</profiles>
<activeProfiles>
<activeProfile>bintray</activeProfile>
</activeProfiles>
</settings>
Criando um esquema
As estruturas do DGS devem ser armazenadas no diretório resources/schema/schema.graphqls
.

Abaixo segue os dados da estrutura.
scalar LocalDate
type Query {
promocoes(dataFilter: LocalDate): [Promocao]
produtos(descricaoFilter: String): [Produto]
}
type Promocao {
descricao: String
desconto: Float
dataInicial: LocalDate
dataFinal: LocalDate
}
type Produto {
id: Int
descricao: String
valor: Float
}
Este esquema permite consultar uma lista de produtos e promoções, filtrando opcionalmente o produto por sua descrição e as promoções por data.
Implementar objetos utilizados na estrutura
Segue o código fonte das classes Java que tem como base as estruturas adicionadas no DGS.
import java.math.BigDecimal;
public class Produto {
private final Long id;
private final String descricao;
private final BigDecimal valor;
public Produto(Long id, String descricao, BigDecimal valor) {
this.id = id;
this.descricao = descricao;
this.valor = valor;
}
public Long getId() {
return id;
}
public String getDescricao() {
return descricao;
}
public BigDecimal getValor() {
return valor;
}
}
import java.math.BigDecimal;
import java.time.LocalDate;
public class Promocao {
private final String descricao;
private final BigDecimal desconto;
private final LocalDate dataInicial;
private final LocalDate dataFinal;
public Promocao(String descricao, BigDecimal desconto, LocalDate dataInicial, LocalDate dataFinal) {
this.descricao = descricao;
this.desconto = desconto;
this.dataInicial = dataInicial;
this.dataFinal = dataFinal;
}
public String getDescricao() {
return descricao;
}
public BigDecimal getDesconto() {
return desconto;
}
public LocalDate getDataInicial() {
return dataInicial;
}
public LocalDate getDataFinal() {
return dataFinal;
}
}
Implementar serviços
Vamos simular o desenvolvimento de um serviço que obtenha os dados de produtos e promoções, e que também apliquem os filtros por descrição de produto e data de promoções.
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class ProdutoService {
private final List<Produto> produtos = List.of(
new Produto(1L, "Cadeira", BigDecimal.valueOf(110)),
new Produto(2L, "Mesa", BigDecimal.valueOf(999)),
new Produto(3L, "Monitor", BigDecimal.valueOf(1200)),
new Produto(4L, "Mouse", BigDecimal.valueOf(320.50)),
new Produto(5L, "Teclado", BigDecimal.valueOf(275))
);
public List<Produto> get(String descricao) {
if (Objects.isNull(descricao)) {
return produtos;
}
return produtos.stream()
.filter(s -> s.getDescricao().contains(descricao))
.collect(Collectors.toList());
}
}
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class PromocaoService {
private final List<Promocao> promocoes = List.of(
new Promocao("Dia das mães", BigDecimal.TEN, LocalDate.parse("2021-05-01"), LocalDate.parse("2021-05-09")),
new Promocao("Natal", BigDecimal.valueOf(13), LocalDate.parse("2021-12-03"), LocalDate.parse("2021-12-25"))
);
public List<Promocao> get(LocalDate data) {
if (Objects.isNull(data)) {
return promocoes;
}
return promocoes.stream()
.filter(s -> s.getDataInicial().compareTo(data) <= 0 && s.getDataFinal().compareTo(data) >= 0)
.collect(Collectors.toList());
}
}
Implementar um buscador de dados
Os buscadores de dados são responsáveis por devolver dados para uma consulta.
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsData;
import com.netflix.graphql.dgs.InputArgument;
import java.util.List;
@DgsComponent
public class ProdutosDatafetcher {
private final ProdutoService produtoService;
public ProdutosDatafetcher(ProdutoService produtoService) {
this.produtoService = produtoService;
}
@DgsData(parentType = "Query", field = "produtos")
public List<Produto> produtos(@InputArgument("descricaoFilter") String descricao) {
return produtoService.get(descricao);
}
}
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsData;
import com.netflix.graphql.dgs.InputArgument;
import java.time.LocalDate;
import java.util.List;
@DgsComponent
public class PromocaoDatafetcher {
private final PromocaoService promocaoService;
public PromocaoDatafetcher(PromocaoService promocaoService) {
this.promocaoService = promocaoService;
}
@DgsData(parentType = "Query", field = "promocoes")
public List<Promocao> promocoes(@InputArgument("dataFilter") LocalDate data) {
return promocaoService.get(data);
}
}
Implementar conversores
Como utilizamos o objeto LocalDate na classe de promoções e também na definição da estrutura do DGS, precisamos desenvolver um conversos de objetos, para que o GraphQL saiba converter um objeto do tipo LocalDate para String e de String para LocalDate.
Não se desespere, isto é muito simples de ser desenvolvido. Basta criar uma classe que implemente Coercing
e que esteja anotada com @DgsScalar
.
import com.netflix.graphql.dgs.DgsScalar;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@DgsScalar(name="LocalDate")
public class DateTimeScalar implements Coercing<LocalDate, String> {
@Override
public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof LocalDate) {
return ((LocalDate) dataFetcherResult).format(DateTimeFormatter.ISO_DATE);
} else {
throw new CoercingSerializeException("Not a valid DateTime");
}
}
@Override
public LocalDate parseValue(Object input) throws CoercingParseValueException {
return LocalDate.parse(input.toString(), DateTimeFormatter.ISO_DATE);
}
@Override
public LocalDate parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue) {
return LocalDate.parse(((StringValue) input).getValue(), DateTimeFormatter.ISO_DATE);
}
throw new CoercingParseLiteralException("Value is not a valid ISO date time");
}
}
Este é todo o código necessário, o aplicativo está pronto para ser testado.
Testar aplicação
A biblioteca disponibiliza o endpoint graphiql
, para que possamos realizar as consultas utilizando o GraphQL.
Como não alteramos a porta de padrão do nosso servidor de aplicação, podemos acessar o seguinte endereço para executar as consultas.
http://localhost:8080/graphiql

Para consultarmos somente a descrição dos produtos, podemos executar a seguinte consulta.
{
produtos {
descricao
}
}
{
"data": {
"produtos": [
{
"descricao": "Cadeira"
},
{
"descricao": "Mesa"
},
{
"descricao": "Monitor"
},
{
"descricao": "Mouse"
},
{
"descricao": "Teclado"
}
]
}
}
Podemos retorna os dados das consultas de produtos e promoções.
{
produtos {
id
descricao
},
promocoes {
descricao
desconto
}
}
{
"data": {
"produtos": [
{
"id": 1,
"descricao": "Cadeira"
},
{
"id": 2,
"descricao": "Mesa"
},
{
"id": 3,
"descricao": "Monitor"
},
{
"id": 4,
"descricao": "Mouse"
},
{
"id": 5,
"descricao": "Teclado"
}
],
"promocoes": [
{
"descricao": "Dia das mães",
"desconto": 10
},
{
"descricao": "Natal",
"desconto": 13
}
]
}
}
Observe que adicionamos nas consultas apenas os campos que desejamos que sejam retornados.
Como último exemplo, vamos filtrar os produtos e promoções.
{
produtos(descricaoFilter: "M") {
id
descricao
},
promocoes(dataFilter: "2021-05-04") {
descricao
desconto
dataFinal
}
}
{
"data": {
"produtos": [
{
"id": 2,
"descricao": "Mesa"
},
{
"id": 3,
"descricao": "Monitor"
},
{
"id": 4,
"descricao": "Mouse"
}
],
"promocoes": [
{
"descricao": "Dia das mães",
"desconto": 10,
"dataFinal": "2021-05-09"
}
]
}
}
Conclusão
Neste post desenvolvemos um serviço GraphQL utilizando a biblioteca DGS da Netflix num projeto Spring Boot.
O código fonte da aplicação desenvolvida se encontra neste repositório no github.