CodePark/ questions / Layered, clean & hexagonal architecture
START PRACTICINGPRACTICE
Backend question · Software design & patterns › Layered, clean & hexagonal architecture

In a NestJS app that aims for clean architecture, where should framework code such as @Injectable(), @Entity() from TypeORM and HttpException go? How strict should you be?

Strictly, framework code belongs in the outer layers. These parts live in infrastructure or interface adapters:

  • controllers
  • Nest modules
  • ORM entities
  • repository implementations
  • the code that maps errors to HttpException

Domain entities and use cases stay plain TypeScript. They throw domain errors such as OrderAlreadyPaidError. They depend on repository interfaces, not on TypeORM's Repository. A mapper converts between the ORM entity and the domain entity.

In practice, teams make conscious trade-offs:

  • Putting @Injectable() on a use case class is a small, harmless coupling. The decorator doesn't change behaviour, and the class still works with new.
  • Reusing TypeORM-decorated classes as domain entities saves mapping code. But it ties the domain to the database schema and to lazy-loading quirks.

A reasonable rule: never let HTTP types or ORM query APIs into domain logic. Decide case by case on passive annotations, which are decorators that only add metadata. For a CRUD-heavy service, full separation may cost more than it gives back.

  • Framework, ORM and HTTP code belong in the outer layers.
  • Domain code and use cases throw domain errors and use repository interfaces.
  • Mappers translate between ORM entities and domain objects.
  • Many teams accept passive annotations like @Injectable() as a fair trade-off.
  • Full separation may not pay off for a simple CRUD service.