#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 có cho ít nhất hai câu:
- 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ó)
- Nó có aggregate riêng với bất biến riêng không?
- Nó có thể thay đổi độc lập với các module khác không?
- 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 và không chia sẻ bảng với module khác và độ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/04và 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 checkxanh (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_keyshoặ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:
- 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. - Symlink trên Windows (§11.3) — phải xác nhận hoạt động trước khi tạo module thứ hai.
- Có làm contract package riêng hay đặt interface trong
Shared? Đề xuất: contract package riêng choPricing,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