Skip to content

Task 1-2: Listener Idempotency Check via Redis

Phase: 1 - Idempotency Priority: High Module: batch Depends on: task-1-1 Reference: docs/BountyHunter-Backend/details/feature-prize-allocation/SPEC.md

Background

Sau khi task-1-1 thêm correlationId vào PrizeAllocationData, task này implement idempotency check trong tất cả 9 listeners thông qua shared IdempotencyService.

Tasks

1. Tạo shared IdempotencyService

DI Note: IdempotencyService@Service inject RedisTemplate<String, String> (hoặc StringRedisTemplate) qua constructor (@RequiredArgsConstructor + final field). Đảm bảo RedisTemplate<String, String> bean được khai báo trong @Configuration class của batch module (hoặc dùng StringRedisTemplate autowire trực tiếp). Mỗi listener inject IdempotencyService qua constructor.

File mới: batch/service/IdempotencyService.java

@Service
@RequiredArgsConstructor
@Slf4j
public class IdempotencyService {

    private final StringRedisTemplate redisTemplate;  // hoặc RedisTemplate<String, String>
    private static final String KEY_PREFIX = "idempotency:prize-alloc:";
    private static final long TTL_SECONDS = 3600;

    /**
     * Atomically marks correlationId as processed.
     * @return true nếu chưa được xử lý (isNew) — OK to proceed
     *         false nếu duplicate — skip processing
     */
    public boolean markAsProcessed(String correlationId) {
        String key = KEY_PREFIX + correlationId;
        Boolean isNew = redisTemplate.opsForValue()
            .setIfAbsent(key, "1", TTL_SECONDS, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(isNew);
    }
}

2. Apply cho tất cả 9 listeners

Template để apply vào mỗi listener:

// Inject: private final IdempotencyService idempotencyService;

@JmsListener(destination = "${queue.prize-allocation.prize}", ...)
public void onMessage(String messageJson) {
    PrizeAllocationData data = convertMessage(messageJson, PrizeAllocationData.class);

    if (!idempotencyService.markAsProcessed(data.getCorrelationId())) {
        log.warn("[IDEMPOTENCY] Skipping duplicate correlationId={}", data.getCorrelationId());
        return;  // ACK, không process
    }

    // existing logic ...
}

  • [ ] JMSPrizeAllocationListener
  • [ ] JMSPrizeForStreamerAllocationListener
  • [ ] JMSUnlimitedPrizeAllocationListener
  • [ ] JMSMissionAllocationListener
  • [ ] JMSGachaAllocationListener
  • [ ] JMSPresentBoxAllocationListener
  • [ ] JMSMKPAllocationListener
  • [ ] JMSPresaleAllocationListener
  • [ ] JMSNoneAllocationListener

Verification / Acceptance Criteria

  • [ ] IdempotencyService.java tồn tại, compile thành công
  • [ ] Tất cả 9 listeners inject và gọi idempotencyService.markAsProcessed() trước khi process
  • [ ] Duplicate message (cùng correlationId) → skip, log WARN
  • [ ] StringRedisTemplate bean available trong batch module (không lỗi autowire)
  • [ ] Redis key idempotency:prize-alloc:{correlationId} có TTL = 3600s (verify qua redis-cli TTL)

Files to Create / Modify

  • batch/service/IdempotencyService.java (new)
  • 9 listener files trong batch/jms/prize_allocation/