Ao desenvolver clientes REST com ActiveResource, você precisa se lembrar de que não está de fato trabalhando com uma conexão de banco de dados. Por exemplo, há muita coisa acontecendo para fazer um método localizar funcionar:
Enviando a solicitação GET.
O que quer que o servidor precise fazer para processar a solicitação, o que pode incluir obter dados de um banco de dados, serializar a resposta e qualquer trabalho extra que o servidor precise fazer.
Enviando a resposta pela rede.
O cliente desserializa os dados e retorna uma nova instância do modelo.
Uma maneira de reduzir a quantidade de trabalho desnecessário é implementar um cache de leitura. Fazer isso aumenta bastante o desempenho, mas apresenta outro problema: caches inválidos. Ou seja, se a API fizer alterações em um recurso, ficaremos presos a um recurso armazenado em cache inválido.
Felizmente, o HTTP vem com um método para invalidar caches: cabeçalhos Etag.
Como funciona
Quando o servidor retorna um recurso, ele também envia seu Etag (normalmente um hash gerado a partir da data da última atualização) em um cabeçalho HTTP “Etag”.
O cliente então armazena em cache o recurso e seu Etag. Quando o cliente envia uma solicitação para obter o mesmo recurso, ele também envia seu Etag em cache em um cabeçalho “If-None-Match”.
O servidor então compara o “If-None-Match” recebido com o Etag atual. Se eles corresponderem, isso significa que o recurso em cache do cliente é válido e o servidor responderá com uma resposta 304 (não modificada) muito curta que não incluirá o conteúdo do recurso; se eles não corresponderem, ele responderá com uma resposta completa que inclui o conteúdo do recurso.
Se o cliente obtiver a resposta 304, ele usará seu recurso em cache; caso contrário, ele atualizará seu cache com o novo recurso e o usará.
Aqui, vou mostrar como implementar o cache no suporte ativo e como usar Etags para invalidar o cache.
O servidor
require "sinatra"
require "sinatra/activerecord"
set :database, "sqlite3:///foo.sqlite3"
class User < ActiveRecord::Base
end
get '/users/:id.json' do
@user = User.find(params[:id])
etag @user.updated_at
sleep 4
content_type :json
@user.to_json
end
put '/users/:id.json' do
@user = User.find(params[:id])
@user.touch(:updated_at)
content_type :json
@user.to_json
end
Esta é uma API muito simples no sinatra. Para responder a uma solicitação para obter um usuário, basta carregá-lo do banco de dados, simular algum trabalho aguardando 4 segundos e, em seguida, retornar uma representação json do usuário.
A mágica está na chamada para o método entity_tag do sinatra (apelidado de etag), que lida com a implementação do servidor de cache etag.
O cliente
class User < ActiveResource::Base
end
O Cache
module ActiveResourceCaching
extend ActiveSupport::Concern
included do
class_attribute :cache
self.cache = nil
end
module ClassMethods
def cache_with(*store_option)
self.cache = ActiveSupport::Cache.lookup_store(store_option)
self.alias_method_chain :get, :cache
end
end
def get_with_cache(path, headers = {})
cached_resource = self.cache.read(path)
response = if cached_resource && cached_etag = cached_resource["Etag"]
get_without_cache(path, headers.merge("If-None-Match" => cached_etag))
else
get_without_cache(path, headers)
end
return cached_resource if response.code == "304"
self.cache.write(path, response)
response
end
end
ActiveResource::Connection.send :include, ActiveResourceCaching
ActiveResource::Connection.cache_with :file_store, '/tmp/cache'
Esta é uma implementação de um cache de leitura localizado no topo da classe de conexão do recurso ativo.
A beleza disso é que, graças à grandiosidade dos recursos ativos, você pode usar qualquer armazenamento de cache que desejar.