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: