Arquivo

Textos com Etiquetas ‘REST’

Jim Webber faz consultoria na Locaweb

5 de maio de 2010

Jim Webber possui uma vasta experiência em arquitetura e desenvolvimento de Web Services. Atualmente está trabalhando na ThoughtWorks de Londres e finalizando seu novo livro REST in Practice: Hypermedia and Systems Architecture, que tem previsão de publicação para setembro desse ano.

Durante toda essa semana, ele está na Locaweb prestando consultoria para as equipes de tecnologia da empresa.

Eu e Jim Webber na Locaweb

Ontem foi a vez da equipe de SaaS, a qual eu faço parte. Mostramos a ele nossos sistemas, arquiteturas, tecnologias, formas de trabalho, desafios, etc. Também falamos dos nossos problemas atuais e, é claro, fizemos um monte de perguntas.

O Jim nos ajudou com algumas dúvidas e nos propos vários soluções e caminhos que podem ser tomados. Ele é um cara bastante extrovertido e simpático. Passamos o dia todo conversando e foi uma experiência muito boa para toda a equipe.

Eu particularmente tive um desafio pessoal, pois apresentei ao Jim o atual projeto de Comércio Eletrônico que estamos desenvolvendo. Foi minha primeira apresentação em inglês e logo para um renomado expectador. Bom, pelo feedback dos meus companheiros de equipe, e do próprio Jim, tudo ocorreu bem.

Arquitetura, Ruby, TDD , , , , , , , , ,

Métodos que retornam mais de um valor em Ruby

20 de fevereiro de 2010

Nas últimas semanas, a equipe que eu trabalho estava desenvolvendo um web service onde havia a necessidade de renderizar o retorno de uma lógica de negócio em representações XML. Digo representações (no plural), pois para um retorno com sucesso a representação seria uma e para retorno com erro a representação seria outra.

Por exemplo, um retorno com sucesso:

<natural-person>
  <name>Prodis</name>
  <cpf>01234567890</cpf>
</natural-person>

E um retorno sem sucesso:

<error>
  <description>CPF inválido.</description>
</error>

Como era um web service em REST, para sucesso retornamos o código de status HTTP “200 OK” e, dependendo do não sucesso da operação, o código de status HTTP da resposta poderia ser “400 Bad Request”, “404 Not Found” ou qualquer outro código 4xx ou 5xx que melhor se adequasse.

Mantendo um controller magro, a idéia era somente instanciar uma classe de negócio, chamar um método e renderizar o retorno. Algo como:

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    natural_person = business.search_by_cpf params[:cpf]

    # TODO: Renderizar pessoa física ou mensagem de erro
  end
end

A partir daqui a equipe iniciou uma discussão sobre a melhor forma de se obter o(s) retorno(s) esperado(s). O controller precisava saber se a consulta havia sido feita com sucesso, para renderizar um objeto NaturalPerson retornando o código de status HTTP 200, ou se a consulta não tivesse sucesso, renderizar a mensagem de erro retornando um código de status HTTP 4xx adequado.

Como “bons programadores .NET e Java”, a primeira coisa que pensamos foi lançar uma exceção customizada caso a consulta não tivesse sucesso, capturar essa exceção no controller e, através das informações de descrição de erro e código de status HTTP contidas nessa exceção, renderizar o retorno adequado.

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new

    begin
      natural_person = business.search_by_cpf params[:cpf]

      render natural_person, 200
    rescue BusinessException => e
      render e.error, e.status_code
    end
  end
end

A gente não tinha visto muito código Ruby utilizando begin rescue, então essa solução não nos pareceu muito “Ruby way”. Achamos melhor pedir a opinião de alguém com mais experiência em Ruby. Perguntamos ao Rafael Rosa, que nos disse que cada vez que lançamos uma exceção em Ruby “alguma coisa ruim acontece no servidor” e consequentemente a aplicação ficará mais lenta.

Ele indicou um post falando a respeito:
http://www.simonecarletti.com/blog/2010/01/how-slow-are-ruby-exceptions

Rafael Rosa nos sugeriu retornar um array de duas posições: uma com o código de status HTTP e outra com o objeto a ser renderizado.

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    result = business.search_by_cpf params[:cpf]

    render result[1], result[0]
  end
end

Resolveu, mas o código não ficou muito intuitivo. A partir daí imaginamos algumas outras soluções.

Retornar um hash:

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    result = business.search_by_cpf params[:cpf]

    render result[:data], result[:status_code]
  end
end

Criar uma classe de retorno:

class SomeBusinessResult
  attr_accessor :status_code, :data
end
class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    result = business.search_by_cpf params[:cpf]

    render result.data, result.status_code
  end
end

Posteriormente, o Rafael Rosa também sugeriu essa última opção, mas criando uma Struct ao invés de uma classe.

Então eu sugeri o método search_by_cpf retornar dois valores. Todos da equipe me perguntaram: “Como assim retornar dois valores?”. Falei que em Ruby um método pode retornar vários valores, que vi isso no livro The Ruby Programming Language.

O código do controller ficou bem mais intuitivo:

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    status_code, data = business.search_by_cpf params[:cpf]

    render data, status_code
  end
end

O método search_by_cpf está retornando tanto o código de status HTTP quanto os dados para serem renderizados:

class SomeBusiness
  def search_by_cpf(cpf)
    # Lógica de negócio aqui
    return 200, natural_person
  end
end

Note que mesmo a linha 4 sendo a última linha de instrução do método, o retorno de mais de um valor obrigatoriamente precisa utilizar o comando return.

Quando há mais de um valor de retorno para um método, os valores são colocados implicitamente dentro de uma array e essa array fica sendo o único retorno do método.

O mesmo resultado seria obtido dessa forma:

class Business
  def search_by_cpf(cpf)
    # Lógica de negócio aqui
    [200, natural_person]
  end
end

Quem está consumindo um método que retorna mais de um valor, pode utilizar o recurso de atribuição paralela do Ruby para distribuir os valores de retorno em variáveis distintas, como é o caso no nosso controller:

class NaturalPersonController < ActiveRecord::Controller
  def index
    business = SomeBusiness.new
    status_code, data = business.search_by_cpf params[:cpf]

    render data, status_code
  end
end

O dinamismo do Ruby lhe oferece várias opções para você encontrar soluções para o mesmo problema ou questão. Cabe a você decidir qual melhor abordagem para seu tipo de problema. O interessante é você conhecer essas opções para facilitar a sua decisão.

Ruby , , ,