APIs RESTful com Grape – Parte 2

Após um longo, longo, longo período, aqui estou eu de novo para fazer a segunda parte da série de artigos sobre a construção de APIs RESTful com Grape. Sem muita enrolação, vamos direto ao ponto! Relembrando, todo o código fonte está no Github.

app/api/v*/base.rb

No artigo anterior, eu não mostrei como fica o código dos arquivos base.rb que estão nos diretórios de cada versão de nossa API. Vou começar mostrando eles aqui, até mesmo porque, na versão anterior que está no Github, havia um pequeno problema: eu não tinha definido o formato padrão a ser usado para representar os recursos retornados por nossa API. Abaixo, temos o arquivo app/api/v1/base.rb. Não mostraremos o arquivo da versão 2, pois é idêntico a este.

require 'api/v1/cervejas'
require 'api/v1/tipos'
require 'models/model'
require 'api/v1/entities/entity'

module API

  module V1

    class Base < Grape::API       
      version 'v1', :using => :header, :vendor => 'alienlabz', :format => :json
      format :json

      mount API::V1::Cervejas => '/'
      mount API::V1::Tipos => '/'
    end

  end

end

Inicialmente, importamos os arquivos necessários para essa versão. Em seguida, criamos a classe Base, herdando de Grape::API, e definimos como acessar uma versão específica da API. Como eu já havia explicado isso no artigo anterior, vou só copiar e colar o que escrevi lá: definimos que a versão da API será obtida através do cabeçalho Accept do HTTP. Para que esta versão da API seja invocada, o cabeçalho Accept deverá estar escrito assim: application/vnd.alienlabz-v1+json. Isto é um padrão, não se preocupe em entender os motivos de ele estar definido assim. Obviamente, caso queira acessar outra versão da API, basta trocar o v1 por v2, por exemplo.

Pronto, esses arquivos contém apenas e unicamente isto! Difícil? Duvido!

As Entidades

A segunda ideia que trataremos nesse artigo são as entidades. Observe que no nosso arquivo app/api/v1/tipos.rb, no método get, retornamos um Array contendo uma única instância do Model que representa um tipo de cerveja, certo? Mas, será que essa é a melhor maneira? Eu acredito que não. E por diversos motivos. Vou citar só dois:

  1. Um model é a sua unidade de negócio e contém informações que você não quer ver expostas através da API.
  2. Não poluir seus models com informações sobre como eles serão expostos através de uma API RESTful. E se no futuro você também quiser expor através de uma API SOAP? Vai poluir esse model ainda mais?

Pensando nisso, a galera do Grape também criou o Gem grape-entity. Vamos começar instalando-o: gem install grape-entity. Em seguida, vamos abrir o arquivo app/api/v1/entities/entity.rb. Observe que este arquivo contém todas as nossas entidades, contudo, assim como ocorre com os seus modelos, você pode separar cada entidade em um arquivo específico. Talvez façamos isso no futuro.

Uma entidade é simplesmente uma classe que estende de Grape::Entity e que define quais atributos serão expostos através da API. É claro que ele não é apenas isso, pois existem dezenas de outras funcionalidades. Mas, para este artigo, vamos nos ater apenas à sua funcionalidade básica.

module Entities

  class Cerveja < Grape::Entity
    expose :id
    expose :nome
    expose :tipo
  end

  class Tipo < Grape::Entity
    expose :id
    expose :nome
  end

end

Note como usamos o método expose para informar quais atributos estarão disponíveis através de cada entidade. O Grape Entity é poderoso e nos permitir usar expressões mais complexas para definir se um atributo será exposto ou não. Por exemplo, através dele podemos aplicar a ideia de respostas parciais, conforme discutido neste artigo do Google. Não se preocupe, pois faremos isso no futuro.

Agora precisamos de apenas uma alteração a mais: informar na nossa API que queremos retornar uma entidade e não o model. Para isso, vamos abrir novamente o arquivo /app/api/v1/tipo.rb e fazer uma pequena alteração no método get, conforme vemos no código abaixo:

        get do
          tipo = Models::Tipo.new
          tipo.id = 1
          tipo.nome = 'Pilsen'

          present [tipo], :with => Entities::Tipo
        end

Notou a diferença? Está ali, na penúltima linha. Antes, retornávamos [tipo], e agora estamos usando a expressão present [tipo], :with => Entities::Tipo. Acho que é auto-explicativo, mas vamos lá! O método present é do Grape e recebe como parâmetro o recurso, no nosso caso uma lista de tipos, e a entidade que será usada para representar esse recurso. Simples assim!