DEV Community

Jordi Henrique Silva
Jordi Henrique Silva

Posted on

44

Turbinando seus Microsserviços com Spring Cache e Redis

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 e Redis.

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 é 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 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.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

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.

spring:
    redis:
        host: ${HOST:localhost}
        port: ${PORT:6379}
        password: ${PASSWORD:password}
    cache:
        type: redis
        cache-names: user_payment_methods
Enter fullscreen mode Exit fullscreen mode

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.

@EnableCaching
@Configuration
public class RedisConfig {

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

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:

@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")
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

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. 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.

@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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
  2. Spring Cache 3.2, aqui fornece mais detalhes que a versão atual
  3. Spring Cache atual
  4. Customizer TTL Redis Cache
  5. Spring Cache With Redis: Baeldung
  6. Principais Aprendizados Spring Cache Redis

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (4)

Collapse
 
marcuspaulo profile image
Marcus Paulo

Parabéns pelo post Jordi.

Collapse
 
thalytadev profile image
Thalyta-dev

Incrível! Sempre direto ao ponto e focado na prática. Parabéns!

Collapse
 
jacieldias profile image
Jaciel

Parabéns pelo artigo Jordi, simples e objetivo.

Collapse
 
eguadorodrigo profile image
Rodrigo

Muito bom Jordi, parabéns!

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay