Skip to content

Dependency Flow

How objects are constructed and how control flows through dichit-backend. This is the concrete mechanism behind clean-architecture.md.

Composition root

Everything is wired at the edges. The app is assembled in src/interfaces/app.ts:

sequenceDiagram
    participant Server as server.ts
    participant App as app.ts
    participant DI as Awilix container
    participant Plugins as infrastructure/plugins
    participant Routes as v1/v2/v3 + webhooks + ws

    Server->>App: makeApp(app, options)
    App->>DI: register(fastifyAwilixPlugin)
    App->>DI: makeInfrastructureDependencies() → diContainer.register(...)
    App->>Plugins: AutoLoad(PLUGINS_PATH)
    App->>App: registerQueueProcessors()
    App->>Routes: register swagger + apiRoutes per version
    App->>Routes: register websocketInterface
    App->>Server: ready + listen

src/infrastructure/di.ts is the single place that constructs concrete implementations. Application code never imports them directly — it depends on interfaces from src/application/interfaces/**.

Constructor injection

Use cases and services receive their collaborators through their constructors. Awilix resolves them from the container ("composition root" pattern), so tests can inject mocks/fakes easily.

// application/use-cases/users/create-user.ts (illustrative)
export class CreateUserUseCase {
  constructor(private readonly userRepository: UserRepository) {}
  // ...
}
flowchart LR
    subgraph Registry["Awilix cradle (interfaces/app.ts)"]
        Config["config"]
        Db["postgres/prisma"]
        Cache["redis"]
        Repos["repositories"]
        Svc["services (aws, mail, sms, fcm)"]
        Q["queue processors"]
    end
    UC["Use cases"] --> Registry
    H["Controllers / handlers"] --> Registry

Direction rules

  • interfaces → application → application/interfaces ← infrastructure
  • Domain entities are shared data shapes; both application and infrastructure map to/from them.
  • infrastructure never imports interfaces.
  • application never imports infrastructure.
  • shared may be imported by any layer (it imports nothing heavy).

Overriding for tests

makeApp accepts dependencyOverrides, letting functional tests swap infrastructure resolvers:

makeApp(app, {
  dependencyOverrides: {
    cacheService: asValue(fakeCache)
  }
});