66. Centralized Scheduling Module
Status: Accepted Date: 2025-07-06
Context
In a complex application like a trading bot, many actions need to happen at a specific time in the future or on a recurring basis. Examples include checking an order's status after 5 seconds, cancelling it after 60 seconds, or fetching new market data every minute. A naive approach is to scatter setTimeout, setInterval, or cron logic throughout the codebase wherever a scheduled task is needed. This leads to a chaotic system that is impossible to monitor, debug, or reason about. It's difficult to know what tasks are scheduled, when they will run, or if they have failed.
Decision
We will create a centralized, dedicated Schedulers module. All time-based and recurring job logic must be managed through this module. Other services are forbidden from implementing their own setTimeout or cron-like logic.
When another service needs to schedule a future action, it will make a call to a service within the Schedulers module (e.g., orderSchedulerService.scheduleFillCheck(orderId)). The Schedulers module then becomes responsible for ensuring that action is triggered at the correct time.
Consequences
Positive:
- Centralized & Observable: All scheduled tasks are managed in one place, making the system's time-based behavior easy to understand, monitor, and debug. We have a single source of truth for "what is supposed to run when."
- Decoupling: Services that need to schedule tasks are decoupled from the mechanism of scheduling. They simply state their intent ("run this job at this time") and the Schedulers module handles the "how."
- Reliability: A centralized module can be built with robust error handling and persistence (
adr://bullmq-scheduler-queues), making the scheduling mechanism far more reliable than scattered, in-memorysetTimeoutcalls that are lost on application restart. - Testability: It's easier to test the scheduling logic in isolation and to test the services that depend on it by providing a mock scheduler.
Negative:
- Single Point of Failure: If the Schedulers module fails, no new time-based tasks will be initiated across the entire application.
- Potential Bottleneck: A high volume of scheduling requests could potentially bottleneck the Schedulers module.
- More Boilerplate for Simple Tasks: For a very simple, non-critical scheduled task, calling a central service is slightly more boilerplate than a one-line
setTimeout.
Mitigation:
- High Availability: The Schedulers module will be stateless (its state is in BullMQ's Redis backend) and can be run with multiple redundant instances to ensure high availability.
- Efficient Implementation: The scheduling mechanism itself (e.g., adding a delayed job to BullMQ) is a very lightweight and efficient operation, making it unlikely to become a bottleneck.
- Clarity over Brevity: The small amount of extra boilerplate is a worthwhile trade-off for the immense gain in clarity, reliability, and observability. The principle is to make complex systems manageable, not to make simple things infinitesimally shorter.