redis-integration
Use when integrating Redis with Quarkus — caching with @CacheResult/@CacheInvalidate, distributed locks, pub/sub messaging, rate limiting, Redisson integration, and both imperative + reactive clients.
Works with
---
name: redis-integration
description: Use when integrating Redis with Quarkus — caching with @CacheResult/@CacheInvalidate, distributed locks, pub/sub messaging, rate limiting, Redisson integration, and both imperative + reactive clients.
license: MIT
---
# Redis Integration with Quarkus
## Overview
Quarkus provides `quarkus-redis-client` for low-level Redis access (both imperative and reactive) and integrates with the `@CacheResult` / `@CacheInvalidate` annotations for declarative caching. Redisson offers advanced features: distributed locks, pub/sub, rate limiters, and cluster/sentinel support.
Reference: [Quarkus Redis Extension Guide](https://jorney.srbala.com/guides/redis-reference), [Redisson Quarkus Integration](https://redisson.pro/blog/advanced-redis-integration-with-quarkus.html), [Quarkus Cache Guide](https://quarkus.io/guides/cache).
## When to Use
- Caching expensive database or API calls.
- Distributed locks for coordinated operations across instances.
- Pub/sub for real-time messaging between services.
- Rate limiting with Redis-backed counters.
- Session storage for stateless services.
Don't use for:
- Local-only single-instance apps (use Caffeine `@CacheResult` instead).
- Persistent storage — Redis is not durable by default.
## Dependencies (Gradle)
```groovy
// build.gradle (infrastructure module)
dependencies {
// Low-level Redis client (imperative + reactive)
implementation 'io.quarkus:quarkus-redis-client'
// Declarative caching
implementation 'io.quarkus:quarkus-cache'
// Redisson (advanced: locks, pub/sub, rate limiters)
implementation 'org.redisson:redisson-quarkus-30:3.43.0'
}
```
## Configuration
```properties
# application.properties
# Redis connection
quarkus.redis.hosts=redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
quarkus.redis.password=${REDIS_PASSWORD:}
quarkus.redis.timeout=5s
quarkus.redis.max-pool-size=10
# Declarative caching
quarkus.cache.caffeine.my-cache.expire-after-write=5m
quarkus.cache.redis.my-redis-cache.expire-after-write=10m
# Redisson
quarkus.redisson.single-server-config.address=redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
quarkus.redisson.single-server-config.password=${REDIS_PASSWORD:}
quarkus.redisson.threads=16
quarkus.redisson.netty-threads=32
```
## Patterns
### 1. Declarative Caching (@CacheResult / @CacheInvalidate)
```java
// application/src/main/java/dev/hieplp/wraith/application/service/UserService.java
@ApplicationScoped
public class UserService implements UserUseCase {
@CacheResult(cacheName = "user-cache")
public UserDTO getUser(String userId) {
// Cache hit → skip this method. Cache miss → execute and store result.
return userRepository.findById(new UserId(userId))
.map(UserMapper.INSTANCE::toDTO)
.orElseThrow(() -> new UserNotFoundException(userId));
}
@CacheInvalidate(cacheName = "user-cache")
public void updateUser(String userId, UpdateUserCommand cmd) {
var user = userRepository.findById(new UserId(userId))
.orElseThrow(() -> new UserNotFoundException(userId));
user.updateProfile(cmd.displayName(), cmd.avatarUrl());
userRepository.save(user);
}
@CacheResult(cacheName = "user-list", lockTimeout = 5000)
public List<UserDTO> listUsers() {
return userRepository.findAll().stream()
.map(UserMapper.INSTANCE::toDTO)
.toList();
}
}
```
### 2. Redis Data Source (type-safe high-level API)
```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/RedisCacheAdapter.java
@ApplicationScoped
public class RedisCacheAdapter implements CachePort {
@Inject
RedisDataSource redisDataSource;
public void set(String key, String value, Duration ttl) {
redisDataSource.string(String.class)
.set(key, value, new SetArgs().ex(ttl));
}
public Optional<String> get(String key) {
var value = redisDataSource.string(String.class).get(key);
return Optional.ofNullable(value);
}
public void delete(String key) {
redisDataSource.key().del(key);
}
// Hash operations
public void setHashField(String hash, String field, String value) {
redisDataSource.hash(String.class).hset(hash, field, value);
}
public Map<String, String> getHash(String hash) {
return redisDataSource.hash(String.class).hgetall(hash);
}
}
```
### 3. Distributed Lock (Redisson)
```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/DistributedLockService.java
@ApplicationScoped
public class DistributedLockService {
@Inject
RedissonClient redisson;
public <T> T withLock(String lockKey, Duration waitTime, Duration leaseTime, Supplier<T> action) {
var lock = redisson.getLock(lockKey);
try {
if (lock.tryLock(waitTime.toSeconds(), leaseTime.toSeconds(), TimeUnit.SECONDS)) {
try {
return action.get();
} finally {
lock.unlock();
}
}
throw new LockAcquisitionException("Could not acquire lock: " + lockKey);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquisitionException("Interrupted while acquiring lock", e);
}
}
// Usage: prevent duplicate order processing
public void processOrder(String orderId) {
withLock("order:" + orderId, Duration.ofSeconds(5), Duration.ofSeconds(30), () -> {
// Critical section — only one instance runs this
return orderProcessor.process(orderId);
});
}
}
```
### 4. Rate Limiter (Redisson)
```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/RateLimiterService.java
@ApplicationScoped
public class RateLimiterService {
@Inject
RedissonClient redisson;
public boolean tryAcquire(String key, long rate, Duration window) {
var limiter = redisson.getRateLimiter("rate:" + key);
limiter.trySetRate(RateType.OVERALL, rate, window);
return limiter.tryAcquire();
}
// Usage in a REST filter:
// if (!rateLimiter.tryAcquire(userId, 100, Duration.ofMinutes(1))) {
// throw new RateLimitExceededException();
// }
}
```
### 5. Pub/Sub (Redisson)
```java
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/EventPublisher.java
@ApplicationScoped
public class EventPublisher {
@Inject
RedissonClient redisson;
public void publish(String topic, String message) {
redisson.getTopic(topic).publish(message);
}
}
// infrastructure/src/main/java/dev/hieplp/wraith/infrastructure/redis/EventSubscriber.java
@ApplicationScoped
public class EventSubscriber {
@Inject
RedissonClient redisson;
public void onStart(@Observes StartupEvent event) {
redisson.getTopic("user-events")
.addListener(String.class, (channel, msg) -> {
log.info("Received event on {}: {}", channel, msg);
// Process event
});
}
}
```
### 6. Low-Level Redis API (imperative + reactive)
```java
// Imperative
@Inject
RedisDataSource highLevelApi; // Type-safe
@Inject
RedisAPI lowLevelClient; // Raw commands
// Reactive (Mutiny)
@Inject
ReactiveRedisDataSource reactiveHighLevel;
@Inject
io.vertx.mutiny.redis.client.RedisAPI reactiveLowLevel;
```
## Hexagonal Architecture: Redis as an Output Adapter
```
domain/ # No Redis dependency
└── model/User.java
application/ # Port interface only
└── port/output/CachePort.java
interface CachePort {
void set(String key, String value, Duration ttl);
Optional<String> get(String key);
void delete(String key);
}
infrastructure/ # Redis adapter implements CachePort
└── redis/RedisCacheAdapter.java
@ApplicationScoped
class RedisCacheAdapter implements CachePort { ... }
└── redis/DistributedLockService.java
@ApplicationScoped
class DistributedLockService { ... }
```
## Common Pitfalls
1. **Cache inconsistency.** When updating data, invalidate the cache BEFORE or IMMEDIATELY AFTER the write. Use `@CacheInvalidate` on update methods, and `@CacheResult` on read methods.
2. **Distributed lock not released.** Always use try-finally to unlock. Redisson's `lock.unlock()` must be called exactly once per `lock()`.
3. **Redis timeout on cold start.** Dev Services needs a few seconds to pull the Redis image. Set `quarkus.redis.timeout=15s` in dev.
4. **Serialization issues with @CacheResult.** The default cache uses Java serialization. For JSON, configure `quarkus.cache.redis.value-type=json`.
5. **Redisson version mismatch.** `redisson-quarkus-30` for Quarkus 3.x, `redisson-quarkus-20` for Quarkus 2.x. Using wrong artifact = classpath conflicts.
6. **Rate limit key explosion.** Use TTL on rate limiter keys to prevent Redis memory bloat: `limiter.trySetRate(..., Duration.ofMinutes(1))` with `limiter.expire(Duration.ofHours(1))`.
## Verification Checklist
- [ ] Redis Dev Services starts in dev mode (`docker ps` shows redis container).
- [ ] `@CacheResult` methods return cached values on second call.
- [ ] `@CacheInvalidate` clears cache and next read hits the database.
- [ ] Distributed lock test: two concurrent threads, only one enters critical section.
- [ ] Rate limiter blocks requests above threshold.
- [ ] Pub/sub: publisher's message received by subscriber.
- [ ] Hexagonal: `CachePort` interface in `application/`, Redis adapter in `infrastructure/`.More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

