Task 1-1: Externalize Redis TTL từ Hardcoded 11 Seconds
Phase: 1 Priority: Medium Module:
RedisMachineRepositoryDepends on: Không có Reference: docs/BountyHunter-ControlServer/details/feature-machine-management/SPEC.md
Background
RedisMachineRepository.save() gọi jedis.expire(key, 11) — TTL 11 giây được hardcode trực tiếp trong source code. Nếu heartbeat interval của machine được điều chỉnh (ví dụ từ 5s lên 15s do network optimization), Redis key sẽ expire trước khi heartbeat tiếp theo tới, khiến machine bị mark OFFLINE sai trên dashboard. Ngược lại, nếu muốn giảm TTL để phát hiện disconnection nhanh hơn, developer phải sửa code và redeploy thay vì chỉ thay đổi config. Magic number 11 không có documentation về lý do chọn giá trị này.
Tasks
Note:
RedisMachineRepositorylà Spring Bean — dùng constructor injection để nhậnredisTtlSecondstừ@Valueannotation. Không dùng field injection (@Autowiredtrên field) để tránh khó test.RedisConfighoặcRedisPropertiesclass nên encapsulate tất cả Redis-related config. Validate TTL > 0 trong constructor để fail fast.
- [ ] Thêm property vào
config.properties:
# Redis TTL for machine status keys.
# Rule: TTL >= 2.5 * heartbeat_interval_seconds to allow for network jitter.
# Default heartbeat: 5s → TTL should be >= 12s. Set 30s for safe margin.
redis.machine.ttl-seconds=30
- [ ] Update
RedisConfig(hoặc tạoRedisProperties) để load property:
public class RedisConfig {
private final int machineTtlSeconds;
public static RedisConfig fromProperties(Properties props) {
int ttl = Integer.parseInt(props.getProperty("redis.machine.ttl-seconds", "30"));
if (ttl <= 0) {
throw new IllegalArgumentException("redis.machine.ttl-seconds must be positive, got: " + ttl);
}
return new RedisConfig(/* other fields */, ttl);
}
public int getMachineTtlSeconds() { return machineTtlSeconds; }
}
- [ ] Update
RedisMachineRepositoryconstructor vàsave()method:
public class RedisMachineRepository {
private final JedisPool jedisPool;
private final int ttlSeconds; // từ config
public RedisMachineRepository(JedisPool jedisPool, int ttlSeconds) {
this.jedisPool = jedisPool;
this.ttlSeconds = ttlSeconds;
}
public void save(int status, String macIp) {
String key = "RCraneMachineStatus:" + macIp;
try (Jedis jedis = jedisPool.getResource()) {
jedis.hset(key, "status", String.valueOf(status));
jedis.expire(key, ttlSeconds); // ← dùng config value thay vì 11
}
}
}
- [ ] Document trong code comment: TTL recommendation formula:
// TTL should be at least 2.5× the machine heartbeat interval.
// Example: heartbeat every 5s → TTL = 13s minimum, recommended 30s for safety.
// Configure via redis.machine.ttl-seconds in config.properties.
jedis.expire(key, ttlSeconds);
- [ ] Tìm tất cả hardcoded
11trongRedisMachineRepositoryvà thay thế bằngttlSeconds
Verification / Acceptance Criteria
- [ ]
jedis.ttl("RCraneMachineStatus:<mac>")trả đúng giá trị từredis.machine.ttl-secondsconfig (không phải 11) - [ ] Thay đổi
redis.machine.ttl-seconds=60trong config → sau khi restart, TTL là 60 giây — không cần code change - [ ] Giá trị
redis.machine.ttl-seconds=0hoặc âm → application fail fast vớiIllegalArgumentExceptionkhi startup - [ ] Unit test:
RedisMachineRepository(mockJedisPool, 30).save(0, "AA:BB")→ verifyjedis.expire("RCraneMachineStatus:AA:BB", 30)được gọi - [ ] Không còn magic number
11trong source code củaRedisMachineRepository
Files to Modify
src/main/java/.../RedisMachineRepository.javasrc/main/java/.../RedisConfig.java(hoặcRedisProperties.java)src/main/resources/config.properties