0

#08 — Sổ tay module: cấu trúc, quy ước, mẫu code Phần 2

10. Chống mẫu — bảng tra

Chống mẫu Vì sao sai Làm gì thay thế
use Illuminate\... trong Domain/ Domain không test được không cần Laravel; khoá cứng vào framework Port + adapter
now() trong Domain Test phải thao tác đồng hồ hệ thống, chạy chậm và không tin cậy Truyền \DateTimeImmutable vào
$order->status = 'paid' Bỏ qua máy trạng thái ⚑ O3 $order->confirm($at)
event(...) trong Domain Rollback để lại event về chuyện chưa xảy ra releaseEvents() + outbox
dispatch(Job) trong transaction Job có thể chạy trước khi commit Outbox, hoặc DB::afterCommit()
Sửa 2 aggregate/1 transaction Deadlock khi tải lên Event + nhất quán cuối
Product::find() trong module Order Vỡ ranh giới, không tách được về sau Contract package hoặc snapshot
Đọc products để hiển thị đơn cũ Đơn cũ đổi nội dung khi sản phẩm đổi Snapshot trong order_items
FK từ orders sang bảng địa chỉ Đơn cũ đổi địa chỉ khi danh mục hành chính cập nhật Snapshot văn bản (doc 02 §3.3)
float cho tiền 0.1 + 0.2 !== 0.3 → lệch sổ không tìm ra nguyên nhân numeric(19,4) + Money VO
Domain/ValueObject/ trong module Generic Tiêu công sức sai chỗ Laravel thuần (§3.2)
Thêm vào phpstan-baseline.neon Nợ kỹ thuật vô hình, tích luỹ vĩnh viễn Sửa lỗi
Bật Octane ở P0 Bug rò rỉ state không tái hiện được, lúc test còn mỏng Bật ở P4 (doc 01 §9.1)

11. Câu hỏi hay gặp

11.1 Đây có phải bounded context mới không?

Chỉ tạo module mới khi trả lời cho ít nhất hai câu:

  1. Nó có từ vựng riêng không? (cùng một từ mang nghĩa khác so với module hiện có)
  2. Nó có aggregate riêng với bất biến riêng không?
  3. Nó có thể thay đổi độc lập với các module khác không?
  4. Có ai trong tổ chức sở hữu nó về mặt nghiệp vụ không?

Nếu chỉ là "một bảng mới" hoặc "một trang admin mới" ⇒ thêm vào module có sẵn.

11.2 Bao giờ tách module thành service riêng?

Không phải câu hỏi của P0–P7. Tiêu chí đo được ở doc 01 §16. Tóm lại: cần scale/deploy khác nhịp rõ rệt không chia sẻ bảng với module khác đội đủ người để trực nó.

Việc dùng contract package (§8.1) khiến ngày đó chỉ là thay một adapter — đó là lý do làm nó sớm dù chưa tách.

11.3 Symlink không hoạt động trên Windows

Triệu chứng: sửa code trong modules/ mà ứng dụng không thấy thay đổi.

# Kiểm tra
Get-Item vendor\modules\catalog | Select-Object Name, LinkType

# Nếu KHÔNG phải SymbolicLink:
#  1) Bật Developer Mode: Settings → Privacy & security → For developers
#  2) Hoặc chạy terminal as Administrator
#  3) Rồi: composer install --no-cache

Phương án dự phòng nếu vẫn không được: bỏ path repository, dùng autoload.psr-4 trực tiếp trong composer.json gốc:

"autoload": {
    "psr-4": {
        "Modules\\Catalog\\": "modules/Catalog/src/",
        "Modules\\Shared\\":  "modules/Shared/src/"
    }
}

Đánh đổi: mất tầng cưỡng chế của Composer (§2.2), chỉ còn deptrac + arch test. Chấp nhận được nhưng yếu hơn — nên ưu tiên sửa symlink.

11.4 Nên viết CLAUDE.md thế nào?

CLAUDE.md ở gốc repo là thứ AI agent đọc trước mọi việc. Nội dung nên có:

  • Trỏ tới docs/01..docs/04 và nói rõ doc nào là nguồn sự thật cho việc gì.
  • Luật phụ thuộc §3.1 dưới dạng ngắn gọn.
  • Danh sách chống mẫu §10.
  • Lệnh chạy: composer check, pest --filter, docker compose up -d.
  • Quy tắc: viết Domain + test trước Infrastructure (§7 bước 7).
  • Nhắc: mọi bất biến có mã ⚑ trong doc 02 §23 phải có test.

12. Definition of Done cho một module

Một module chỉ được coi là xong khi tất cả các dòng dưới đây đúng:

  • [ ] Mọi bất biến của module trong doc 02 §23 đều có ít nhất một test, tên test ghi mã ⚑
  • [ ] Coverage Domain/ ≥ 90%; toàn module ≥ 75%
  • [ ] composer check xanh (Pint, PHPStan, deptrac, Pest)
  • [ ] Đã cố ý vi phạm ranh giới một lần và xác nhận CI đỏ (§6.3)
  • [ ] Migration chạy được và rollback được trên PostgreSQL 18 thật
  • [ ] Bảng "ai sở hữu bảng nào" ở doc 03 §17 đã cập nhật
  • [ ] Domain event mới đã thêm vào danh mục doc 02 §20.3 kèm người nghe
  • [ ] Không có dòng nào thêm vào phpstan-baseline.neon
  • [ ] Nếu chạm tiền hoặc tồn kho: có test đồng thời (tests/Concurrency/)
  • [ ] Nếu gọi hệ ngoài: có ACL trong Infrastructure/Acl/, không có DTO của nhà cung cấp lọt vào Domain
  • [ ] Nếu có tác dụng phụ ra ngoài: idempotent, có idempotency_keys hoặc outbox
  • [ ] README ngắn trong modules/$M/README.md: module này trả lời câu hỏi nghiệp vụ gì

13. Việc tiếp theo

Tài liệu Nội dung Pha
05-api-spec/ OpenAPI 3.1 cho các context ở P1 P1
06-order-lifecycle.md Saga đơn hàng, kịch bản bù trừ chi tiết P2
10-testing-strategy.md Dữ liệu test, môi trường, test đồng thời P0

Cần quyết ở P0, trước khi tạo module đầu tiên:

  1. Tên namespace gốc: Modules\ (đề xuất) hay tên công ty? Đổi sau tốn một lần tìm-thay toàn repo — không đắt, nhưng nên quyết một lần.
  2. Symlink trên Windows (§11.3) — phải xác nhận hoạt động trước khi tạo module thứ hai.
  3. Có làm contract package riêng hay đặt interface trong Shared? Đề xuất: contract package riêng cho Pricing, Inventory, Tax (ba module bị gọi nhiều nhất); các module khác dùng event là đủ.

Tài liệu #4 · Lập 13/08/2026 · Mọi mã ⚑ tham chiếu doc 02 §23. Cấu hình deptrac/Pest trong tài liệu này chưa chạy thật — sẽ kiểm chứng khi dựng dự án ở P0 (§6.3).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí