Skip to content

11. Service Dependency Injection

Date: 2026-01-07

Status

Accepted

Context

Within the services architecture, services were creating instances of other services internally, leading to:

  • Tight coupling between services
  • Difficulty in testing (cannot mock dependencies)
  • Inconsistent RLS context (each service creates its own prismaRLS)
  • Hidden dependencies not visible from service interfaces

This ADR establishes the dependency injection pattern specifically for the services layer.

Examples of problematic patterns within services:

  • UserFeedbackService creating UsersService in constructor
  • OrderService.getPaginatedOrders creating ClientService inline
  • ChangeOrderStatusService.changeOrderStatus creating OrderAvailabilityService inline

Decision

Adopt constructor injection for service-to-service dependencies within the services architecture:

  1. Never instantiate services directly within service classes - all service instantiation must occur in:

    • services/index.ts (factory function)
    • Test files (*.test.ts)
    • Mock files (__mocks__/)
  2. Inject service dependencies via constructor using a deps parameter:

    export interface MyServiceDeps {
    dependentService: DependentService;
    }
    constructor(
    tenantId: string,
    role: string,
    decodedToken: RLSJwt,
    deps: MyServiceDeps,
    ) {
    this.dependentService = deps.dependentService;
    }
  3. Wire dependencies in factory (createTenantServices):

    • Create independent services first
    • Pass them to dependent services
  4. Enforce with ESLint rule local-rules/no-direct-service-instantiation at error level within service files

Consequences

Benefits

  • Testability: Services can be tested with mocked dependencies
  • Explicit dependencies: Constructor signature shows all service-to-service dependencies
  • Single RLS context: All services share tenant context from factory
  • CI enforcement: ESLint rule prevents violations within the services architecture

Risks & Mitigations

  • Migration effort: Existing service-to-service instantiation violations must be fixed
    • Mitigation: Three known violations identified and migrated as part of this change
  • Increased factory complexity: Factory must manage the service dependency graph
    • Mitigation: Clear ordering in factory; TypeScript provides type safety

Scope

This ADR applies specifically to the services architecture layer. Service instantiation from outside the services layer (e.g., in resolvers, controllers, or other application layers) is not covered by this ADR and may follow different patterns.