# 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.ts # Wiring only. No logic, ever. ├── .controller.ts # HTTP surface: parse, delegate, return. No rules. ├── .service.ts # Business rules. The only interesting file. ├── .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.