# Hướng Dẫn Xử Lý Lỗi cho CQRS Handler
## Tiêu Chuẩn Triển Khai của Nền Tảng GoodGo

---

## 📌 Tham Chiếu Nhanh

| Trạng thái | Số lượng | Modules |
|--------|-------|---------|
| ✓ Đã có Xử Lý Lỗi | 11 | auth (5), listings (2), admin, notifications, payments, search |
| ✗ Cần Xử Lý Lỗi | 66 | Tất cả các handler còn lại + 6 auth handler |
| **Tổng cộng** | **77** | **Tất cả các module** |

---

## 🎯 Pattern 1: Command Handler Chuẩn với Xử Lý Lỗi

Sử dụng pattern này cho **hầu hết các command handler**:

```typescript
import { Logger } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, InternalServerErrorException } from '@modules/shared';

@CommandHandler(YourCommand)
export class YourCommandHandler implements ICommandHandler<YourCommand> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(
    private readonly repository: IYourRepository,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: YourCommand): Promise<YourResult> {
    try {
      // Tải aggregate
      const aggregate = await this.repository.findById(command.id);
      if (!aggregate) {
        throw new NotFoundException('Aggregate', command.id);
      }

      // Thực thi logic domain
      aggregate.doSomething(command.data);

      // Lưu trạng thái
      await this.repository.save(aggregate);

      // Phát hành sự kiện
      const events = aggregate.clearDomainEvents();
      for (const event of events) {
        this.eventBus.publish(event);
      }

      return { id: aggregate.id, status: 'success' };
    } catch (error) {
      // Luôn ném lại các domain exception
      if (error instanceof DomainException) throw error;

      // Ghi log các lỗi không mong đợi
      this.logger.error(
        `Command execution failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );

      // Ném exception HTTP chung
      throw new InternalServerErrorException('Operation failed, please try again');
    }
  }
}
```

---

## 🎯 Pattern 2: Query Handler Chuẩn với Xử Lý Lỗi

Sử dụng pattern này cho **tất cả các query handler**:

```typescript
import { Logger } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { InternalServerErrorException } from '@modules/shared';

@QueryHandler(YourQuery)
export class YourQueryHandler implements IQueryHandler<YourQuery> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(private readonly repository: IYourRepository) {}

  async execute(query: YourQuery): Promise<YourQueryResult> {
    try {
      const result = await this.repository.find(query.criteria);
      if (!result) {
        throw new NotFoundException('Data not found');
      }
      return result;
    } catch (error) {
      this.logger.error(
        `Query execution failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Unable to fetch data, please try again');
    }
  }
}
```

---

## 🎯 Pattern 3: Thao Tác Hàng Loạt với Xử Lý Lỗi Theo Từng Mục

Sử dụng pattern này cho **các thao tác batch** (như `bulk-moderate-listings`):

```typescript
@CommandHandler(BulkCommand)
export class BulkCommandHandler implements ICommandHandler<BulkCommand> {
  private readonly logger = new Logger(this.constructor.name);

  async execute(command: BulkCommand): Promise<BulkResult> {
    const succeeded: string[] = [];
    const failed: Array<{ id: string; reason: string }> = [];

    try {
      for (const id of command.ids) {
        try {
          const item = await this.repository.findById(id);
          if (!item) {
            failed.push({ id, reason: 'Item not found' });
            continue;
          }

          // Xử lý từng mục
          item.update(command.data);
          await this.repository.save(item);
          
          succeeded.push(id);
        } catch (itemError) {
          const message = itemError instanceof Error ? itemError.message : 'Unknown error';
          failed.push({ id, reason: message });
          // Tiếp tục xử lý các mục khác
        }
      }

      return { succeeded, failed, processed: command.ids.length };
    } catch (error) {
      this.logger.error(
        `Bulk operation failed: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Batch operation failed');
    }
  }
}
```

---

## 🎯 Pattern 4: Giảm Cấp Duyên Dáng (Dịch Vụ Không Quan Trọng)

Sử dụng pattern này khi **các dịch vụ thứ cấp có thể thất bại mà không chặn** thao tác chính (như phát hiện trùng lặp trong `create-listing`):

```typescript
async execute(command: CreateListingCommand): Promise<CreateListingResult> {
  try {
    // Luồng quan trọng - phải thành công
    const listing = await this.createListing(command);
    await this.repository.save(listing);

    // Không quan trọng: Phát hiện trùng lặp
    let duplicates = [];
    try {
      duplicates = await this.duplicateDetector.find({
        title: command.title,
        location: command.location,
      });
    } catch (error) {
      this.logger.warn(
        'Duplicate detection failed, continuing without warnings',
        'CreateListingHandler'
      );
      // Tiếp tục - danh sách trùng lặp là tùy chọn
    }

    // Không quan trọng: Xác thực giá
    let priceWarning: PriceWarning | undefined;
    try {
      const result = await this.priceValidator.validate(command);
      if (result.isSuspicious) {
        priceWarning = result;
      }
    } catch (error) {
      this.logger.warn(
        'Price validation failed, continuing without warning',
        'CreateListingHandler'
      );
      // Tiếp tục - cảnh báo giá là tùy chọn
    }

    return {
      listingId: listing.id,
      duplicates,
      priceWarning,
    };
  } catch (error) {
    if (error instanceof DomainException) throw error;
    this.logger.error(
      `Create listing failed: ${error instanceof Error ? error.message : String(error)}`,
      error instanceof Error ? error.stack : undefined,
      'CreateListingHandler'
    );
    throw new InternalServerErrorException('Failed to create listing');
  }
}
```

---

## 🎯 Pattern 5: Lỗi Phân Quyền/Xác Thực

Sử dụng pattern này khi **xác thực có thể thất bại** (như `login-user`):

```typescript
@CommandHandler(LoginUserCommand)
export class LoginUserHandler implements ICommandHandler<LoginUserCommand> {
  private readonly logger = new Logger(this.constructor.name);

  constructor(private readonly tokenService: TokenService) {}

  async execute(command: LoginUserCommand): Promise<TokenPair> {
    try {
      return await this.tokenService.generateTokenPair({
        sub: command.userId,
        phone: command.phone,
        role: command.role,
      });
    } catch (error) {
      this.logger.error(
        `Token generation failed for user ${command.userId}: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        'LoginUserHandler'
      );
      // Sử dụng exception cụ thể cho xác thực
      throw new UnauthorizedException('Unable to create session, please try again');
    }
  }
}
```

---

## ❌ Những Lỗi Phổ Biến Cần Tránh

### ❌ Lỗi 1: Khối Catch Im Lặng
```typescript
// XẤU ❌
try {
  await this.repository.save(entity);
} catch (error) {
  // Im lặng - không ghi log, không ném lỗi
}
```

**Cách sửa:**
```typescript
// TỐT ✓
try {
  await this.repository.save(entity);
} catch (error) {
  this.logger.error(`Save failed: ${error}`, error?.stack, 'HandlerName');
  throw new InternalServerErrorException('Save failed');
}
```

---

### ❌ Lỗi 2: Nuốt Domain Exception
```typescript
// XẤU ❌
try {
  const result = entity.validate();
  if (result.isErr) {
    throw result.unwrapErr(); // ValidationException
  }
  // ...
} catch (error) {
  // Điều này nuốt mất lỗi validation
  throw new InternalServerErrorException('Failed');
}
```

**Cách sửa:**
```typescript
// TỐT ✓
try {
  const result = entity.validate();
  if (result.isErr) {
    throw result.unwrapErr();
  }
  // ...
} catch (error) {
  // Ném lại các domain exception
  if (error instanceof DomainException) throw error;
  
  this.logger.error(`Unexpected: ${error}`, error?.stack);
  throw new InternalServerErrorException('Failed');
}
```

---

### ❌ Lỗi 3: Ghi Log ra Console
```typescript
// XẤU ❌
try {
  // ...
} catch (error) {
  console.error(error); // Không có cấu trúc, không được hệ thống logging ghi nhận
  throw error;
}
```

**Cách sửa:**
```typescript
// TỐT ✓
try {
  // ...
} catch (error) {
  this.logger.error(
    `Error: ${error instanceof Error ? error.message : String(error)}`,
    error instanceof Error ? error.stack : undefined,
    this.constructor.name,
  );
  throw new InternalServerErrorException('Operation failed');
}
```

---

### ❌ Lỗi 4: Ghi Log Không Đầy Đủ
```typescript
// XẤU ❌
try {
  // ...
} catch (error) {
  this.logger.error(error.message); // Thiếu stack trace!
  throw error;
}
```

**Cách sửa:**
```typescript
// TỐT ✓
try {
  // ...
} catch (error) {
  this.logger.error(
    error instanceof Error ? error.message : String(error),
    error instanceof Error ? error.stack : undefined,
    this.constructor.name
  );
  throw error;
}
```

---

### ❌ Lỗi 5: Phát Hành Sự Kiện Trước Khi Xác Nhận Thành Công
```typescript
// XẤU ❌
try {
  aggregate.apply(command);
  this.eventBus.publish(aggregate.events); // Trước khi lưu!
  await this.repository.save(aggregate);
} catch (error) {
  // Sự kiện đã được phát hành nhưng entity chưa được lưu!
}
```

**Cách sửa:**
```typescript
// TỐT ✓
try {
  aggregate.apply(command);
  await this.repository.save(aggregate); // Lưu trước
  
  // Chỉ phát hành sự kiện sau khi lưu thành công
  const events = aggregate.clearDomainEvents();
  for (const event of events) {
    this.eventBus.publish(event);
  }
} catch (error) {
  // Không có sự kiện nào được phát hành - dữ liệu nhất quán
}
```

---

## 🔍 Danh Sách Kiểm Tra Khi Kiểm Tra Từng Handler

Khi xem xét xử lý lỗi, hãy kiểm tra:

- [ ] **Khối Try-Catch**: Bao bọc toàn bộ logic của phương thức execute
- [ ] **Ném Lại Domain Exception**: `if (error instanceof DomainException) throw error;`
- [ ] **Ghi Log Lỗi**: Bao gồm thông báo, stack trace và ngữ cảnh
- [ ] **Sử Dụng Logger**: Dùng logger được inject, không dùng console
- [ ] **Exception Phù Hợp**: Ném đúng loại HTTP exception
- [ ] **Không Có Catch Im Lặng**: Mỗi khối catch đều có ghi log hoặc ném lỗi
- [ ] **Phát Hành Sự Kiện**: Chỉ sau khi lưu trạng thái thành công
- [ ] **Rollback Transaction**: Được xử lý bởi try-catch (ngầm định hoặc tường minh)
- [ ] **Thông Báo Cho Người Dùng**: Phản hồi lỗi có ý nghĩa (không phải chi tiết kỹ thuật)
- [ ] **Ngữ Cảnh Logging**: Bao gồm tên lớp handler

---

## 📋 Danh Sách Triển Khai cho Các Handler Cần Xử Lý Lỗi

1. **Xem lại pattern hiện có** trong các handler được triển khai tốt:
   - `auth/commands/login-user` (các pattern xác thực)
   - `listings/commands/create-listing` (giảm cấp duyên dáng)
   - `admin/commands/bulk-moderate-listings` (các thao tác batch)

2. **Thêm vào phương thức execute**:
   ```typescript
   async execute(command: YourCommand): Promise<YourResult> {
     try {
       // logic hiện có
     } catch (error) {
       // xử lý lỗi
     }
   }
   ```

3. **Kiểm tra xem domain exception có nên được ném lại không**:
   ```typescript
   if (error instanceof DomainException) throw error;
   ```

4. **Thêm ghi log phù hợp**:
   ```typescript
   this.logger.error(
     `Message: ${error instanceof Error ? error.message : String(error)}`,
     error instanceof Error ? error.stack : undefined,
     this.constructor.name
   );
   ```

5. **Ném HTTP exception phù hợp**:
   ```typescript
   throw new InternalServerErrorException();
   // hoặc
   throw new NotFoundException();
   // hoặc
   throw new BadRequestException();
   ```

6. **Kiểm thử các tình huống lỗi**:
   - Repository trả về null hoặc lỗi
   - Xác thực domain thất bại
   - Lưu cơ sở dữ liệu thất bại
   - Phát hành sự kiện thất bại

---

## 🚀 Thứ Tự Ưu Tiên Triển Khai

### BẬC 1 - Làm Trước (33 handler)
- **admin**: 14 handler
- **leads**: 5 handler
- **inquiries**: 4 handler
- **reviews**: 5 handler
- **subscriptions**: 5 handler

**Công sức**: ~2 ngày-lập trình viên

### BẬC 2 - Làm Tiếp (18 handler)
- **payments**: 4 handler
- **search**: 8 handler
- **listings**: 5 handler (đã hoàn thành 2)
- **agents**: 3 handler

**Công sức**: ~1 ngày-lập trình viên

### BẬC 3 - Làm Cuối (8 handler)
- **analytics**: 8 handler
- **auth**: 6 handler (đã hoàn thành 5)

**Công sức**: ~1 ngày-lập trình viên

---

## 📞 Câu Hỏi Thường Gặp

**H: Mỗi handler có cần xử lý lỗi không?**  
Đ: Có. Mọi thao tác bất đồng bộ đều có thể thất bại — cơ sở dữ liệu có thể ngừng hoạt động, mạng có thể lỗi, v.v.

**H: Tôi có nên bắt và nuốt domain exception không?**  
Đ: Không. Hãy ném lại chúng bằng `if (error instanceof DomainException) throw error;`

**H: Nếu handler không có lời gọi cơ sở dữ liệu thì sao?**  
Đ: Vẫn thêm xử lý lỗi. Các lời gọi dịch vụ bên ngoài, phép tính và I/O đều cần try-catch.

**H: Tôi có thể dùng xử lý Exception chung không?**  
Đ: Không. Hãy import và sử dụng các exception của NestJS như `InternalServerErrorException`, `NotFoundException`, v.v.

**H: Tôi có nên ghi log ra console không?**  
Đ: Không. Hãy inject và sử dụng dịch vụ NestJS Logger để ghi log có cấu trúc.

**H: Còn việc xử lý promise rejection thì sao?**  
Đ: Async/await kết hợp với try-catch xử lý tất cả các promise rejection. Đó là lý do tại sao chúng ta bao bọc toàn bộ phương thức execute.

---

## 🎓 Tham Khảo: Các Handler Mẫu

### 1. **Login User Handler** (Xuất sắc)
Vị trí: `apps/api/src/modules/auth/application/commands/login-user/login-user.handler.ts`

✓ Thông báo lỗi rõ ràng cho người dùng  
✓ Loại exception phù hợp (UnauthorizedException)  
✓ Stack trace được ghi log  
✓ Ngữ cảnh handler được bao gồm  

### 2. **Create Listing Handler** (Nâng cao)
Vị trí: `apps/api/src/modules/listings/application/commands/create-listing/create-listing.handler.ts`

✓ Luồng quan trọng được bảo vệ  
✓ Các dịch vụ không quan trọng có thể giảm cấp duyên dáng  
✓ Tiếp tục hoạt động ngay cả khi các dịch vụ thứ cấp thất bại  
✓ Cảnh báo vẫn được cung cấp khi có thể  

### 3. **Bulk Moderate Handler** (Pattern Batch)
Vị trí: `apps/api/src/modules/admin/application/commands/bulk-moderate-listings/bulk-moderate-listings.handler.ts`

✓ Thu thập lỗi theo từng mục  
✓ Tiếp tục xử lý các mục khác  
✓ Trả về kết quả đầy đủ kèm danh sách thất bại  
✓ Theo dõi lỗi từng mục riêng biệt  

---

**Cập Nhật Lần Cuối**: 11 tháng 4 năm 2026  
**Phạm Vi Kiểm Tra**: 77 handler trên 12 module  
**Tuân Thủ**: Hiện tại đã triển khai 14,3%
