Task 2-3: Dynamic Queue Update at Runtime
Phase: 2 - Stability Priority: Medium Module:
batchDepends 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):
JMSAdminEventListenermust injectProgrammaticEndpointRegistrationvia@RequiredArgsConstructor:The admin module must inject aprivate final ProgrammaticEndpointRegistration programmaticEndpointRegistration;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 bothProgrammaticEndpointRegistrationandMachineRepository(or equivalent repository bean):private final ProgrammaticEndpointRegistration programmaticEndpointRegistration; private final MachineRepository machineRepository;registeredMachineIdsshould be a thread-safe field, e.g.private final Set<String> registeredMachineIds = ConcurrentHashMap.newKeySet();Required imports: -com.google.common.collect.Sets(Guava) forSets.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)andregisterForNewGameBoothSetting(settingId)are public methods onProgrammaticEndpointRegistrationand can be called without restarting the service - [ ] Calling
registerForNewMachinelogs[DYNAMIC_QUEUE] Listener registered for machineId=<id>and the new JMS listener container is visible inJmsListenerEndpointRegistry - [ ] Option A: A message published to
queue-admin-new-machinetriggers 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
machineIdtwice 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
registeredMachineIdstrong memory để diff