บทที่ 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:

  1. Wallet Aggregate ถือ reservations collection
  2. 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 อื่น

อ่านเพิ่มเติม