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.