Files
goodgo-platform/CONTRIBUTING.md
Ho Ngoc Hai 11f2bf26e6
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

93 lines
3.5 KiB
Markdown

# 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, E>` từ `@modules/shared/domain/result`:
```typescript
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
```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<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.