Turbinando seus Microsserviços com Spring Cache e Redis
Tags: spring, cache, java, redis
Published: 2023-02-28 12:11:14

No decorrer da última década vivenciamos a grande ascensão da arquitetura de microsserviços, antes os times de tecnologia optavam por desenvolver grandes sistemas responsáveis por todas as funcionalidades do negócio. E hoje a principal solução é dividir o domínio do negócio em vários escopos menores e criar pequenos sistemas independentes que os atendam. As vantagens da arquitetura de microsserviços variam desde proporcionar que diferentes times desenvolvam software em ritmos isolados, em até escalar o desempenho de determinadas funcionalidades. 

Na busca pela melhora de desempenho uma das ferramentas utilizadas é o cache que permite aumentar a velocidade de consumo a dados frequentemente acessados. Uma das principais estratégias para o uso de cache é armazenar os dados na memória local do servidor a fim de evitar consultas regulares no banco de dados, APIs externas ou até mesmo processamento de cálculos custosos.

Ao tratar de microsserviços é comum que dado um requisito de disponibilidade ou desempenho seja necessário mais de uma instância dando vida a um cluster. Junto ao ambiente clusterizado vem problemas na implementação de cache local, já que cada uma das instâncias é um processo independente, e a memória não é compartilhada, ocasionando que o cache fique inconsistente entre uma instância e outra. Neste tipo de situação a estratégia de cache local não é suficiente, pois agora quando um dado é armazenado em cache por uma instância, a outra instância deverá ter acesso em sua próxima consulta.  Então se faz necessário a utilização de um provedor de cache distribuído como [Hazelcast](https://hazelcast.com/open-source-projects/) e [Redis](https://redis.io/).

Redis é um banco de dados não relacional de estrutura de chave (key) e valor (value). Sua principal característica é a alta velocidade de acesso devido a sua implementação em memória. E uma das suas principais utilizações é como cache distribuído, já que é possível ter diversas conexões paralelas. Ao optar por utilizar Redis como provedor de cache em uma aplicação é necessário que seja utilizada suas APIs na base de código, gerando uma grande dependência, já que se em algum momento se torne necessário a troca de provedor, haveria um alto custo em manutenção. Problemas como esse são resolvidos adotando abstrações, independente se são construídas por você ou não. Em uma aplicação Spring Boot é possível se apoiar no módulo do Spring Cache.

[Spring Cache](https://spring.io/guides/gs/caching/) é um módulo de extensão do Spring Boot que abstrai a maneira como os dados são inseridos, atualizados e removidos do cache através da sua API no modelo de anotações. Spring Cache oferece suporte ao caching de objetos, então facilita que em diferentes pontos da aplicação compartilhem o mesmo caching.

## Spring Cache com Redis

Para colher os benefícios do uso do Spring Cache com Redis é necessário que uma série de procedimentos de configuração da ferramenta, me refiro desde a inserção de dependências no pom.xml ou qualquer outro gerenciador de dependências que você utilize. Até a definição de beans e properties. Mas não se preocupe! O intuito deste artigo é demostrar como você pode configurar e utilizar o Redis como cache distribuído através das abstrações Spring Cache.

Inicialmente é necessário adicionar a Dependência de um módulo do Spring Data para o Redis, em seguida configuraremos a aplicação para trabalhar com cache e definiremos o Redis como provedor. Após as configurações de conexão e definição do Redis como provedor é necessário que definir as configurações responsáveis por limpar o cache, e qual serializer será utilizado durante a comunicação.

### Configurando a Aplicação 

Iniciaremos adicionando a dependência do [Spring Data Redis](https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/) no projeto, esta dependência ira abstrair o uso do Redis na aplicação, algumas vantagens são client e conector predefinido, serializers personalizados para o tráfego de dados na rede, etc. Abaixo tem um exemplo utilizando Maven.

```XML
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```

O próximo passo é definir as properties de conexão com Redis, e também definir o redis como provedor de cache no arquivo application.yaml.

```YAML
spring:
    redis:
        host: ${HOST:localhost}
        port: ${PORT:6379}
        password: ${PASSWORD:password}
    cache:
        type: redis
        cache-names: user_payment_methods
```

Após a definição do Redis como provedor de cache é necessário que configuramos algumas propriedades como o tempo de vida de objetos no cache, e qual será a forma como os objetos serão serializadas. Por padrão no Spring Data Redis é utilizado o `JdkSerializationRedisSerializer` que utiliza a serialização nativa da JVM, e esta biblioteca nativa é conhecida por permitir a execução de código remoto, que injetam bytecode não verificado que pode causar a execução de código indesejado.  Dado a isso, a recomendação é que outros serializers sejam utilizados, como, por exemplo, o serializer para Json da biblioteca Jackson.


```JAVA
@EnableCaching
@Configuration
public class RedisConfig {

    @Bean
    public RedisCacheConfiguration defaultCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(120))// 2 horas
                .disableCachingNullValues()
                .serializeValuesWith(fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}
```

Iniciamos o código acima, utilizando ao nível de classe a anotação `@EnableCaching` responsável por habilitar o uso do módulo de cache em aplicações Spring Boot. Em seguida é utilizado a anotação `@Configuration` para definir que a classe fornecerá bens para contexto do Spring. E o bean que ela fornecerá é o responsável por definir as configurações padrões de cache. Durante o método `defaultCacheConfiguration()` é definido que os objetos irão permanecer no cache em até 2 horas, e também é definido que valores nulos não serão cacheados, e por fim que os objetos sejam serializados em Json através do `GenericJackson2JsonRedisSerializer`.


## Cacheando Resultados

Ao finalizar as configurações estamos prontos para provisionar o uso do cache na aplicação. Então para melhor entendimento, imagine um Market Place, onde um usuário pode ter diferentes formas de pagamento por estabelecimento. E o sistema deve oferecer uma forma de consultar quais são as formas de pagamento deste usuário. Então na primeira vez em que um usuário consultar as formas, o resultado deverá ser armazenado em cache para usos futuros. Veja um exemplo de implementação no código abaixo:


```JAVA
@Service
public class PaymentMethodsUserService {
    @Autowired
    private UserRepository userRepository;

    @Cacheable(value = "user_payment_methods", key = "#userId")
    public List<PaymentMethod> getPaymentMethodsToUserById(UUID userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not exists"));

        if (user.getPaymentMethods().isEmpty()) {
            return defaultMethods();
        }

        return user.getPaymentMethods();
    }

    private List<PaymentMethod> defaultMethods() {
        return List.of(
                new PaymentMethod("Cash", "Money")
        );
    }
}
```

Para habilitar o cache no método `getPaymentMethodsToUserById(UUID userId)` é necessário que anotação `@Cacheble` seja utilizada ao nível de método e que os argumentos value e key sejam definidos. O argumento value é responsável por identificar qual cache sera consultado. Já o argumento key é responsável por definir como a chave do cache será criada. Por padrão o primeiro argumento é utilizado, porém, caso necessário é possível criar chaves mais elaboradas através de Spring Expression Language (SpEL).  Para saber mais sobre como as keys são geradas consulte a [documentação](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/cache.html#cache-annotations-cacheable-default-key). Dado que um método é anotado com `@Cacheable` o seguinte comportamento é incorporado, primeiro é feito uma consulta se existe a chave no cache, o valor é retornado e o código do corpo do método não é executado. Caso contrario, o código do corpo do método é executado e seu resultado no fim é armazenado em cache para consultas futuras.

### Invalidando o Cache

Nosso projeto já consegue armazenar novas informações no cache, e realizar consultas, porém, também precisamos identificar a situação onde o cache deve ser invalidado, para que as informações sejam atualizadas. Imagine agora que o sistema está evoluindo e começou a aceitar novas formas de pagamento, e agora o usuário poderá adicionar as mesmas em sua coleção. Então, sempre que o usuário adicionar uma nova forma de pagamento, o seu registro de formas no cache deverá ser apagado. Veja um exemplo de implementação no código abaixo. 

```JAVA
@Service
public class PaymentMethodsUserService {
    @Autowired
    private UserRepository userRepository;

    @CacheEvict(value = "user_payment_methods", key = "#userId")
    public void addPaymentMethods(UUID userId, PaymentMethod paymentMethod) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not exists"));

        user.add(paymentMethod);
        userRepository.save(user);
    }
}
```

Se observamos no método `addPaymentMethods()` foi utilizado a anotação `@CacheEvict` que possui os mesmo argumentos que a anotação `@Cachable`. Quando um método é anotado `@CacheEvict`  o mesmo se torna um gatilho para invalidação do cache, e então caso sua execução seja realizada sem interrupção de alguma exceção o cache é sera invalidado. Caso contrario o cache não sofre alterações.

Então agora é possível utilizar este cache em diversos pontos do sistema, basta apenas que as anotações e o contrato dos métodos sejam equivalentes a estes. E a aplicação poderá rodar em diversas instâncias e compartilhar o mesmo cache, mantendo a consistência dos dados.

## Conclusão

A utilização de cache permite que o aumento de desempenho em um sistema, já que dados que tem alta frequência de acesso podem ser recuperados mais rapidamente, já que não é necessário um processamento custoso, ou aguardar a finalização de operações de I/O. A utilização de cache em memória local é uma estratégia interessante quando apenas uma instância do serviço será executada, pois, a partir do momento em que o ambiente se torna clusterizado a memória de cache não é compartilhada deixando os dados inconsistentes. O que justifica a utilização de um provedor de cache distribuído. Redis é um provedor de cache distribuído que facilita o compartilhamento de dados na rede, e oferece suporte para uso em diferentes linguagens.

A utilização de cache em uma aplicação Spring Boot pode ser feita através da Spring Cache que permite que o uso de cache seja abstraído por anotações. Spring Cache oferece suporte ao Redis como provedor, e através de configurações é possível rapidamente utilizá-lo como cache distribuído. A anotação `@Cacheable` permite que um método faça a consulta por objetos no cache e `@CacheEvict` permite que invalidar o cache. Tão importante como inserir, remover e atualizar os dados no cache é definir o tempo de duração para não ser surpreendido com problemas indesejáveis.

## Referências

1. [Spring Data Redis](https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:serializer)
2. [Spring Cache 3.2, aqui fornece mais detalhes que a versão atual](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/cache.html)
3. [Spring Cache atual](https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache-annotations-put)
4. [Customizer TTL Redis Cache](https://www.javacodemonk.com/custom-ttl-for-spring-data-redis-cache-6b38c550)
5. [Spring Cache With Redis: Baeldung](https://www.baeldung.com/spring-boot-redis-cache)
6. [Principais Aprendizados Spring Cache Redis](https://gist.github.com/jordisilvazup/2ac70a22f0644ed2c73ce5aeee2a7feb)