บทที่ 21 · Part 4 — Model Behavior in Code

Factories, Repositories and Modules

ใช้ creation, persistence และ module boundaries เพื่อปกป้อง model โดยไม่สร้าง abstraction ครบชุดทุกจุด

Factories, Repositories and Modules

Tactical DDD มักถูกสอนเป็นรายการให้สร้าง Factory, Repository และ Module ให้ครบ แต่แต่ละ pattern มีแรงเฉพาะ Factory ปกป้องการสร้างที่ซับซ้อน Repository ให้ illusion ของ collection สำหรับ Aggregate และ Module จัด model ตาม concept หาก constructor/SQL/package เดิมชัดอยู่แล้ว abstraction เพิ่มอาจไม่คุ้ม

จบบทนี้คุณจะ

ใช้ Factory เมื่อ creation มี invariant/meaningful process ออกแบบ Repository ตาม Aggregate จาก consumer side แยก write model จาก read query และจัด Module ด้วย Ubiquitous Language พร้อมรู้ เมื่อใดไม่ควรใช้ pattern

Factory

Factory มีค่าหากการสร้าง valid Aggregate:

  • ต้องคำนวณ/เลือก subtype
  • มีหลาย objects ที่ต้องสัมพันธ์ถูก
  • ต้อง snapshot policy/terms
  • constructor ยาวจนซ่อน business meaning
func OpenTransferIntent(
	id TransferIntentID,
	beneficiary Beneficiary,
	amount Money,
	terms TermsVersion,
) (*TransferIntent, error) {
	// establish invariants or fail
}

ชื่อ Factory operation ใช้ language เช่น Open, Issue, Schedule ไม่ใช่ CreateObjectFactory ถ้า Value Object validate 2 บรรทัด constructor ธรรมดาพอ

Factory ไม่ควร call provider/database เอง Application Service เตรียม facts แล้วส่งเข้า

Repository

Repository ให้ interface คล้าย collection ของ Aggregate และซ่อน mapping:

type TransferIntents interface {
	ByID(ctx context.Context, id TransferIntentID) (*TransferIntent, error)
	Save(ctx context.Context, intent *TransferIntent) error
}

consumer/model/application side เป็นเจ้าของ abstraction ไม่ให้ MySQL method names กำหนด contract Save ต้องระบุ optimistic version/transaction expectations และ adapter มี integration tests จริง

Repository ไม่ใช่ Generic CRUD

หลีกเลี่ยง Repository[T] ที่มี FindAll, UpdateField, Delete เหมือนทุก model เพราะ Aggregate behavior/identity/query ต่างกัน Repository ต่อ Aggregate root ไม่ต่อ table

Read model สำหรับ Portal อาจใช้:

type TransferHistoryQueries interface {
	ListForCustomer(ctx context.Context, customerID string, page Page) ([]TransferRow, error)
}

ไม่ต้อง hydrate Aggregates เพื่อ display

Unit of Work และ Transaction

Repository หลายตัวใน local transaction อาจประสานด้วย transaction/application boundary แต่ business process ข้าม provider/contexts ไม่ใช่ database Unit of Work ใช้ outbox/reconciliation ในบทต่อไป

Repository ไม่ทำให้ Data Authority ชัดเอง

ต้องมี context ownership, database grants/query scope และ prohibition ต่อ cross-context writes Portal repository ที่เขียน Ledger table ยังละเมิด boundary แม้ interface สวย

Module

Module จัด concepts ให้ cohesive และ language บอกเรื่อง:

transferintent/
eligibility/
fundsreservation/
paymentexecution/
reconciliation/

ดีกว่า models/, services/, helpers/ เมื่อ outer structure ต้องสื่อ capability แต่ไม่ใช่กฎว่า ทุก context ต้อง package เหมือนกัน Module boundary ดูจาก public API/import/change locality ไม่ใช่ folder name เพียงอย่างเดียว

Choosing Table

ForcePatternOmit when
valid creation ซับซ้อนFactoryconstructor ชัด
Aggregate/persistence decouplingRepositorysimple CRUD/query และ model ไม่ได้ต้องปกป้อง
atomic local changesUnit of Worktransaction เดียวตรงไปตรงมา
conceptual cohesionModulesplitting ทำให้ navigation แย่กว่าเดิม

แบบฝึกปฏิบัติ: Transfer Persistence Boundary

ส่ง:

  • Factory ของ Transfer Intent และ invariants ที่ establish
  • Repository interface พร้อม concurrency semantics
  • separate history query interface
  • module tree ด้วย domain names
  • SQL adapter test cases: not found, version conflict, transaction rollback
  • patterns deliberately omitted พร้อมเหตุผล

รายการตรวจสอบ

  • Factory สร้าง valid object หรือ fail
  • Creation ไม่มี hidden I/O
  • Repository ตาม Aggregate และ consumer-owned
  • Save มี transaction/concurrency contract
  • Read query ไม่ hydrate Aggregate โดยไม่จำเป็น
  • Module ใช้ Ubiquitous Language
  • Pattern ที่ไม่คุ้มถูกละไว้

สรุปบทนี้

Factory, Repository และ Module ช่วย creation, persistence illusion และ conceptual cohesion แต่ไม่ใช่เครื่องหมายว่าทำ DDD ครบ เลือกเมื่อปกป้อง model/meaning ได้จริงและคง datastore/query capabilities ที่สำคัญไว้

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