проектdocs/Agents/rules/data-dto-boundaries.md
data-dto-boundaries.md
Обновлён 1 апр. 2026 г., 12:41 · 0 комментариев
title: Use DTOs at Layer Boundaries impact: HIGH impactDescription: Prevents internal data structures from leaking across layers tags: data, dto, boundaries, validation, zod
Паспорт документа
- Статус документа: living standard
- Актуально на: 28 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: при изменении инженерной практики, CI/CD, архитектурных правил или локального workflow
- Область применения: внутренние rule/reference-card документы для инженерной команды
- Связанные документы:
Use DTOs at Layer Boundaries
Impact: HIGH
Data Transfer Objects (DTOs) define the shape of data at each boundary:
- Input DTOs: Validate and type incoming data (Zod schemas)
- Response DTOs: Shape outgoing data (no sensitive fields)
Incorrect (raw Prisma types leaking to API):
@Get(':id')
async getItem(@Param('id') id: string) {
return this.prisma.item.findFirst({ where: { id } }); // Exposes all fields!
}
Correct (DTO at each boundary):
// packages/shared/src/schemas/item.ts — shared Zod schema
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3).max(200),
description: z.string().min(10).max(5000).optional(),
shortDescription: z.string().max(300).optional(),
priceFrom: z.number().nonnegative().optional(),
priceTo: z.number().nonnegative().optional(),
subjectId: z.string().uuid().optional(),
studyFormat: z.enum(['ONLINE', 'OFFLINE', 'HYBRID']),
studyType: z.enum(['GROUP', 'MINI_GROUP', 'ONE_ON_ONE']).optional(),
language: z.enum(['RU', 'UZ_LATIN', 'UZ_CYRILLIC', 'EN', 'KK', 'TG', 'ANY']).default('ANY'),
});
export type CreateItemDTO = z.infer<typeof CreateItemSchema>;
// apps/api/src/modules/item/dto/item-response.dto.ts
export interface ItemListDTO {
id: string;
name: string;
slug: string;
priceFrom: number | null;
priceTo: number | null;
imageUrl: string | null;
studyFormat: string;
seller: { id: string; type: string };
location: { name: string } | null;
ratingAvg: number;
reviewsCount: number;
}
// Controller uses the response DTO type
@Get(':slug')
async getBySlug(@Param('slug') slug: string): Promise<ItemDetailDTO> {
const item = await this.itemService.findBySlug(slug);
if (!item) throw new NotFoundException();
return item; // Already shaped by repository's select
}
Rules:
- Input validation schemas live in
packages/shared(shared with frontend) - Response DTOs are defined per use-case (list vs detail vs admin)
- Repositories shape data via Prisma
selectto match DTOs - Never return raw Prisma objects from controllers