Skip to content

Dependency Injection

dichit-backend uses Awilix with the @fastify/awilix Fastify plugin for all dependency wiring. This is how Clean Architecture stays clean at runtime (see architecture/dependency-flow.md).

How it works

  1. makeApp registers fastifyAwilixPlugin (composition root).
  2. makeInfrastructureDependencies() in src/infrastructure/di.ts builds every concrete implementation and registers them with diContainer.register(...).
  3. app.diContainer.cradle exposes the resolved graph to route handlers, services, and WebSocket handlers.
  4. Tests can override resolvers via makeApp(app, { dependencyOverrides }).

Patterns

Constructor injection (preferred for classes)

export class ListUsersUseCase {
  constructor(private readonly userRepository: UserRepository) {}
}

Cradle access (Fastify handlers)

app.get('/users', async (request, reply) => {
  const listUsers = request.diScope.resolve('listUsersUseCase');
  // or
  const listUsers = app.diContainer.cradle.listUsersUseCase;
});

Contracts & implementations

flowchart LR
    UC["Use case"] -->|"depends on"| I["interface (application/interfaces)"]
    I -->|"implemented by"| IM["impl (infrastructure)"]
    IM -->|"registered in"| DI["di.ts"]
    DI -->|"resolved into"| C["Awilix cradle"]
    C --> H["controllers / handlers / WS"]
  • Contracts live in src/application/interfaces/**.
  • Implementations live in src/infrastructure/**.
  • di.ts is the only place that maps interface → implementation.

Adding a dependency

  1. Define (or reuse) the contract in src/application/interfaces/....
  2. Implement it in src/infrastructure/....
  3. Register it in src/infrastructure/di.ts.
  4. Inject it via constructor/cradle in the consumer.
  5. Add a unit test with a mock of the contract.

Testing

  • Unit tests inject fakes/mocks of the contract — no real infra.
  • Functional tests use dependencyOverrides to swap cache/SMS/etc. (see testing.md).