69. Service Separation for Schedulers
Status: Accepted Date: 2025-07-06
Context
Within our centralized Schedulers module, we will be managing the lifecycle of multiple different entities. For example, an Order has a very different lifecycle and set of scheduled events (e.g., check for fill, timeout) than a Position (e.g., check P&L, update trailing stop) or a Tournament (e.g., start, end, calculate results). Combining all of this disparate logic into a single, monolithic SchedulerService would violate the Single Responsibility Principle and create a large, unwieldy class that is difficult to maintain.
Decision
We will apply the Single Responsibility Principle within the Schedulers module by creating separate, dedicated scheduler services for each major entity type.
For example, we will have:
OrderSchedulerService: Responsible exclusively for scheduling all lifecycle events related to theOrderentity.PositionSchedulerService: Responsible exclusively for scheduling events related to thePositionentity.TournamentSchedulerService: Responsible forTournamentlifecycle events.
Each service will have a narrow, well-defined focus, making the codebase much cleaner and easier to manage.
Consequences
Positive:
- High Cohesion & Single Responsibility: Each service has a single, clear purpose, making it easy to understand, test, and maintain. The
OrderSchedulerServiceknows nothing aboutPositions, and vice versa. - Improved Maintainability: When a change is needed for the order lifecycle, we know exactly which file to edit. This reduces the risk of accidentally breaking unrelated functionality.
- Easier Testing: Unit testing is significantly simpler, as each service can be tested in isolation with a clear and limited scope.
- Clear Code Organization: This decision leads to a clean and predictable file structure within the Schedulers module.
Negative:
- More Boilerplate Code: This approach results in more files and classes compared to a single monolithic service. There might be some small amount of duplicated boilerplate for things like injecting common dependencies.
- Potential for Cross-Service Logic: There might be rare cases where the lifecycle of one entity depends on another, which could require one scheduler service to call another, slightly increasing coupling.
Mitigation:
- Shared Base Class or Utilities: If significant boilerplate is identified, we can create a shared
BaseSchedulerServiceor utility functions to abstract away the common code, while keeping the specific logic separate. - Event-Driven Communication: For the rare cases where schedulers might need to interact, we can use an internal event bus rather than direct service-to-service calls to keep them decoupled. However, the primary design goal will be to make each scheduler self-contained.
- Benefits Outweigh Costs: The small cost of extra files is vastly outweighed by the long-term benefits of a clean, maintainable, and testable architecture.