Files
goodgo-platform/docs/audits/INQUIRIES_MODULE_EXPLORATION_2.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

24 KiB

Module Inquiries - Khám Phá Toàn Diện

GoodGo Platform Backend API
Vị Trí Module: apps/api/src/modules/inquiries/
Tổng Số Dòng Code: 1.212 dòng
Ngày Khám Phá: 11 tháng 4 năm 2026


📋 MỤC LỤC

  1. Cấu Trúc Thư Mục
  2. Danh Sách File Đầy Đủ
  3. Kiến Trúc Module
  4. Các Lớp & Handler Chính
  5. Phân Tích Tầng DDD
  6. Tóm Tắt File Test

📁 CẤU TRÚC THƯ MỤC

apps/api/src/modules/inquiries/
├── application/
│   ├── __tests__/
│   │   ├── create-inquiry.handler.spec.ts
│   │   ├── get-inquiries-by-agent.handler.spec.ts
│   │   ├── get-inquiries-by-listing.handler.spec.ts
│   │   └── mark-inquiry-read.handler.spec.ts
│   ├── commands/
│   │   ├── create-inquiry/
│   │   │   ├── create-inquiry.command.ts
│   │   │   └── create-inquiry.handler.ts
│   │   └── mark-inquiry-read/
│   │       ├── mark-inquiry-read.command.ts
│   │       └── mark-inquiry-read.handler.ts
│   └── queries/
│       ├── get-inquiries-by-agent/
│       │   ├── get-inquiries-by-agent.handler.ts
│       │   └── get-inquiries-by-agent.query.ts
│       └── get-inquiries-by-listing/
│           ├── get-inquiries-by-listing.handler.ts
│           └── get-inquiries-by-listing.query.ts
├── domain/
│   ├── __tests__/
│   │   └── inquiry-domain.spec.ts
│   ├── entities/
│   │   └── inquiry.entity.ts
│   ├── events/
│   │   ├── inquiry-created.event.ts
│   │   └── inquiry-read.event.ts
│   └── repositories/
│       ├── inquiry.repository.ts
│       └── inquiry-read.dto.ts
├── infrastructure/
│   └── repositories/
│       └── prisma-inquiry.repository.ts
├── presentation/
│   ├── __tests__/
│   │   └── inquiries.controller.spec.ts
│   ├── controllers/
│   │   └── inquiries.controller.ts
│   └── dto/
│       ├── create-inquiry.dto.ts
│       └── list-inquiries.dto.ts
├── index.ts
└── inquiries.module.ts

📄 DANH SÁCH FILE ĐẦY ĐỦ

TẦNG APPLICATION (8 file)

Commands (4 file)

Đường Dẫn File Loại Mô Tả
application/commands/create-inquiry/create-inquiry.command.ts Command DTO để tạo inquiry - chứa userId, listingId, message, phone
application/commands/create-inquiry/create-inquiry.handler.ts Command Handler Thực thi CreateInquiryCommand; xác thực listing tồn tại, tạo entity, phát sự kiện
application/commands/mark-inquiry-read/mark-inquiry-read.command.ts Command DTO để đánh dấu inquiry đã đọc - chứa inquiryId, agentUserId
application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts Command Handler Thực thi MarkInquiryReadCommand; xác thực quyền hạn, đánh dấu inquiry đã đọc, phát sự kiện

Queries (4 file)

Đường Dẫn File Loại Mô Tả
application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts Query DTO để liệt kê inquiry của agent - chứa agentUserId, page, limit
application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts Query Handler Tra cứu agent theo userId, ủy quyền cho repository findByAgent
application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts Query DTO để liệt kê inquiry theo listing - chứa listingId, page, limit
application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts Query Handler Ủy quyền trực tiếp cho repository findByListing

Test Tầng Application (4 file)

Đường Dẫn File Loại Số Test Phạm Vi
application/__tests__/create-inquiry.handler.spec.ts Jest 4 test Happy path, listing không tồn tại, phát domain event
application/__tests__/mark-inquiry-read.handler.spec.ts Jest 5 test Happy path, inquiry không tồn tại, listing không tồn tại, truy cập bị từ chối, agent không tồn tại
application/__tests__/get-inquiries-by-listing.handler.spec.ts Jest 2 test Kết quả phân trang, kết quả rỗng
application/__tests__/get-inquiries-by-agent.handler.spec.ts Jest 2 test Kết quả phân trang, agent không tồn tại

TẦNG DOMAIN (6 file)

Entities (1 file)

Đường Dẫn File Loại Mô Tả
domain/entities/inquiry.entity.ts Aggregate Root Entity domain cốt lõi với id, listingId, userId, message, phone, isRead; các phương thức: createNew(), markAsRead()

Events (2 file)

Đường Dẫn File Loại Mô Tả
domain/events/inquiry-created.event.ts Domain Event Phát khi inquiry được tạo - chứa aggregateId, listingId, userId
domain/events/inquiry-read.event.ts Domain Event Phát khi inquiry được đánh dấu đã đọc - chứa aggregateId, listingId, userId

Repositories (2 file)

Đường Dẫn File Loại Mô Tả
domain/repositories/inquiry.repository.ts Interface + Symbol Interface IInquiryRepository với 6 phương thức; symbol INQUIRY_REPOSITORY cho DI; interface PaginatedResult
domain/repositories/inquiry-read.dto.ts Interface Read DTO cho queries - bao gồm tiêu đề listing, thông tin người dùng, timestamps

Test Tầng Domain (1 file)

Đường Dẫn File Loại Số Test Phạm Vi
domain/__tests__/inquiry-domain.spec.ts Jest 5 test Tạo entity, phone null, domain events, markAsRead

TẦNG INFRASTRUCTURE (1 file)

Triển Khai Repository

Đường Dẫn File Loại Mô Tả
infrastructure/repositories/prisma-inquiry.repository.ts Service Triển khai IInquiryRepository dùng Prisma; 6 phương thức: findById, save, markAsRead, findByListing, findByAgent, countUnreadByAgent

TẦNG PRESENTATION (5 file)

Controller (1 file)

Đường Dẫn File Loại Routes Auth
presentation/controllers/inquiries.controller.ts NestJS Controller POST /, GET /listing/:id, GET /agent/me, PATCH /:id/read JWT + RBAC

Các Endpoint:

  • POST /inquiries - Tạo inquiry (BUYER)
  • GET /inquiries/listing/:listingId - Lấy inquiry theo listing
  • GET /inquiries/agent/me - Lấy inquiry của agent đang đăng nhập (chỉ AGENT)
  • PATCH /inquiries/:id/read - Đánh dấu inquiry đã đọc (chỉ AGENT)

Data Transfer Objects (2 file)

Đường Dẫn File Loại Thuộc Tính Validation
presentation/dto/create-inquiry.dto.ts DTO listingId, message, phone? Chuỗi bắt buộc, message tối đa 2000 ký tự
presentation/dto/list-inquiries.dto.ts DTO page?, limit? Số nguyên, tối thiểu 1, tối đa 100, mặc định 1/20

Test Tầng Presentation (1 file)

Đường Dẫn File Loại Số Test Phạm Vi
presentation/__tests__/inquiries.controller.spec.ts Jest 6 test Cả 4 endpoint, xử lý phone null, giá trị mặc định phân trang

FILE MODULE (2 file)

Đường Dẫn File Loại Mô Tả
inquiries.module.ts NestJS Module Exports: InquiriesController; Providers: PrismaInquiryRepository, 2 command handler, 2 query handler
index.ts Barrel Export Exports: InquiriesModule, symbol INQUIRY_REPOSITORY, interface IInquiryRepository, InquiryEntity

🏗️ KIẾN TRÚC MODULE

Mẫu Thiết Kế: CQRS + Event Sourcing + DDD

┌─────────────────────────────────────────────────────────────┐
│                    PRESENTATION LAYER                        │
│  Controllers + DTOs (inquiries.controller.ts)               │
└────────────────────────┬────────────────────────────────────┘
                         │
        ┌────────────────┼────────────────┐
        ↓                ↓                ↓
   ┌─────────┐    ┌─────────┐    ┌─────────┐
   │ Commands│    │ Queries │    │ Validation│
   └────┬────┘    └────┬────┘    └─────────┘
        │              │
        └──────┬───────┘
               ↓
    ┌──────────────────────┐
    │  APPLICATION LAYER   │
    │  Handlers + Services │
    └──────────┬───────────┘
               │
        ┌──────┴──────┐
        ↓             ↓
    ┌──────────┐  ┌──────────┐
    │ Commands │  │ Queries  │
    │ Handlers │  │ Handlers │
    └────┬─────┘  └────┬─────┘
         │             │
         └──────┬──────┘
                ↓
    ┌──────────────────────┐
    │   DOMAIN LAYER       │
    │ Entities + Events    │
    │ Repository Interface │
    └──────────┬───────────┘
               │
               ↓
    ┌──────────────────────┐
    │ INFRASTRUCTURE LAYER │
    │ Prisma Repository    │
    │ Database Queries     │
    └──────────────────────┘

Luồng Dữ Liệu

Tạo Inquiry:

POST /inquiries (CreateInquiryDto)
  → InquiriesController.createInquiry()
  → CommandBus.execute(CreateInquiryCommand)
  → CreateInquiryHandler.execute()
    - Xác thực listing tồn tại (Prisma)
    - Tạo InquiryEntity
    - Lưu vào repository (Prisma)
    - Phát InquiryCreatedEvent qua EventBus
  → Trả về { id, listingId, createdAt }

Đánh Dấu Đã Đọc:

PATCH /inquiries/:id/read (chỉ agent)
  → InquiriesController.markAsRead()
  → CommandBus.execute(MarkInquiryReadCommand)
  → MarkInquiryReadHandler.execute()
    - Tìm inquiry entity
    - Xác minh agent là chủ sở hữu listing
    - Gọi entity.markAsRead()
    - Cập nhật trong repository
    - Phát InquiryReadEvent qua EventBus
  → Trả về { success: true }

Lấy Danh Sách Inquiry:

GET /inquiries/listing/:id hoặc /agent/me (có phân trang)
  → InquiriesController.getByListing() hoặc getMyInquiries()
  → QueryBus.execute(GetInquiriesByListingQuery hoặc GetInquiriesByAgentQuery)
  → Handler ủy quyền cho repository
  → Repository.findByListing() hoặc findByAgent()
    - Truy vấn Prisma với join
    - Ánh xạ sang InquiryReadDto
    - Trả về PaginatedResult
  → Trả về dữ liệu phân trang

🔑 CÁC LỚP & HANDLER CHÍNH

Domain Entity: InquiryEntity

export class InquiryEntity extends AggregateRoot<string> {
  // Thuộc tính
  private _listingId: string;
  private _userId: string;
  private _message: string;
  private _phone: string | null;
  private _isRead: boolean;

  // Factory Method
  static createNew(id, listingId, userId, message, phone): InquiryEntity
     Tạo inquiry mới với isRead=false
     Phát InquiryCreatedEvent

  // Business Logic
  markAsRead(): void
     Đặt isRead thành true
     Phát InquiryReadEvent
}

Command Handlers

CreateInquiryHandler

class CreateInquiryHandler implements ICommandHandler<CreateInquiryCommand> {
  async execute(command: CreateInquiryCommand): Promise<CreateInquiryResult> {
    // 1. Xác thực listing tồn tại
    const listing = await prisma.listing.findUnique(...)
    if (!listing) throw NotFoundException

    // 2. Tạo entity qua factory
    const inquiry = InquiryEntity.createNew(...)

    // 3. Lưu vào database
    await inquiryRepo.save(inquiry)

    // 4. Phát domain events
    const events = inquiry.clearDomainEvents()
    events.forEach(e => eventBus.publish(e))

    return { id, listingId, createdAt }
  }
}

MarkInquiryReadHandler

class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCommand> {
  async execute(command: MarkInquiryReadCommand): Promise<void> {
    // 1. Tải aggregate root
    const inquiry = await inquiryRepo.findById(...)
    if (!inquiry) throw NotFoundException

    // 2. Xác minh quyền hạn
    const listing = await prisma.listing.findUnique(...)
    const agent = await prisma.agent.findUnique(...)
    if (agent.id !== listing.agentId) throw ForbiddenException

    // 3. Cập nhật trạng thái domain
    inquiry.markAsRead()

    // 4. Lưu trạng thái
    await inquiryRepo.markAsRead(...)

    // 5. Phát sự kiện
    const events = inquiry.clearDomainEvents()
    events.forEach(e => eventBus.publish(e))
  }
}

Query Handlers

GetInquiriesByListingHandler

class GetInquiriesByListingHandler implements IQueryHandler<GetInquiriesByListingQuery> {
  async execute(query: GetInquiriesByListingQuery): Promise<PaginatedResult<InquiryReadDto>> {
    return this.inquiryRepo.findByListing(
      query.listingId,
      query.page,
      query.limit
    )
  }
}

GetInquiriesByAgentHandler

class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByAgentQuery> {
  async execute(query: GetInquiriesByAgentQuery): Promise<PaginatedResult<InquiryReadDto>> {
    // 1. Tra cứu agent ID từ user ID
    const agent = await prisma.agent.findUnique({ where: { userId } })
    if (!agent) throw NotFoundException

    // 2. Ủy quyền cho repository
    return this.inquiryRepo.findByAgent(agent.id, page, limit)
  }
}

Triển Khai Repository

PrismaInquiryRepository

Các Phương Thức:

  1. findById(id) - Tra cứu inquiry đơn lẻ
  2. save(entity) - Tạo inquiry
  3. markAsRead(id) - Cập nhật cờ isRead
  4. findByListing(listingId, page, limit) - Tìm kiếm phân trang với join
  5. findByAgent(agentId, page, limit) - Tìm kiếm phân trang qua agent của listing
  6. countUnreadByAgent(agentId) - Tổng hợp số lượng chưa đọc

Quan Hệ Prisma Sử Dụng:

  • inquiry.listing → property (lấy tiêu đề)
  • inquiry.user → fullName, phone
  • listing.agentId → lọc theo agent
  • Phân trang: skip/take với orderBy giảm dần

🎯 CẤU TRÚC TẦNG DDD

TẦNG DOMAIN (domain/)

Mục Đích: Logic nghiệp vụ thuần túy, độc lập với framework

Bao Gồm:

  • Entities - inquiry.entity.ts (Aggregate Root)

    • Lớp TypeScript thuần túy
    • Đóng gói các quy tắc nghiệp vụ (cờ isRead, xác thực trường)
    • Factory method để tạo mới
    • Các phương thức chuyển đổi trạng thái
    • Tập hợp domain events
  • Events - inquiry-created.event.ts, inquiry-read.event.ts

    • Ghi lại các sự kiện nghiệp vụ quan trọng
    • Dùng cho event sourcing và audit trail
    • Lớp dữ liệu đơn giản
  • Repositories - inquiry.repository.ts, inquiry-read.dto.ts

    • Interface định nghĩa hợp đồng (đảo ngược phụ thuộc)
    • Symbol cho DI token
    • Read DTO tách biệt với write entity
    • Interface kết quả phân trang

Sự Cô Lập:

  • Không có dependency NestJS
  • Không có dependency database
  • Không có dependency dịch vụ bên ngoài

TẦNG APPLICATION (application/)

Mục Đích: Use case và điều phối

Bao Gồm:

  • Commands - Các thao tác thay đổi dữ liệu

    • CreateInquiryCommand - Input DTO
    • MarkInquiryReadCommand - Input DTO
    • Các handler điều phối thao tác domain
  • Queries - Các đọc bất biến

    • GetInquiriesByListingQuery
    • GetInquiriesByAgentQuery
    • Các handler ủy quyền cho repository

Trách Nhiệm:

  • Xác thực điều kiện tiên quyết (listing tồn tại, agent được phép)
  • Điều phối các thao tác domain entity
  • Phát domain events
  • Xử lý các mối quan tâm xuyên suốt (logging, v.v.)

Tích Hợp NestJS:

  • Decorator @CommandHandler()
  • Decorator @QueryHandler()
  • Dependency injection qua constructor

TẦNG INFRASTRUCTURE (infrastructure/)

Mục Đích: Chi tiết database và persistence

Bao Gồm:

  • Repositories - prisma-inquiry.repository.ts
    • Triển khai interface repository của domain
    • Dùng Prisma client cho các truy vấn
    • Ánh xạ bản ghi database ↔ domain entity
    • Xử lý logic phân trang

Trách Nhiệm:

  • Xây dựng truy vấn
  • Ánh xạ kết quả
  • Tính toán phân trang
  • Quan hệ join
  • Tối ưu hóa riêng cho database

Sự Cô Lập:

  • Triển khai có thể hoán đổi (có thể dùng TypeORM, MongoDB, v.v.)
  • Code domain không bị ảnh hưởng khi thay đổi database

TẦNG PRESENTATION (presentation/)

Mục Đích: Giao diện HTTP và I/O

Bao Gồm:

  • Controllers - inquiries.controller.ts

    • Decorator NestJS @Controller
    • Các handler route HTTP
    • Phân phối đến CQRS bus
    • Trả về HTTP response
  • DTOs - create-inquiry.dto.ts, list-inquiries.dto.ts

    • Xác thực đầu vào (class-validator)
    • Tài liệu Swagger (@ApiProperty)
    • Tách biệt với domain entity

Trách Nhiệm:

  • Xử lý route
  • Xác thực request
  • Xác thực danh tính/Phân quyền
  • Định dạng response
  • Tài liệu API

Tích Hợp NestJS:

  • Decorator: @Post, @Get, @Patch
  • Guard: JwtAuthGuard, RolesGuard
  • Middleware: @CurrentUser, @Roles

📊 TÓM TẮT FILE TEST

Thống Kê Test

Tầng File Số Test Loại
Domain domain/__tests__/inquiry-domain.spec.ts 5 Jest
Application application/__tests__/create-inquiry.handler.spec.ts 4 Jest
Application application/__tests__/mark-inquiry-read.handler.spec.ts 5 Jest
Application application/__tests__/get-inquiries-by-listing.handler.spec.ts 2 Jest
Application application/__tests__/get-inquiries-by-agent.handler.spec.ts 2 Jest
Presentation presentation/__tests__/inquiries.controller.spec.ts 6 Jest
TỔNG 6 file 24 test Jest + Vitest

Phạm Vi Test Theo File

Test Domain (inquiry-domain.spec.ts) - 5 test

✓ InquiryEntity.createNew() có phone
✓ InquiryEntity.createNew() với phone null
✓ createNew() phát InquiryCreatedEvent
✓ markAsRead() đặt cờ thành true
✓ markAsRead() phát InquiryReadEvent

Test CreateInquiryHandler - 4 test

✓ Tạo inquiry thành công
✓ Ném NotFoundException khi listing không tồn tại
✓ Phát domain events sau khi lưu

Test MarkInquiryReadHandler - 5 test

✓ Đánh dấu inquiry đã đọc thành công
✓ Ném NotFoundException khi không tìm thấy inquiry
✓ Ném NotFoundException khi không tìm thấy listing
✓ Ném ForbiddenException khi người dùng không phải agent
✓ Ném ForbiddenException khi không tìm thấy agent

Test GetInquiriesByListingHandler - 2 test

✓ Trả về kết quả phân trang
✓ Trả về dữ liệu rỗng khi không có inquiry

Test GetInquiriesByAgentHandler - 2 test

✓ Trả về kết quả phân trang
✓ Ném NotFoundException cho người dùng không phải agent

Test InquiriesController - 6 test

✓ POST tạo inquiry với command dispatch
✓ POST truyền phone null khi không cung cấp
✓ GET /listing phân phối query với giá trị mặc định
✓ GET /listing truyền phân trang tùy chỉnh
✓ GET /agent/me phân phối agent query
✓ PATCH đánh dấu inquiry và trả về thành công

🔍 THỐNG KÊ TÓM TẮT

Chỉ Số Số Lượng
Tổng Số File 25
File Nguồn (.ts, trừ test) 19
File Test 6
Tổng Số Dòng Code 1.212
Commands 2
Queries 2
Command Handlers 2
Query Handlers 2
Domain Events 2
HTTP Endpoint 4
DTOs 2
Interfaces 3 (IInquiryRepository, PaginatedResult, InquiryReadDto)
Test Suite 6
Test Case 24

🛠️ CÁC DEPENDENCY CHÍNH

Gói Bên Ngoài:

  • @nestjs/common - Framework
  • @nestjs/cqrs - CQRS bus
  • @paralleldrive/cuid2 - Tạo ID
  • @prisma/client - ORM
  • class-validator - Xác thực DTO
  • @nestjs/swagger - Tài liệu API

Module Nội Bộ:

  • @modules/shared - AggregateRoot, DomainEvent, exceptions
  • @modules/auth - JwtPayload, JwtAuthGuard, RolesGuard

🔐 Bảo Mật & Phân Quyền

Xác Thực Danh Tính:

  • Tất cả endpoint yêu cầu @UseGuards(JwtAuthGuard)
  • JWT token được trích xuất qua decorator @CurrentUser()

Phân Quyền:

  • GET /agent/me → áp dụng @Roles('AGENT')
  • PATCH /:id/read → áp dụng @Roles('AGENT')
  • MarkInquiryReadHandler xác minh:
    • Inquiry tồn tại
    • Listing tồn tại
    • Người dùng đã đăng ký là agent
    • Agent sở hữu listing

📝 HỢP ĐỒNG API

POST /inquiries

Request:  { listingId, message, phone? }
Response: { id, listingId, createdAt }
Status:   201 Created | 400 Bad Request | 401 Unauthorized | 404 Not Found

GET /inquiries/listing/:listingId

Request:  Query: page?, limit?
Response: PaginatedResult<InquiryReadDto>
Status:   200 OK | 401 Unauthorized

GET /inquiries/agent/me

Request:  Query: page?, limit?
Response: PaginatedResult<InquiryReadDto>
Status:   200 OK | 401 Unauthorized | 403 Forbidden

PATCH /inquiries/:id/read

Request:  (không có body)
Response: { success: boolean }
Status:   200 OK | 401 Unauthorized | 403 Forbidden | 404 Not Found

🎓 NHẬN XÉT KIẾN TRÚC

Điểm Mạnh

  1. Kiến Trúc Sạch - Phân tách mối quan tâm rõ ràng giữa các tầng
  2. Mẫu CQRS - Đường đọc/ghi riêng biệt giúp tăng khả năng mở rộng
  3. Thiết Kế Hướng Domain - Logic nghiệp vụ nằm trong entity, không phải mô hình thiếu máu
  4. Hướng Sự Kiện - Domain events cho phép audit trail và event sourcing
  5. Khả Năng Kiểm Thử - Mỗi tầng có thể kiểm thử độc lập với mock
  6. An Toàn Kiểu - TypeScript đầy đủ với interface và kiểu chặt chẽ
  7. Framework DI - NestJS cung cấp dependency injection sẵn có

Quyết Định Thiết Kế

  1. InquiryEntity là Aggregate Root

    • Đóng gói quy tắc nghiệp vụ inquiry
    • Kiểm soát chuyển đổi trạng thái qua phương thức (createNew, markAsRead)
    • Thu thập domain events
  2. Mẫu Repository

    • Interface trong domain, triển khai trong infrastructure
    • Cho phép hoán đổi nguồn dữ liệu mà không ảnh hưởng đến logic nghiệp vụ
  3. DTO Đọc/Ghi Riêng Biệt

    • CreateInquiryDto (đầu vào) so với InquiryReadDto (đầu ra)
    • Cho phép tiến hóa API linh hoạt
  4. CQRS Handlers

    • Commands xử lý thay đổi dữ liệu với phân quyền
    • Queries xử lý đọc với lọc
    • Cả hai độc lập, có thể tối ưu riêng biệt
  5. Interface Phân Trang

    • Phân trang nhất quán trên tất cả endpoint danh sách
    • Mô hình page + limit với totalPages được tính toán

Kết Thúc Báo Cáo Khám Phá