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

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.