Cache de invocações a serviço Rest? (Restfulie)

Olá amigos do forum,

Estou trabalhando em um projeto onde estamos planejando criar uma camada de serviço que será acessada a partir de vários clients. Nossa idéia é criar uma API REST para expor esses serviços usando Restfulie/Vraptor.

Uma dúvida que surgiu é com relação ao cache do que será retornado da chamada para o serviço. Li nos docs que o Restfulie consegue incluir metadados/heades referentes ao cache no lado do servidor (last modified, etag, max age, etc). Mas esse resultado pode ser cacheado em um client Java?

Não sei se estou sendo claro, mas supondo um código usando commons-http que faça a chamada para a URI do serviço que retorna uma entidade, uma vez que o Restfulie inclui as informações de cache no response, esse resultado já será cacheado na aplicação client “magicamente”?

(aproveitando, o Restfulie tem API pro lado client?)

Obrigado amigos!

Ih amigos deveria ter pesquisado mais antes de incomodá-los no fórum. Obviamente o cache não será feito de maneira magica no client, hehe.

Encontrei essa classe no commons-http que parece que será a salvação da lavoura:

http://hc.apache.org/httpcomponents-client-ga/httpclient-cache/apidocs/org/apache/http/impl/client/cache/CachingHttpClient.html

De qualquer forma, vocês consideram que essa abordagem é válida? Invocar um serviço REST e manter o cache no meu client, programaticamente?

Obrigado!

o que vai estar em caché? dados? esses dados são consultados no banco? sua API de ORM está com o cache habilitado? 1º e 2º nível?

[quote=felipeguerra]o que vai estar em caché? dados? esses dados são consultados no banco? sua API de ORM está com o cache habilitado? 1º e 2º nível?

[/quote]

Sim. Sim. Sim. Sim.

Mas independente da API de ORM estar com o cache habilitado, o que eu desejo cachear é a resposta da chamada HTTP, uma vez que pra quem vai invocar o serviço a origem dos dados vai ser transparente.

se o servidor retornou na requisição os headers de cache (ETag, LastModified, etc), você não só pode como deve cachear o conteúdo ou pelo menos fazer a requisição condicional (If-None-Match, If-Modified-Since).

e o httpclient faz isso com o caching-http-client.

beleza Lucas, minha idéia era isso mesmo. De fato, parece que o httpclient resolve esse galho. Os caras que voce citou ((If-None-Match, If-Modified-Since) são o que, headers que devo passar na requisição?

Aproveitando, eu estava lendo nessa documentação do Restfulie aqui, https://github.com/caelum/restfulie-java, e sobre o cache diz pra implementar a interface RestfulEntity. Essa implementação deve ser feita na classe que expoe o serviço (o controller, por ex.) ou na classe do recurso (que seria a entidade)? Percebo também que os exemplos contem entidades com anotações do XStream, o Restfulie não trabalha com as anotações do JAX-B?

Valeu!

em geral, se vc usa um cliente que suporta caching, ele manda esses headers já automaticamente, se a resposta do servidor tiver ETag ou Last-Modified… se vc conseguir monitorar o servidor pra ver se esses headers estão sendo enviados.

o restfulie usa o XStream, mas vc consegue sobrescrever um componente pra ele começar a usar o jaxB sem problemas.

e sobre a interface RestfulEntity, a implementação dela vai na entidade ou na classe do serviço?

Valeu Lucas.

depende =)

o ideal é na classe que representa o recurso - a entidade.

pelo nome da interface (RestfulEntity) era de se supor, me perdoe a ignorancia :lol: ,

mas não é um pouco intrusivo isso? se eu quiser retornar uma entidade qualquer lá do meu modelo, a princípio essa classe não deveria se preocupar com cache…

não, mas ela sabe quando ela foi modificada pela última vez, e sabe gerar o ETag de acordo com os seus dados (um hash md5 de alguns campos por exemplo)… a única coisa estranha é o maximum age…

mas é razoável supor que a entidade saiba mais ou menos de qto em qto tempo ela é executada.

Uma entidade não precisa ser apenas um monte de atributo e getters/setters :wink:

Hm, tem razão…essas informações, embora não sejam necessiariamente pertinentes para o funcionamento interno da classe, são parte do dominio da entidade. Faz sentido :stuck_out_tongue:

Valeu lucas, brigadao.

Voltando ao assunto, Lucas, pode dar mais uma ajudinha aqui? Tô fazendo uma provinha de conceito aqui sobre esses items,

O meu controller é esse aqui,

package br.com.service.controller;

import static br.com.caelum.vraptor.view.Results.*;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;
import br.com.service.model.Model;

@Resource
public class ModelController {
	
	private Result result;
	
	public ModelController(Result result){
		this.result = result;
	}
	
	public void all(){
		
		Model model = new Model(1, "Model 1");
		
		result.use(representation()).from(model).serialize();
	}
}

a classe do recurso,

package br.com.service.model;

import java.util.Calendar;

import org.joda.time.DateTime;

import br.com.caelum.vraptor.restfulie.hypermedia.HypermediaResource;
import br.com.caelum.vraptor.restfulie.relation.RelationBuilder;
import br.com.caelum.vraptor.restfulie.resource.RestfulEntity;

public class Model implements RestfulEntity, HypermediaResource {
	
	private int id;
	private String name;
	
	public Model(){}
	
	public Model(int id, String name) {	
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}
	public String getName() {
		return name;
	}

	@Override
	public int getMaximumAge() {
		return 300;
	}

	@Override
	public String getEtag() {
		return id + "." + name.toLowerCase();
	}

	@Override
	public Calendar getLastModified() {
		return DateTime.now().toGregorianCalendar();
	}

	@Override
	public void configureRelations(RelationBuilder builder) {
		
	}
}

Fiz um client usando o http-client (a proposito onde estão os jars do client do restfulie, no repositorio do github?), e os cabeçalhos sobre o cache funcionaram mesmo (viva!). Só que,

Quando eu faço o controller assim, forçando o retorno para json

Model model = new Model(1, "Model 1");
		
result.use(json()).from(model).serialize();

os headers (etag, last-modified, etc) não são adicionados. é assim mesmo que é pra ser?

os cabeçalhos também só foram adicionados quando, alem de RestfulEntity, eu implementei HypermediaResource na classe Model. Essa interface também é imperativa?

Valeu!

eh, quem coloca os headers de cache é esse cara:

então só funciona com o representation() mesmo e com HypermediaResource, infelizmente.

abre uma issue pedindo pra mudar isso por favor?

e se quiser fazer um pull request melhor ainda =)

pra funcionar com o json() vc pode criar uma implementação sua do JSONSerialization que usa a mesma lógica do DefaultRepresentationResult (com o RestfulEntity se vc preferir)

vc pode criar uma classe que estende XStreamJSONSerialization e sobrescrever o método from colocando o código dos headers a mais.

Dá uma olhada ai Lucas

package br.com.service.component;

import javax.servlet.http.HttpServletResponse;

import br.com.caelum.vraptor.interceptor.TypeNameExtractor;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.RequestScoped;
import br.com.caelum.vraptor.restfulie.RestHeadersHandler;
import br.com.caelum.vraptor.restfulie.hypermedia.HypermediaResource;
import br.com.caelum.vraptor.serialization.ProxyInitializer;
import br.com.caelum.vraptor.serialization.Serializer;
import br.com.caelum.vraptor.serialization.xstream.XStreamBuilder;
import br.com.caelum.vraptor.serialization.xstream.XStreamJSONSerialization;

@Component @RequestScoped
public class JSONResourceSerialization extends XStreamJSONSerialization {

	private RestHeadersHandler headersHandler;
	
	public JSONResourceSerialization(HttpServletResponse response, TypeNameExtractor extractor, ProxyInitializer initializer, XStreamBuilder builder, RestHeadersHandler headersHandler) {
		super(response, extractor, initializer, builder);
		
		this.headersHandler = headersHandler;
	}

	@Override
	public <T> Serializer from(T object, String alias) {
		
		if(HypermediaResource.class.isAssignableFrom(object.getClass())) {
			headersHandler.handle(HypermediaResource.class.cast(object));
		}
		
		return super.from(object, alias);
	}
}

funcionou com o json()

cara mais uma duvida, por favor…a resposta também está retornando o JSESSIONID, via cookie. é o vraptor que está criando a sessão?

e outra menos importante, hehe, nessa classe ai a principio eu extendi RestfulSerializationJSON, mas ocorreu um erro do Spring informando que não havia nenhuma implementação da interface Restfulie (mas tem, br.com.caelum.vraptor.restfulie.DefaultRestfulie). Algum motivo pra esse erro (aliás pra que serve exatamente a classe RestfulSerializationJSON :lol: )?

edit: ops abro a issue sim, e esse detalhe que os headers só são adicionados pra objetos do tipo HypermediaResource, comenta na issue pra alterar tambem?

Valeu Lucas.

acho que essa restfulxxxjson não está suportada oficialmente… é uma implementação parcial…

você não usa a sessão em nenhum outro lugar? o vraptor não tem nenhum componente sessionScoped registrado. Só talvez use a sessão pra implementar o FlashScope

não, tô fazendo um teste bem básico, meu “client” é só uma chamada pra url

public static void main(String[] args) throws Exception {
		
		Response response = Request.Get("http://localhost:8080/service/model/all").addHeader("Accept", "application/json").execute();
		
		for (Header header : response.returnResponse().getAllHeaders()){
			System.out.println(header.getName() + " - " + header.getValue());
		}
		  
	}

só se o http-client estiver passando alguma coisa na invocação, vou ver na documentação.

bom e a bagaça aí do json, era isso que voce tinha sugerido?

valeu mano

era isso mesmo… se vc não quiser implementar o HipermediaResource na entidade, vc pode trocar o teste pra RestfulEntity na classe do json.

mas o método handle da interface RestHeadersHandler só aceita um HypermediaResource como parametro, e ele já tem a lógica pra adicionar o header…

vou abrir a issue lá, na descrição posso comentar sobre a obrigatoriedade dessa interface? na documentação tambem diz pra implementar apenas RestfulEntity

valeu

comenta isso sim

valeu =)