Projeto de exemplo demonstrando a implementação de cache distribuído com Redis em aplicações .NET. Utiliza uma API de pagamentos como caso de uso prático para ilustrar padrões de cache, persistência em memória e otimização de performance.
- Integração do Redis com ASP.NET Core 8.0
- Implementação do padrão Cache-Aside
- Abstração de serviço de cache para facilitar manutenção
- Serialização e deserialização de objetos complexos
- Configuração de TTL (Time To Live) para expiração de dados
- Uso de chaves estruturadas para organização de dados
- Deploy containerizado com Docker Compose
O projeto implementa o padrão Cache-Aside onde:
- Leitura: Primeiro tenta buscar do cache, se não encontrar, retorna null
- Escrita: Sempre salva no cache com tempo de expiração configurável
- Atualização: Sobrescreve o valor existente no cache
As chaves seguem o padrão {prefixo}:{identificador}
:
payment:3fa85f64-5717-4562-b3fc-2c963f66afa6
Política de Memória: allkeys-lru
- Remove as chaves menos recentemente usadas quando atinge o limite de memória
Limite de Memória: 128MB
Persistência: Desabilitada para máxima performance (dados apenas em memória)
Tempo de Expiração: 1 hora (configurável por operação)
O projeto utiliza uma interface ICacheService
que abstrai as operações do Redis:
public interface ICacheService
{
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default);
Task<bool> RemoveAsync(string key, CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default);
Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, CancellationToken cancellationToken = default);
}
Essa abstração permite trocar a implementação de cache sem impactar o restante da aplicação.
- Redis 7 Alpine - Banco de dados em memória
- StackExchange.Redis - Cliente Redis para .NET
- ASP.NET Core 8.0 - Framework para API de exemplo
- System.Text.Json - Serialização de objetos
- Docker Compose - Orquestração de containers
- Clean Architecture - Separação de responsabilidades
- Docker e Docker Compose instalados
docker-compose up -d
Isso irá iniciar dois containers:
- Redis na porta
6379
- API de exemplo na porta
5192
Variável | Descrição | Valor |
---|---|---|
Redis__ConnectionString |
String de conexão do Redis | redis:6379 |
O Redis estará disponível em localhost:6379
. Você pode conectar usando:
# Via Redis CLI
docker exec -it payment-redis redis-cli
# Comandos úteis
redis-cli ping
redis-cli keys "payment:*"
redis-cli get "payment:{guid}"
redis-cli ttl "payment:{guid}"
Acesse http://localhost:5192/swagger
para explorar os endpoints de exemplo e ver o Redis em ação.
Exemplo de fluxo:
POST /api/payment
- Cria um pagamento e salva no RedisGET /api/payment/{id}
- Busca do Redis (cache hit)- Aguarde 1 hora - Dados expiram automaticamente
GET /api/payment/{id}
- Retorna 404 (cache miss, dados expiraram)
// Infrastructure/Extensions/CacheDependencyInjection.cs
services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var configuration = ConfigurationOptions.Parse(connectionString);
configuration.AbortOnConnectFail = false;
return ConnectionMultiplexer.Connect(configuration);
});
services.AddSingleton<ICacheService, RedisCacheService>();
// Infrastructure/Services/RedisCacheService.cs
public class RedisCacheService : ICacheService
{
private readonly IDatabase _database;
public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default)
{
var value = await _database.StringGetAsync(key);
if (!value.HasValue) return default;
return JsonUtil.Deserialize<T>(value!);
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default)
{
var json = JsonUtil.Serialize(value);
await _database.StringSetAsync(key, json, expiration);
}
}
// Infrastructure/Repositories/PaymentRepository.cs
public class PaymentRepository : IPaymentRepository
{
private const string KeyPrefix = "payment";
private static readonly TimeSpan DefaultExpiration = TimeSpan.FromHours(1);
public async Task<Guid> CreateAsync(Payment payment, CancellationToken cancellationToken = default)
{
var key = $"{KeyPrefix}:{payment.Identifier}";
await _cacheService.SetAsync(key, payment, DefaultExpiration, cancellationToken);
return payment.Identifier;
}
public async Task<Payment?> GetByIdentifierAsync(Guid identifier, CancellationToken cancellationToken = default)
{
var key = $"{KeyPrefix}:{identifier}";
return await _cacheService.GetAsync<Payment>(key, cancellationToken);
}
}
redis:
image: redis:7-alpine
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru --save "" --appendonly no
deploy:
resources:
limits:
cpus: '0.25'
memory: 128M
Parâmetros importantes:
--maxmemory 128mb
- Limita uso de memória--maxmemory-policy allkeys-lru
- Remove chaves antigas quando memória está cheia--save ""
- Desabilita persistência em disco--appendonly no
- Desabilita log de operações
# Listar todas as chaves de pagamento
redis-cli keys "payment:*"
# Ver quantidade de chaves
redis-cli dbsize
# Verificar tempo de vida restante de uma chave (em segundos)
redis-cli ttl "payment:3fa85f64-5717-4562-b3fc-2c963f66afa6"
# Informações gerais do servidor
redis-cli info
# Uso de memória
redis-cli info memory
# Estatísticas de operações
redis-cli info stats
# Remover uma chave específica
redis-cli del "payment:3fa85f64-5717-4562-b3fc-2c963f66afa6"
# Remover todas as chaves de pagamento
redis-cli keys "payment:*" | xargs redis-cli del
# Limpar todo o banco de dados (cuidado!)
redis-cli flushdb
O projeto segue Clean Architecture com separação clara de responsabilidades:
Solution/
├── Domain/ # Regras de negócio e contratos
│ ├── Entities/ # Entidades do domínio
│ ├── Repositories/ # Interface do repositório
│ └── Services/ # Interface ICacheService
├── Application/ # Casos de uso
│ ├── UseCases/ # Lógica de aplicação
│ └── DTOs/ # Objetos de transferência
├── Infrastructure/ # Implementações técnicas
│ ├── Services/ # RedisCacheService (implementação)
│ ├── Repositories/ # PaymentRepository (usa Redis)
│ └── Extensions/ # Configuração de DI
├── WebApi/ # API REST (exemplo)
│ └── Controllers/ # Endpoints HTTP
└── Shared/ # Utilitários compartilhados
└── Utils/ # JsonUtil para serialização
Benefícios desta arquitetura:
- Fácil substituição do Redis por outro cache (Memcached, etc.)
- Testabilidade através de interfaces
- Separação entre regras de negócio e infraestrutura