บทที่ 19 · Part 4 — Model Behavior in Code
Aggregates and Invariants
ออกแบบ consistency boundary จาก business invariant, concurrency และ failure ไม่ใช่จาก foreign-key graph
Aggregates and Invariants
ทีมหนึ่งสร้าง Transfer Aggregate ที่โหลด Customer, Risk Profile, Wallet, Provider Attempts และ Ledger Entries ทั้งหมด เพราะ objects “เกี่ยวข้องกัน” ผลคือ command เล็กช้าและ conflict สูง Aggregate ไม่ใช่ object graph ของทุกความสัมพันธ์ แต่เป็น consistency boundary ที่ root คุม invariant
จบบทนี้คุณจะ
หา Aggregate จาก invariant และ transaction need ออกแบบ root/inside references ให้เล็ก แยก consistency ภายใน Aggregate จาก process ข้าม Aggregates/Contexts และเชื่อม optimistic/pessimistic concurrency กับ business conflict ที่ต้องป้องกัน
เริ่มจาก Invariant
DDD Aggregate ของ Martin Fowler อธิบาย collection ที่ถูกปฏิบัติเป็น unit และมี root คุม integrity เขียน invariant ก่อน:
Within Transfer Intent, confirmed Money and Beneficiary cannot change silently.
Within Funds Reservation, released amount cannot be consumed again.
Within Execution Attempt, one idempotency identity cannot represent different parameters.
อย่ารวมเพราะมี foreign key ถ้า invariant ไม่ต้อง atomic ร่วม ให้ reference by identity
Aggregate Root เป็น Gate
func (t *TransferIntent) Confirm(terms TermsVersion) error {
if t.status != IntentDraft {
return ErrIntentNotDraft
}
t.status = IntentConfirmed
t.terms = terms
return nil
}
Caller เปลี่ยน fields ภายในไม่ได้ Root ใช้ language operation และคืน domain error
Aggregate เล็กและ Transaction เดียว
guideline ทั่วไป:
- transaction เปลี่ยน Aggregate เดียว
- Aggregate อื่นอ้างด้วย identity
- cross-Aggregate reaction ผ่าน application/process/domain events ตาม consistency need
- eventual consistency ต้องมี owner/recovery ไม่ใช่ปล่อย “ eventually” ลอย ๆ
หาก business invariant ต้อง atomic ข้าม objects จริง อาจอยู่ Aggregate เดียว แต่ท้าทาย scope ด้วย concurrency/load/lifecycle
เส้นภายในกรอบต้อง consistent ใน transaction ของ Aggregate ส่วนเส้นประข้าม Aggregate เป็น identity/ fact ที่ process ประสานตาม consistency need ไม่ใช่ object reference ที่เปิดให้แก้พร้อมกัน
Concurrency ไม่ถูกแก้ด้วย Object Model
Aggregate ใน memory ไม่ป้องกัน 2 requests โหลด version เดียวแล้ว Save ทับกัน ใช้ optimistic version:
UPDATE transfer_intents
SET status = ?, version = version + 1
WHERE id = ? AND version = ?;
ถ้า affected rows = 0 ให้ reload/reject/retry ตาม business semantics ไม่ blind retry command ที่ ผู้ใช้ไม่ตั้งใจ
Pessimistic lock เหมาะบาง high-contention decision แต่มี wait/deadlock cost เลือกตาม evidence
Aggregate ไม่ครอบ Network Transaction
Provider call, Risk service และ Ledger posting ไม่สามารถอยู่ใน ACID Aggregate เดียว ให้ model business process, stable identity, idempotency, compensation/reconciliation แยก
Temporal Invariant
บาง rule ขึ้นกับเวลา/policy version:
- eligibility decision ยัง valid ณ confirm หรือไม่
- reservation หมดอายุแล้วหรือยัง
- cancellation request มาก่อน irreversible evidence หรือไม่
ส่ง Clock/Policy reference ที่ testable เข้าพฤติกรรม แต่อย่าให้ Aggregate query network
Aggregate Design Canvas
DDD Crew Aggregate Design Canvas ช่วยถาม:
Business decision:
Commands:
Invariants:
Information required:
Entities / Values:
Concurrency conflicts:
Events / outcomes:
Rejected alternatives:
Canvas เป็น exploration ไม่บังคับใช้ Aggregate หาก Transaction Script/constraint พอ
Common Smells
- Aggregate ต่อ table
- root มี collections ไม่จำกัด
- method รับ repositories/HTTP client
- event ทุก field change
- cross-context object อยู่ใน Aggregate
- setter เปิด bypass invariant
- save ไม่มี concurrency contract
แบบฝึกปฏิบัติ: Reserve Funds
ออกแบบ 2 alternatives:
- Wallet Aggregate ถือ reservations collection
- Reservation เป็น Aggregate แยกพร้อม funds authority constraint
ทดสอบ concurrent reserve, release-vs-consume, duplicate command, expiration และ stale display balance ส่ง invariant/transaction/concurrency table และเหตุผลเลือก
Available Funds Policy มี Owner
สูตร available/overdraft/hold priority ต้องมาจาก responsible Product/Finance/Risk owner พร้อม version/date ตัวอย่างใน lab สอน boundary ไม่กำหนด policy จริง
รายการตรวจสอบ
- Aggregate เริ่มจาก invariant
- Root คุม state changes
- Boundary เล็กพอ transaction/concurrency
- References ข้าม Aggregate ใช้ identity
- Persistence มี optimistic/pessimistic contract
- Cross-context process ไม่ถูกยัดใน Aggregate
- Temporal policy/version ถูก model
- Alternative ถูก stress-test
สรุปบทนี้
Aggregate ปกป้อง consistency ที่ต้องทันทีผ่าน root และ transaction boundary ไม่ใช่ graph ของทุกสิ่ง Object model ต้องทำงานร่วมกับ database concurrency ส่วน process ข้าม boundary ต้องใช้กลไก reliability และ business recovery อื่น
อ่านเพิ่มเติม
- DDD Aggregate — Martin Fowler
- Aggregate Design Canvas — DDD Crew
- Azure Tactical DDD — aggregate/invariant guidance