
Recentemente tivemos que desenvolver um webservice para uma operadora de telefonia colombiana para atender a uma demanda urgente da presidencia. O webservice precisaria consumir um serviço REST (que trafega XML), fazer o parsing do XML, aplicar as regras de negócio definidas pela operadora e entregar a resposta em JSON que seria por sua vez usada em um aplicativo móvel.
Nosso webservice seria usado para exibir diversos dados para o cliente final da companhia telefônica. O aplicativo móvel segrega os dados em diversas telas, porém, o serviço do qual buscamos os dados “brutos” nos entrega as informações todas misturados: não há como buscar da fonte apenas os registros que são usados para a tela 1, depois buscar os dados para a tela 2 e assim por diante.
Nesse cenário, para preencher a tela 1 ou a tela N, precisamos tirar do serviço “fonte” o mesmo fluxo de dados.
Um outro complicador é que esse serviço REST é bastante lento para responder. Para evitar que o aplicativo móvel tenha que “pagar o mesmo preço” sempre que o usuário final navegue em diferentes telas resolvemos, entre outras providências e otimizações, implementar uma solução de caching do XML que o REST retorna.
Usamos o Spring Cache e o EhCache para guardar em memória o resultado do serviço REST. O snippet abaixo mostra o código da classe de configuração do Spring similar à que usamos na solução final.
package br.com.omniatech.examples.caching.configuration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import net.sf.ehcache.config.CacheConfiguration;
@Configuration
@EnableCaching
@ComponentScan(basePackages = { "br.com.omniatech" })
public class SpringConfiguration implements CachingConfigurer {
@Bean(destroyMethod="shutdown")
public net.sf.ehcache.CacheManager ehCacheManager() {
net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
CacheConfiguration cacheConfiguration = new CacheConfiguration();
cacheConfiguration.setName("ExampleCache");
cacheConfiguration.setTimeToLiveSeconds(120);
cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
cacheConfiguration.setMaxEntriesLocalHeap(1000);
config.addCache(cacheConfiguration);
return net.sf.ehcache.CacheManager.newInstance(config);
}
@Bean
@Override
public CacheManager cacheManager() {
net.sf.ehcache.CacheManager ehCacheManager = ehCacheManager();
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(ehCacheManager);
return ehCacheCacheManager;
}
@Bean
@Override
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
@Bean
@Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager());
}
@Bean
@Override
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler();
}
}
O código acima configura todo o framework de cache e cria um cache de nome “ExampleCache” que vai manter os resultados do serviço REST por 120 segundos. Após esse tempo, caso o usuário no aplicativo móvel solicite mais dados a requisição ao REST será feita novamente.
A seguir podemos ver um exemplo de método cujo resultado vai ser “cacheado” pelo Spring:
package br.com.omniatech.examples.caching.business;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class ExampleBusinessLogic {
@Cacheable("ExampleCache")
public String expensiveStringFunction(String string) {
//Sleeping to simulate the expensiveness of the function
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
String now = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(new Date());
System.out.println(String.format("CACHE MISS for key '%1$s' at %2$s", string, now));
return String.format("%1$s was cached at %2$s", string , now);
}
}
Na primeira chamada do método para um determinado valor de “string” o corpo do método é executado porém, qualquer outra chamada dentro do TTL do cache (2 minutos), retornará o valor dessa primeira chamada. O corpo do método não é chamado nesses casos por isso é importante que ele não possua outras responsabilidades que devam ser satisfeitas a cada chamada.
O código fonte completo do projeto de exemplo pode ser baixado no GitHub em: