3.5 KiB
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ừ:
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.
// 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, E> từ @modules/shared/domain/result:
static create(value: string): Result<Phone, string> {
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
// 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<any> hoặc PaginatedResult<any>. Đị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.