Implementação de Cache Local de Alta Performance com Caffeine no Spring Boot

Arquitetura de Cache Local com Caffeine

O Caffeine consoildou-se como a principal biblioteca de cache em memória para aplicações Java modernas. Originalmente, o Guava Cache era o padrão da indústria, utilizando algoritmos como LRU e mecanismos de coleta de lixo baseados em referências. No entanto, o Caffeine reescreveu essa arquitetura aproveitando os recursos do Java 8, oferecendo uma taxa de acerto (hit rate) muito superior e um desempenho otimizado. Desde a versão 2.0 do Spring Boot, ele é automaticamente configurado como o gerenciador de cache padrão quando detectado no classpath.

Por que optar por Cache Local?

A principal vantagem do cache em memória local reside na latência. Enquanto soluções distribuídas como o Redis são excelentes para compartilhamento de estado entre múltiplos nós, elas ainda incorrem em overhead de rede, serialização e I/O. Para dados de leitura frequente, alta volatilidade e que não exigem consistência estrita enntre diferentes instâncias da aplicação, o cache local elimina o gargalo de rede, entregando respostas na casa dos nanossegundos.

Configuração de Dependências

Para integrar o Caffeine ao ecossistema Spring, adicione as seguintes dependências ao seu arquivo pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
</dependencies>

Definição de Parâmetros via YAML

A maneira mais rápida de configurar o cache é através do arquivo de propriedades do Spring, definindo as especificações diretamente via string de configuração do Caffeine:

spring:
  cache:
    type: caffeine
    cache-names: productCatalog, userSessions
    caffeine:
      spec: maximumSize=2000,expireAfterAccess=15m,recordStats

Configuração Programática via Java Config

Para um controle mais granular, como a adição de listeners de remoção ou configurações específicas por bean, a abordagem programática é a mais recomendada:

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class InventoryCacheSetup {

    @Bean("inventoryCache")
    public Cache<String, InventoryItem> buildInventoryCache() {
        return Caffeine.newBuilder()
                .initialCapacity(200)
                .maximumSize(5000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .removalListener((key, value, cause) -> 
                    System.out.println("Entrada removida - Chave: " + key + " | Motivo: " + cause))
                .build();
    }
}

Manipulação Manual do Cache

Quando a lógica de negócio exige controle explícito sobre a invalidação ou atualização dos dados, a API nativa do Caffeine pode ser injetada e utilizada diretamente:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.github.benmanes.caffeine.cache.Cache;

@Service
public class InventoryManager {

    private final Cache<String, InventoryItem> stockCache;

    public InventoryManager(@Qualifier("inventoryCache") Cache<String, InventoryItem> stockCache) {
        this.stockCache = stockCache;
    }

    public void updateStock(String sku, InventoryItem item) {
        stockCache.put(sku, item);
    }

    public InventoryItem fetchStock(String sku) {
        // Retorna o valor se presente, ou null caso não exista no cache
        return stockCache.getIfPresent(sku);
    }
    
    public void clearStock(String sku) {
        stockCache.invalidate(sku);
    }
}

Abordagem Declarativa com Spring Cache

Para operações CRUD padrão, a abstração de cache do Spring permite desacoplar a lógica de cacheamento do código de negócio através de anotações:

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@CacheConfig(cacheNames = "articleStore")
public class ArticleManager {

    // Simulação de repositório de dados
    private final Map<Long, Article> mockDatabase = new ConcurrentHashMap<>();

    @CachePut(key = "#article.id")
    public Article publishArticle(Article article) {
        mockDatabase.put(article.getId(), article);
        return article;
    }

    @Cacheable(key = "#articleId")
    public Article findArticle(Long articleId) {
        simulateDatabaseLatency();
        return mockDatabase.get(articleId);
    }

    @CacheEvict(key = "#articleId")
    public void retractArticle(Long articleId) {
        mockDatabase.remove(articleId);
    }
    
    private void simulateDatabaseLatency() {
        try { 
            Thread.sleep(300); 
        } catch (InterruptedException e) { 
            Thread.currentThread().interrupt(); 
        }
    }
}

Tags: caffeine spring-boot java cache-local spring-cache

Publicado em 6-15 20:31 por Thomas