บทที่ 18 · Part 4 — Boundaries, Data and Integration
Persistence and Data Boundaries
แยก domain, persistence, write/read model และ cache โดยใช้ MySQL กับ Redis ตาม authoritative responsibility
Persistence and Data Boundaries
ระบบแสดง Wallet balance จาก Redis ได้เร็ว ทีมจึงนำค่าเดียวกันไปอนุมัติ Transfer ช่วง cache lag ผู้ใช้โอนเกิน available balance อีกด้านหนึ่ง Repository คืน ORM model ที่มีทุก column จน domain, API และ cache ใช้ type เดียวกัน Data architecture ต้องบอก ความหมายและ authority ของแต่ละ representation ก่อนเลือก datastore
จบบทนี้คุณจะ
แยก domain, persistence, write, read model และ cache วาง Repository/Data Mapper เมื่อ ปกป้อง complexity จริง ออกแบบ MySQL transaction/concurrency กับ Redis derived state และสร้าง data responsibility map สำหรับ financial decision path
[!IMPORTANT] วิธีอ่านหลักฐานในบทนี้ คำที่เป็น Definition/Pattern มีแหล่งต้นทางอยู่ใกล้ claim ส่วน decision table, starting structure, checklist และ lab เป็น Heuristic/Trade-off หรือ Course Convention สำหรับฝึกตัดสินใจ ไม่ใช่มาตรฐานสากล เว้นแต่บทจะระบุแหล่งและขอบเขตไว้ชัดเจน
Model 5 ชนิด
| Model | Purpose | Owner |
|---|---|---|
| Domain model | ตัดสิน rule/invariant | owning business module |
| Persistence model | map schema/row/version | persistence adapter |
| Write model | command/state สำหรับ authoritative change | application/domain boundary |
| Read model | query shape ที่ optimize การอ่าน | query owner |
| Cache | สำเนา derived ที่มี freshness/eviction | cache adapter/consumer |
type อาจเหมือนกันใน module เล็กได้ แต่ต้องเป็น deliberate choice อย่า share เพราะขี้เกียจ map Field ที่อ่านเพื่อ display ไม่ควรเปิด method เปลี่ยน domain state
MySQL ในบทบาท Authoritative Store
MySQL เหมาะเป็นตัวอย่าง relational authority ด้วย transaction, constraints และ locking แต่ต้องออกแบบตาม schema/engine/version จริง:
- primary/unique key สำหรับ identity/idempotency
- foreign/check constraints เท่าที่ invariant อยู่ใน row/relation ที่ database enforce ได้
BEGIN/COMMIT/ROLLBACKสำหรับ atomic local state change- optimistic version หรือ locking read ตาม contention/failure model
- tenant ID ใน key/query และ DB grants/schema separation ตาม risk
- migration/backfill plan ที่ preserve old/new application compatibility
ตัวอย่าง idempotent credit:
CREATE TABLE wallet_credits (
credit_id VARCHAR(40) PRIMARY KEY,
wallet_id VARCHAR(40) NOT NULL,
operation_id VARCHAR(80) NOT NULL,
amount_minor BIGINT NOT NULL,
currency CHAR(3) NOT NULL,
UNIQUE KEY uq_wallet_operation (wallet_id, operation_id),
CHECK (amount_minor > 0)
);
จำนวนเงินจริงใช้ integer minor units แต่ currency metadata/precision policy ต้องยืนยัน Unique key ทำให้ retry logical operation ไม่ insert effect ซ้ำ ส่วน application map duplicate เป็น outcome เดิมหรือ conflict ตาม command contract
ขอบเขตของ Transaction และ Concurrency
Go transaction guide แนะนำใช้ sql.Tx
และหลีกเลี่ยงการเรียก non-transaction sql.DB ปนใน flow เดียว MySQL
locking reads มี semantics
ตาม isolation/index/query shape จึงต้อง test กับ engine/version จริง
เลือก:
- Optimistic version เมื่อ conflict ไม่บ่อยและ retry/reload ชัด
- Pessimistic lock เมื่อ serialization จำเป็นและ transaction สั้น
- Unique/check constraint สำหรับ invariant ที่ database แสดงได้
- Ledger/accounting design เฉพาะเมื่อมี authoritative requirements ไม่ใช่เพราะ DDD
Domain invariant ต้องมี storage-level partner
Method ApproveRefund ป้องกัน rule ใน memory แต่ concurrent transactions อาจอ่าน
version เดียวกัน ต้องใช้ version/lock/constraint และ concurrent integration test
ห้ามถือ Aggregate เป็น concurrency mechanism ด้วยตัวเอง
Redis ในบทบาท Derived State ที่สร้างใหม่ได้
Redis cache-aside ให้ application อ่าน cache แล้ว fallback source-of-truth ก่อน populate Redis ในคอร์สนี้ Redis เป็นตัวอย่าง derived/replaceable state:
Key: portal:wallet-summary:{tenant}:{wallet}
Value: posted/available display fields + sourceVersion + generatedAt
TTL: chosen from freshness requirement
Source: Wallet query contract
Rebuild: query owner and repopulate
ต้องออกแบบ cache miss, stale read, invalidation, stampede, key namespace, tenant isolation, serialization version และ outage behavior ถ้า Redis ล่ม decision path ไม่ควร fail-open ด้วย stale value
Redis ทำ authoritative state ได้ในบางระบบ แต่ต้องพิสูจน์ durability/consistency/operations ตาม design นั้น ไม่ใช่ claim ของคอร์สนี้ Default teaching stance ทำให้ cache delete/rebuild ได้
แยก Decision Path จาก Display Path
Portal แสดงข้อมูลพร้อม asOf/freshness ได้ แต่ command ต้องส่ง Wallet ID/amount ไม่ใช่
“balance ที่ browser เห็น” เพื่อให้ owner re-read และตรวจ invariant
แบบฝึกปฏิบัติ: Data Responsibility Map
กรอกต่อ data set:
Name:
Owner module/context:
Authoritative source:
Write path and transaction:
Concurrency rule:
Read/display copies:
Freshness and invalidation:
Rebuild procedure:
Allowed decision uses:
Tenant/security controls:
Tests and operator evidence:
ทำสำหรับ Charge, Refund, Wallet Summary, Invoice View และ idempotency record ทดสอบ cache outage, concurrent refund และ retry credit operation
รายการตรวจสอบ
- Domain/persistence/read/cache models มี purpose แยก
- Authoritative source และ owner ระบุชัด
- MySQL constraints/transaction/concurrency match invariant
- Tenant scope enforce ใน query/key/test
- Redis state มี TTL/version/rebuild/outage behavior
- Display state ไม่เข้า financial decision โดยเงียบ ๆ
- Repository ปกป้อง complexity แทนการซ่อน datastore capability ทั้งหมด
สรุปบทนี้
Data boundary ไม่ใช่การห่อ SQL ด้วย Repository อย่างเดียว ต้องแยก authority, purpose, freshness และ concurrency MySQL แสดง authoritative transaction/constraints ส่วน Redis แสดง derived read/cache การตัดสินเงินต้องกลับไป owning context และ state ที่พิสูจน์ได้