Skip to content

004 - Module Self-Containment Architecture

Status

Accepted

Date

2026-02-27

Context

The original architecture had AppModule owning TypeORM data sources for all feature modules, global guards registered as raw providers in AppModule, and cross-cutting services (HashingService, EncryptionService) not properly wired as a @Global() module. This created tight coupling: every new feature module required changes to AppModule, and the DI container failed at runtime because useExisting aliases were registered without their concrete classes.

Additionally, core/ contained guards and decorators but was not a NestJS module, so its providers had to be manually registered in AppModule.

Decision

  1. SharedModule (@Global()): Owns cross-cutting infrastructure services (hashing, encryption). Registers concrete classes before symbol aliases so useExisting resolves correctly.

  2. CoreModule (@Global()): Owns APP_GUARD providers (JwtAuthGuard, AuthTypeGuard). Decorators stay in core/decorators/ as pure functions (no DI needed).

  3. Feature modules (IdentityModule, NotificationsModule): Each owns its TypeOrmModule.forRootAsync(...) data source and TypeOrmModule.forFeature(...). Modules do not export TypeOrmModule.

  4. AppModule: Pure orchestrator — imports AppConfigModule, SharedModule, CoreModule, RouterModule, and feature modules. No providers.

  5. Refresh endpoint consolidation: Single POST /identity/auth/refresh replaces duplicate per-actor endpoints.

  6. Template registry port: TemplateRegistryService in notifications follows the port/adapter pattern via ITemplateRegistryService interface, eliminating the DDD violation of a command handler importing directly from infrastructure.

Alternatives Considered

  • Keep data sources in AppModule: Simpler initial setup but violates module encapsulation and forces AppModule to know every entity list.
  • Dynamic modules for core guards: Overhead not justified for two global guards.

Consequences

  • Each feature module is fully self-contained and can be tested in isolation.
  • AppModule is minimal and stable across feature additions.
  • Runtime DI crash from broken useExisting wiring is fixed.
  • POST /identity/auth/user/refresh and POST /identity/auth/client/refresh are removed in favor of POST /identity/auth/refresh.
  • Feature docs: apps/docs/features/api/auth/identity-auth-service-ports.md

Source Paths

  • apps/api/src/app.module.ts
  • apps/api/src/core/core.module.ts
  • apps/api/src/shared/shared.module.ts
  • apps/api/src/modules/identity/identity.module.ts
  • apps/api/src/modules/notifications/notifications.module.ts
  • apps/api/src/modules/notifications/application/services/template-registry.service.interface.ts