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

Services, Policies and Specifications

วาง business decision ที่ไม่เข้ากับ Entity ใด พร้อมแยก Domain Service จาก application orchestration

Services, Policies and Specifications

เมื่อทีมไม่รู้ว่าวาง rule ที่ไหน มักสร้าง TransferService ซึ่ง validate, load database, call provider, คำนวณ fee, ตรวจ risk และส่ง notification จนชื่อ Service ไม่บอก model DDD แยก Domain Service, Application Service, Policy และ Specification ตามชนิดของความรู้ ไม่ใช่ suffix เดียว

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

แยก domain operation ที่ไม่เข้ากับ Entity/Value Object จาก application orchestration model policy ที่เปลี่ยนตาม owner/version และใช้ Specification เมื่อ predicate มีชื่อ/reuse/composition จริง โดยไม่สร้าง service class ครอบทุก logic

Domain Service

Domain Service เป็น stateless business operation ที่เป็นส่วนของ Ubiquitous Language แต่ไม่เป็น responsibility ธรรมชาติของ Entity/Value เดียว เช่นการเลือก route จาก candidate providers ภายใต้ cost/reliability policy

type RouteSelector struct{}

func (RouteSelector) Select(policy RoutingPolicy, candidates []ProviderQuote) (Route, error) {
	// pure domain decision; no HTTP/database
}

ถ้า operation เป็นเพียง CRUD orchestration หรือ logging ไม่ใช่ Domain Service

Application Service

Application Service ประสาน use case:

authorize principal
load aggregates/facts
invoke domain decision
save transactionally
call/publish through ports after correct boundary
translate result to caller

มันมี error/transaction/idempotency responsibility แต่ไม่ควรถือ business rule ที่ Domain Expert ต้องพูดถึง Microsoft แยก Domain/Application Services ใน Azure Tactical DDD

Policy เป็น First-class Model

Policy encapsulates decision ที่อาจเปลี่ยนตาม strategy, product, jurisdiction หรือเวลา:

type EligibilityPolicy struct {
	version       string
	effectiveFrom time.Time
	jurisdiction  string
}

Behavior อาจอยู่ใน policy object หรือใช้ policy facts กับ service สำคัญคือ provenance/owner

Engineer ไม่เป็น Policy Authority โดยค่าเริ่มต้น

transfer limits, KYC, retention และ approval thresholds ต้องมาจาก accountable owner/regulator พร้อม source/version/jurisdiction/effective date Code ต้อง trace version ไม่สร้างเลขเอง

Specification

Specification ตั้งชื่อ business predicate ที่ reuse/compose และอธิบายได้ เช่น TransferRequiresManualReview หรือ BeneficiaryIsEligible

ใช้เมื่อ:

  • predicate เป็น Ubiquitous Language
  • หลาย behavior ต้องใช้เหมือนกัน
  • composition AND/OR/NOT เพิ่มความชัด
  • rule ต้อง test independently

ไม่ใช้ห่อทุก boolean เช่น AmountIsPositiveSpecification ถ้า Money constructor พอ

Domain Predicate กับ Database Query

Specification ใน model กับ query filter ไม่จำเป็นต้อง object เดียว Domain predicate อาจใช้ rich values ขณะที่ database query optimize index ถ้าฝืนให้ Specification สร้าง SQL persistence concern อาจรั่วเข้ามา

แยก:

Domain: TransferRequiresReview(policy, transfer)
Query:  ReviewQueue.FindPending(criteria)

และมี tests เพื่อให้ semantics สอดคล้องเท่าที่ contract ต้องการ

วาง Rule ด้วย Responsibility Test

ถาม:

  1. Rule ปกป้อง invariant ของ Entity/Aggregate เดียวหรือไม่ → method
  2. เป็น calculation/decision ข้าม Values โดยไม่มี natural owner หรือไม่ → Domain Service
  3. เปลี่ยนตาม named business policy/version หรือไม่ → Policy
  4. เป็น predicate ที่ reuse/compose ใน language หรือไม่ → Specification
  5. เป็นการประสาน I/O/transaction/actor หรือไม่ → Application Service

แบบฝึกปฏิบัติ: Eligibility Decision

ออกแบบ:

Application Service: Assess Transfer Eligibility
Domain facts:
Policy object + provenance:
Domain Service, if justified:
Specifications:
Aggregate behavior:
External facts and ACL:
Decision event:

ทดสอบ policy version เปลี่ยน, decision expiry, missing evidence, different jurisdiction และ replay ว่า result เดิมอธิบายได้ด้วย policy version ใด

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

  • Domain Service เป็น business operation แบบ stateless
  • Application Service orchestration ไม่มี hidden domain rules
  • Policy มี owner/source/version/time/context
  • Specification มี language/reuse/composition value
  • Database query ไม่ถูกบังคับใช้ domain object เดียว
  • Rule อยู่ใกล้ concept ที่รับผิดชอบที่สุด
  • Generic SomethingService ถูกท้าทาย

สรุปบทนี้

Service, Policy และ Specification แยกชนิดความรู้ที่มักปนใน service class ใหญ่ Domain Service ถือ operation ที่เป็นภาษา Application Service ประสาน use case Policy ทำ decision provenance ชัด และ Specification ตั้งชื่อ predicate ที่มีคุณค่าจริง

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