Skip to main content

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 the Order entity.
  • PositionSchedulerService: Responsible exclusively for scheduling events related to the Position entity.
  • TournamentSchedulerService: Responsible for Tournament lifecycle 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 OrderSchedulerService knows nothing about Positions, 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 BaseSchedulerService or 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.