# Hướng Dẫn Đóng Góp ## Quy Ước Xử Lý Lỗi ### Tổng Quan Toàn bộ xử lý lỗi ở tầng ứng dụng sử dụng **domain exceptions** từ `@modules/shared/domain/domain-exception`. Không bao giờ import các lớp exception từ `@nestjs/common` trong handlers — hãy dùng domain exceptions của dự án. `GlobalExceptionFilter` bắt tất cả các exception và chuẩn hóa chúng thành một JSON response nhất quán với error codes có cấu trúc. ### Domain Exceptions | Exception | HTTP Status | Khi nào dùng | |---|---|---| | `NotFoundException(entity, id?)` | 404 | Không tìm thấy entity trong database | | `ValidationException(message, details?)` | 400 | Dữ liệu đầu vào không hợp lệ, vi phạm business rule, lỗi tạo value object | | `ConflictException(message)` | 409 | Tài nguyên bị trùng lặp, vi phạm idempotency | | `UnauthorizedException(message?)` | 401 | Thông tin xác thực hoặc token không hợp lệ/đã hết hạn | | `ForbiddenException(message?)` | 403 | Đã xác thực nhưng không được phép thực hiện hành động | Import từ: ```typescript import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception'; ``` ### Các Mẫu Theo Từng Tầng #### Command/Query Handlers Handlers ném domain exceptions trực tiếp. Không cần bọc try-catch — `GlobalExceptionFilter` xử lý các exception chưa được bắt. ```typescript // Good: domain exception with entity context const user = await this.userRepo.findById(id); if (!user) throw new NotFoundException('User', id); // Good: Result pattern from value objects const phoneResult = Phone.create(command.phone); if (phoneResult.isErr) { throw new ValidationException(phoneResult.unwrapErr()); } const phone = phoneResult.unwrap(); // Good: business rule validation if (subscription.status === 'CANCELLED') { throw new ValidationException('Subscription da bi huy'); } ``` #### Controllers Controllers là các tầng ủy quyền mỏng — chúng dispatch tới command/query bus và trả về kết quả. Không cần xử lý lỗi ở tầng controller. #### Domain Services / Value Objects Sử dụng mẫu `Result` từ `@modules/shared/domain/result`: ```typescript static create(value: string): Result { if (!isValid(value)) return Result.err('So dien thoai khong hop le'); return Result.ok(new Phone({ value })); } ``` Handlers sử dụng `Result` bằng cách kiểm tra `.isErr` và ném `ValidationException`. ### Những Điều KHÔNG Nên Làm ```typescript // Bad: NestJS built-in exceptions (missing errorCode in response) import { NotFoundException } from '@nestjs/common'; // Bad: object-style exceptions (inconsistent with domain exception API) throw new NotFoundException({ code: ErrorCode.NOT_FOUND, message: '...' }); // Bad: generic try-catch swallowing infrastructure errors as domain errors try { await this.prisma.plan.findFirst({ where: { tier } }); } catch { throw new NotFoundException('Plan not found'); // hides DB connection errors } // Bad: returning Result from handlers to controllers // Handlers should unwrap Result and throw on error ``` ### Kiểu Trả Về Của Repository Tất cả các phương thức đọc của repository phải trả về DTOs được định kiểu rõ ràng — không bao giờ dùng `Promise` hoặc `PaginatedResult`. Định nghĩa read DTOs ở tầng domain cùng với interface của repository. Xem `listing-read.dto.ts` để tham khảo ví dụ chuẩn.