Qadam Roadmap
проект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 select to match DTOs
  • Never return raw Prisma objects from controllers