Files
web_sport/apps/api/src/modules/README.md
T

2.6 KiB

Module anatomy

Every feature module follows the same internal shape. Consistency here is worth more than local cleverness — a developer opening orders/ for the first time should already know where everything is.

<module>/
├── <module>.module.ts       # Wiring only. No logic, ever.
├── <module>.controller.ts   # HTTP surface: parse, delegate, return. No rules.
├── <module>.service.ts      # Business rules. The only interesting file.
├── <module>.repository.ts   # The ONLY file allowed to touch PrismaService.
├── dto/                     # Request/response shapes + Zod schema bindings.
├── mappers/                 # Prisma row → API type. Keeps Prisma types internal.
├── events/                  # Events this module publishes and subscribes to.
└── public/
    └── index.ts             # The only entry point for other modules.

The four rules

  1. Controllers contain no business logic. If a controller has an if that is not input shaping, the rule belongs in the service.

  2. Only the repository imports Prisma. Services depend on repository interfaces. This is what makes services unit-testable without a database and what keeps a later storage change from rippling outward.

  3. A module owns its tables exclusively. OrdersModule never queries products — it asks ProductsModule's public service, or it stores a snapshot. Shared tables are how a monolith becomes unsplittable.

  4. Cross-module imports go through public/. Deep imports are blocked by ESLint (@sport/eslint-config/nest). If you need something that is not exported, widen the public surface deliberately — do not reach around it.

Talking to another module

Need Mechanism
An answer, now, to continue this request Call its public service
To react to something that happened Subscribe to its domain event
To change its data Call its public service — never write its tables

Modules marked EXTRACTION CANDIDATE

inventory, orders, payments and search are written so they could become independent services later: no foreign reads, communication via events, and no shared transactions with the rest of the monolith beyond their own tables.

That is a constraint on how they are written, not a plan to extract them. Extraction is justified by a real scaling or team-boundary problem, and nothing here assumes it will ever happen.