GraphQL + Rails na prática – Separando lógica de negócio

Assim como é uma boa prática manter o Controller livre de lógica em aplicações REST, no mundo GraphQL é importante seguir a mesma ideia e evitar colocar muito código junto com a definição de tipos e campos. Assim você consegue uma maior facilidade de manutenção, desde testes até desacoplamento e reuso.

O que fazer quando não temos Games

Se voltarmos no nosso campo ‘game’ definido em ‘query_type.rb’ vemos que a lógica atual apenas busca por um Game mas não lida com a falta de dados e outras exceções que podem aparecer:


# app/graphql/types/query_type.rb
field :game do
  type Types::GameType
  argument :id, !types.Int
  resolve -> (root, args, ctx) {
    Game.find(args[:id])
  }
end

Caso o ‘id’ passado não corresponda a nenhum dado no banco uma exceção será lançada e a requisição será interrompida. A maneira mais simples de resolver esse cenário é utilizar o método ‘find_by_’ do Rails que não lança uma exceção caso o dado não seja encontrado, mas ainda continuamos retornando um ‘null’.

Se você quiser saber mais sobre por que retornar null é uma má ideia, dá uma olhada nesse post: https://brizeno.wordpress.com/2016/06/21/refatorando-tudo-nao-retorne-nulo-nunca/.

Uma solução, ao invés de retornar nulo, é utilizar um objeto que é funcionalmente válido mas não possui nenhuma informação. Seguindo essa ideia podemos criar um método no nosso model ‘Game’ que cria um “objeto nulo”.

#app/models/game.rb
class Game < ApplicationRecord
  def self.nil_game
    Game.new(name: '',
             description: '',
launch_year: 0,
             characters: [])
  end
end

O nosso objeto nulo é um objeto válido, mas que não tem nenhuma informação, assim ao invés de retornar um nulo, que precisa ser tratado de maneira diferente, temos uma resposta que pode ser tratada como qualquer outra.

Criando um Resolver

Agora precisamos adicionar o tratamento de, quando a busca pelo game retornar um nulo, utilizar o objeto nulo. Colocar essa lógica lá na definição de tipo do GraphQL pode não ser uma boa ideia, então temos duas opções: 1) criar um método no modelo que faz isso e 2) criar um Resolver para lidar com essa lógica.

Qual opção faz mais sentido depende do seu contexto, mas para esse post vamos explorar como criar um Resolver para separar a lógica da definição do tipo. Crie uma nova pasta dentro de ‘app/graphql’ chamada ‘resolvers’ e adicione o arquivo ‘find_game.rb’. Nele vamos colocar o seguinte código:

# app/graphql/resolvers/find_game.rb
class Resolver::FindGame < GraphQL::Function
  argument :id, !types.Int
  type Types::GameType

  def call(root, args, ctx) do
    game = Game.find_by_id(args[:id])
    game.nil? ? Game.nil_game : game
  end
end

A estrutura interna do resolver é bem parecida com a definição do tipo, a grande diferença é que usamos o método 'call' para conter a lógica.

Agora, lá na definição do tipo em 'app/graphql/types/query_type.rb' modificamos o campo 'game' para o seguinte:

# app/graphql/types/query_type.rb
field :game, function: Resolvers::FindGame.new

Agora, conseguimos executar todas as queries de maneira um pouco mais segura.

Criar Resolvers dedicados é uma boa ideia quando temos muita lógica de negócio pois eles são fáceis de testar (basta instanciar o resolver e chamar 'call'). Outra boa refatoração que pode ser feita é extrair toda aquela lógica que está no 'mutation_type.rb' para um resolver dedicado.

Nos próximos posts vamos explorar como criar tipos de inputs para reduzir a duplicação nas definições de tipos. Então acompanhem os próximos posts e compartilha essa série de tutoriais com as pessoas que você acha que gostariam de aprender mais sobre GraphQL!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s