Desenvolvendo uma API Ruby on Rails com GraphQL

O cenário de desenvolvimento web está em constante evolução, e a escolha da arquitetura para as APIs é muito importante e desempenha um papel crucial no sucesso e evolução de um projeto. Duas das abordagens mais populares são REST (Representational State Tranfer) e GraphQL. Neste artigo vamos abordar o GraphQL…

O cenário de desenvolvimento web está em constante evolução, e a escolha da arquitetura para as APIs é muito importante e desempenha um papel crucial no sucesso e evolução de um projeto. Duas das abordagens mais populares são REST (Representational State Tranfer) e GraphQL. Neste artigo vamos abordar o GraphQL e sua integração com o Ruby on Rails.

GraphQL vs REST

Antes de nos aprofundarmos na implementação do GraphQL, seria interessante entender a diferença entre GraphQL e REST. Mesmo o REST, sendo uma escolha mais popular e confiável, com sua estrutura baseada em endpoints específicos. O GraphQL surge como uma alternativa interessante para o desenvolvimento de APIs.

Arquitetura REST

Em REST, cada recurso é mapeado para um endpoint específico, que é uma URL única dentro do sistema. Cada operação que você deseja realizar em um recurso é mapeada para um método HTTP específico, como: GET, POST, PUT e DELETE.

Entretanto, o REST enfrenta alguns desafios, como: Over-fetching (Sobrecarga de dados), Under-fetching (Subcarga de dados), Versionamento de novos recursos.

Arquitetura GraphQL

O GraphQL por outro lado, introduz uma abordagem bem mais flexível. Os clientes solicitam exatamente os dados necessários em uma única consulta, evitando a sobrecarga de dados. Isso oferece uma vantagem significativa em termos de eficiência.

Além de reduzir os problemas com versionamento, dando a possibilidade de adicionar campos ou tipos novos sem quebrar clientes existentes.

GraphQL: Uma Visão Geral

Diferentemente das abordagens tradicionais, onde os clientes frequentemente recebem dados fixos a partir de um endpoint específico. O GraphQL permite que os clientes solicitem apenas os dados necessários. Isso é possível por meio de uma única rota, normalmente chamada /graphql, e os clientes especificam a estrutura exata dos dados desejados na consulta.

Consultas (Queries) em GraphQL

As consultas seguem uma estrutura hierárquica, onde o cliente especifica quais campos e relacionamentos ele precisa obter. Um exemplo de consulta básica em GraphQL:

query {
  author {
    name
    books {
      title
    }
  }
}

Neste exemplo, a consulta está solicitando dados de um autor, incluindo seu nome e uma lista de seus livros com os títulos.

  • Campos e Tipos: Cada campo em uma consulta tem um tipo específico de dado associado. O servidor GraphQL responde com os dados solicitados, mantendo a estrutura hierárquica da consulta.

Mutações em GraphQL

Mutations são usadas para realizar operações de escrita ou alterações no servidor GraphQL. Elas são semelhantes às consultas, mas são usadas para indicar ao servidor que uma alteração de estado deve ser realizada. Um exemplo de mutação em GraphQL:

mutation {
  createAuthor(name: "John Green") {
    id
    name
  }
}

Neste exemplo, a mutação está criando um novo autor com o nome “John Gree”. A resposta do servidor incluirá os campos especificados na mutação, como id e name.

  • Retorno Personalizado: O servidor GraphQL pode ser configurado para retornar dados específicos após a execução de uma mutação. Isso permite que o cliente obtenha informações atualizadas após uma operação de escrita.

Em resumo, consultas são usadas para recuperar dados, enquanto mutações são usadas para realizar operações de escrita ou alterações no servidor. Juntas, elas formam uma maneira poderosa e flexível de interagir com APIs, permitindo que os clientes especifiquem exatamente os dados de que precisam e realizem operações complexas de maneira eficiente.

Criando uma API

Iniciando um projeto Ruby on Rails

Primeiro, nós precisamos criar um novo projeto rails. Execute o comando abaixo no seu console, para iniciar um novo projeto.

rails new graphql-api --webpack=react

Após criarmos o nosso projeto, vamos precisar criar nosso modelos (Model). Para esse rápido exemplo, vamos criar dois modelos, Author e Book.

rails g model Author first_name:string last_name:string
rails g model Book title:string description:string author:references

Para adicionar o relacionamento entre autor e livro, navegue até o arquivo app/models/author.rb e adicione has_many :books, dependent: :destroy.

class Author < ApplicationRecord
    has_many :books
end

Com os nosso modelos criados, no arquivo db/seeds.rb, vamos popular nosso projeto com alguns dados.

rick = Author.create(first_name: "Rick", last_name: "Riordan")

books_data = [
    { title: "The Lightning Thief", description: "Percy Jackson and the Olympians", author: rick },
    { title: "The Sea of Monsters", description: "Percy Jackson and the Olympians", author: rick },
]

Book.create(books_data)

Execute o comando a seguir no terminal.

rails db:create db:migrate db:seed

Adicionando o GraphQL ao projeto Ruby on Rails

Depois de fazermos o setup inicial do nosso projeto. Vamos adicionar e instalar o GraphQL, com os seguintes comandos no terminal.

bundle add graphql
rails generate graphql:install

Agora com o GraphQL configurado no nosso projeto, podemos observar que esses comandos criaram uma pasta dentro do diretório app, chamada graphql. Onde nesta pasta, podemos ver uma estrutura criada com o arquivo graphql_api_schema.rb e três pastas: mutations, resolvers e types.

Confira o arquivo graphql_api_schema.rb. É aqui onde declaramos para onde todas as consultas e mutações devem ir.

class GraphqlApiSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)
end

Olhando o arquivo config/routes.rb, podemos observar que foi montado uma rota através do GraphiQL::Rails. Onde nos permite testar queries e mutações usando uma interface web.

Rails.application.routes.draw do
  mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" if Rails.env.development?

  post "/graphql", to: "graphql#execute"
end

Criar e executar queries

Vamos remover o conteúdo de exemplo, no arquivo app/graphql/types/query_type.rb, e adicionar dois campos, chamados authors e books. Observe que cada campo tem um nome, um tipo e um null: que pode ser true ou false. Também podemos colocar uma descrição, mas a mesma não é obrigatória.

Também precisamos criar um método que vai nos retornar os objetos para cada campo.

module Types
  class QueryType < Types::BaseObject
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    field :authors, [Types::AuthorType], null: false, description: "Returns a list of authors"
    field :books, [Types::BookType], null: false, description: "Returns a list of books"

    def books
      Book.all
    end

    def authors
      Author.all
    end
  end
end

Agora nós precisamos criar o AuthorType e BookType, usando a gem GraphQL. Para isso, execute os comandos a seguir no terminal.

rails g graphql:object book
rails g graphql:object author

Esses comandos vão criar os arquivos types/author_type.rb e types/book_type.rb, já adicionando os campos automaticamente como mágica. Agora precisamos incluir os campos que possuem um tipo específico e as opções null.

module Types
  class BookType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: true
    field :description, String, null: true
    field :author_id, Integer, null: false
    field :author, Types::AuthorType, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
  end
end

Na classe AuthorType vamos criar um método e um campo para juntar os campos first_name e last_name, chamado full_name. Isso mostra a flexibilidade que temos para criar novos campos modificados, usando os dados que temos do objeto.

module Types
  class AuthorType < Types::BaseObject
    field :id, ID, null: false
    field :first_name, String, null: true
    field :last_name, String, null: true
    field :full_name, String, null: true
    field :books, [Types::BookType], null: true
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false

    def full_name
      [object.first_name, object.last_name].compact.join(" ")
    end
  end
end

Agora podemos iniciar o servidor Rails, executando rails s em um console. Em um browser abrindo a URL: http://localhost:3000/graphiql, teremos essa tela.

Executando a seguinte busca.

query {
  books {
    title
    description
    author {
      fullName
    }
  }
}

Vamos obter os dados que foram populados com o seed.

Criar e executar mutações

Como foi falado acima, nós usamos mutações para realizar operações de escrita ou alterações. Para isso vamos para o arquivo app/graphql/mutations/base_mutation.rb, e vamos deixar ele exatamente assim.

module Mutations
  class BaseMutation < GraphQL::Schema::Mutation
  end
end

Com isso, já podemos criar nossas mutações. Usando os seguintes comandos no terminal, vamos criar as mutações para criar um autor e um livro.

rails g graphql:mutation create_author
rails g graphql:mutation create_book

Esses comandos vão criar os arquivos app/graphql/mutations/create_author.rb e app/graphql/mutations/create_book.rb.

Com os arquivos em mãos, vamos precisar adicionar os argumentos que podemos receber, o tipo da mutação e o método resolve que vai conter a lógica da mutação.

module Mutations
  class CreateAuthor < BaseMutation
    argument :first_name, String
    argument :last_name, String

    type Types::AuthorType

    def resolve(first_name:, last_name:)
      Author.create!(first_name: first_name, last_name: last_name)
    end
  end
end
module Mutations
  class CreateBook < BaseMutation
    argument :title, String, required: true
    argument :description, String, required: true
    argument :author_id, Integer, required: true

    type Types::BookType

    def resolve(title:, description:, author_id:)
      Book.create!(title: title, description: description, author_id: author_id)
    end
  end
end

Vamos testar!

O interessante do GraphQL, é que o cliente pode escolher qual o retorno que ele deseja ao executar uma mutação. Podendo chamar até métodos que não são campos em do objeto. Como esse exemplo, onde o retorno foi um método, chamado full_name, que é derivado dos campos first_name e last_name.

Vamos aproveitar o segundo teste para ver como os erros do GraphQL, voltam para o cliente.

Podemos observar que ele retorna um erro, indicando que o Author deve existir, pois esse id que foi enviado, não existe na base de dados.

  • O ponto positivo é que o backtrace do erro, é enviado também, isso pode ajudar bastante, principalmente no ambiente de desenvolvimento.
  • Por outro lado, quem está acostumado com erros vindos de APIs REST, podem sentir falta de um status HTTP, indicando o tipo do erro. Fazendo com que o cliente precise tratar esses tipos de erro de uma maneira diferente.

Para finalizarmos um famoso CRUD, e deixar o artigo um pouco mais completo. Iremos criar as classes para atualizar e remover um autor. Vamos usar o mesmo comando que executamos para criar uma mutação de create. Entretanto, agora é para atualizar e remover.

rails g graphql:mutation update_author
rails g graphql:mutation remove_author

Seguindo a mesma lógica, precisamos adicionar os argumentos, tipo e método resolve a cada mutação.

module Mutations
  class UpdateAuthor < BaseMutation
    argument :id, ID, required: true
    argument :first_name, String
    argument :last_name, String

    type Types::AuthorType

    def resolve(id:, first_name: nil, last_name: nil)
      author = Author.find(id)
      author.update!(first_name: first_name, last_name: last_name)

      author
    end
  end
end
module Mutations
  class RemoveAuthor < BaseMutation
    argument :id, ID, required: true

    type Types::AuthorType

    def resolve(id:)
      author = Author.find(id)
      author.destroy

      author
    end
  end
end

Agora, vamos executar essas mutações.

O nosso autor foi atualizado com sucesso!

E por último, removemos o autor. E na segunda tentativa, recebemos o erro de que o autor com id=10, não pode ser encontrado.

Com isso, finalizamos nossa pequena API Ruby on Rails com GraphQL.

Conclusão

Desenvolver uma API Ruby on Rails utilizando GraphQL, oferece uma abordagem poderosa e flexível, visando atender às demandas cada vez mais complexas do desenvolvimento web. Ao decorrer deste artigo, exploramos a criação de consultas personalizadas, minimizando o excesso de dados transferidos, e a implementação eficaz de mutations. Proporcionando uma estrutura robusta para a manipulação de dados.

A combinação da produtividade do Ruby on Rails com a flexibilidade do GraphQL abre novas possibilidades para o desenvolvimento de APIs eficientes. Os desenvolvedores podem construir sistemas mais flexíveis, oferecendo uma experiência otimizada tanto para os desenvolvedores quanto para os clientes. A junção dessas tecnologias representa um passo significativo em direção a um desenvolvimento de software mais dinâmico e adaptável às demandas atuais de software.

0 Comentário