Skip to content

Task 2-3: Dynamic Queue Update at Runtime

Phase: 2 - Stability Priority: Medium Module: batch Depends on: Không có Reference: docs/BountyHunter-Backend/details/feature-batch-async-processing/SPEC.md

Background

ProgrammaticEndpointRegistration chạy một lần tại @PostConstruct. Nếu admin tạo machine/setting mới trong runtime, cần restart batch service để có listener mới - gây downtime không cần thiết.

Tasks

1. Expose method registerForNewMachine / registerForNewSetting

File: batch/jms/register_destination/ProgrammaticEndpointRegistration.java

public void registerForNewMachine(String machineId) {
    LOGGER.info("[DYNAMIC_QUEUE] Registering listener for machineId={}", machineId);
    registerMachineListener(machineId);
    LOGGER.info("[DYNAMIC_QUEUE] Listener registered for machineId={}", machineId);
}

public void registerForNewGameBoothSetting(String settingId) {
    LOGGER.info("[DYNAMIC_QUEUE] Registering listener for settingId={}", settingId);
    registerSettingListeners(settingId);
    LOGGER.info("[DYNAMIC_QUEUE] Listener registered for settingId={}", settingId);
}

2. Trigger khi admin tạo machine/setting mới

Option A: JMS event từ admin module

DI Note (Option A): JMSAdminEventListener must inject ProgrammaticEndpointRegistration via @RequiredArgsConstructor:

private final ProgrammaticEndpointRegistration programmaticEndpointRegistration;
The admin module must inject a JmsTemplate (or equivalent producer bean) to publish the event. Required imports for the listener: - org.springframework.jms.annotation.JmsListener - com.figpop.batch.jms.register_destination.ProgrammaticEndpointRegistration

// Admin module publishes event
jmsProducer.send("queue-admin-new-machine", machineId);

// Batch module listener:
@JmsListener(destination = "queue-admin-new-machine")
public void onNewMachineCreated(String machineId) {
    programmaticEndpointRegistration.registerForNewMachine(machineId);
}

Option B: Scheduled polling

DI Note (Option B): The class hosting checkForNewMachines() must inject both ProgrammaticEndpointRegistration and MachineRepository (or equivalent repository bean):

private final ProgrammaticEndpointRegistration programmaticEndpointRegistration;
private final MachineRepository machineRepository;
registeredMachineIds should be a thread-safe field, e.g. private final Set<String> registeredMachineIds = ConcurrentHashMap.newKeySet(); Required imports: - com.google.common.collect.Sets (Guava) for Sets.difference - org.springframework.scheduling.annotation.Scheduled

@Scheduled(fixedDelay = 60000)
public void checkForNewMachines() {
    Set<String> knownMachines = registeredMachineIds;
    Set<String> allMachines = machineRepository.findAllMachineIds();

    Sets.difference(allMachines, knownMachines).forEach(newMachineId -> {
        programmaticEndpointRegistration.registerForNewMachine(newMachineId);
        registeredMachineIds.add(newMachineId);
    });
}

Verification / Acceptance Criteria

  • [ ] registerForNewMachine(machineId) and registerForNewGameBoothSetting(settingId) are public methods on ProgrammaticEndpointRegistration and can be called without restarting the service
  • [ ] Calling registerForNewMachine logs [DYNAMIC_QUEUE] Listener registered for machineId=<id> and the new JMS listener container is visible in JmsListenerEndpointRegistry
  • [ ] Option A: A message published to queue-admin-new-machine triggers listener registration within one message-processing cycle (no restart required)
  • [ ] Option B: Within 60 s of a new machine row being inserted in the DB, the listener is registered automatically
  • [ ] Registering the same machineId twice does not create duplicate listener containers (idempotency)

Files to Modify

  • batch/jms/register_destination/ProgrammaticEndpointRegistration.java
  • (New) batch/jms/admin/JMSAdminEventListener.java (if Option A)

Notes

  • Option A (event-driven) phù hợp hơn cho real-time; Option B đơn giản hơn
  • Cần track registeredMachineIds trong memory để diff