ตอนที่ 6 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง
Theme หลัก: ระบบบริหารพนักงาน (Staff / Employee Management System)
ในตอนที่ 5 เราใช้ Aggregate เพื่อกำหนดขอบเขตและปกป้อง invariant ของ domain
แต่เมื่อเริ่มออกแบบจริง คุณจะเจอสถานการณ์แบบนี้:
logic นี้เป็นกฎของธุรกิจแน่ ๆ … แต่ไม่รู้จะใส่ไว้ใน entity ตัวไหนดี
นี่คือจุดที่ Domain Service เข้ามามีบทบาท
ปัญหาที่เจอบ่อยในระบบพนักงาน
ลองดู use case ต่อไปนี้:
- พนักงานขอเปลี่ยนกะ
- ระบบต้องตรวจสอบว่า
- ไม่ชนกับกะอื่นของพนักงาน
- ไม่ชนกับกะที่ได้รับอนุมัติแล้ว
- ไม่เกินนโยบายที่ HR กำหนด
คำถามคือ:
- logic นี้ควรอยู่ใน ShiftRequest?
- หรืออยู่ใน Staff?
คำตอบคือ: ไม่ชัดเจนทั้งคู่
ความเข้าใจผิดเกี่ยวกับ Service
ในระบบทั่วไป เรามักเห็น:
- ShiftService
- StaffService
- RequestService
ที่ภายในเต็มไปด้วย if-else
DDD แยก Service ออกเป็น 3 ประเภท:
- Application Service (Use Case)
- Domain Service
- Infrastructure Service
ตอนนี้เราจะโฟกัสที่ข้อ 2
Domain Service คืออะไร
Domain Service คือ:
class ที่รวม logic ทางธุรกิจ
ซึ่ง ไม่เป็นเจ้าของข้อมูลเอง
และ ไม่เหมาะจะอยู่ใน entity ใด entity หนึ่ง
คุณสมบัติสำคัญ:
- อยู่ใน layer ของ domain
- ใช้ภาษาเดียวกับธุรกิจ (Ubiquitous Language)
- ไม่มี state ระยะยาว
ตัวอย่าง Domain Service: ShiftPolicy
class ShiftPolicy {
bool canRequestShift(
Staff staff,
WorkingPeriod period,
List<ShiftRequest> existingRequests,
) {
if (staff.status != StaffStatus.active) return false;
for (final request in existingRequests) {
if (request.period.overlaps(period)) {
return false;
}
}
return true;
}
}
จุดสำคัญ:
- ไม่เก็บข้อมูลเอง
- รับทุกอย่างผ่าน parameter
- สื่อความหมายเชิงธุรกิจ
Domain Service ไม่ใช่ Utility
ข้อผิดพลาดที่พบบ่อยคือ:
class DateUtils {
static bool overlap(DateTime a, DateTime b) {}
}
นี่คือ Technical Utility
แต่ Domain Service:
- ต้องสะท้อนกฎของธุรกิจ
- ชื่อบอก intent ชัดเจน
ShiftPolicy ดีกว่า ShiftHelper
Domain Service ทำงานร่วมกับ Aggregate
Aggregate Root ยังคงเป็นผู้ตัดสินขั้นสุดท้าย
ตัวอย่างใน ShiftRequest:
class ShiftRequest {
void request(
Staff staff,
WorkingPeriod period,
ShiftPolicy policy,
List<ShiftRequest> existing,
) {
if (!policy.canRequestShift(staff, period, existing)) {
throw Exception('Shift request not allowed');
}
// สร้าง request
}
}
Policy ช่วยตัดสิน แต่ aggregate รับผิดชอบ invariant
สัญญาณว่าคุณควรใช้ Domain Service
คุณควรพิจารณา Domain Service เมื่อ:
- logic ใช้หลาย entity
- logic ไม่ belong กับ entity เดียว
- การยัด logic ลง entity ทำให้มันอ้วนเกินไป
แต่ถ้า logic เป็นพฤติกรรมของ entity โดยตรง:
อย่ารีบใช้ Domain Service
สิ่งที่ Domain Service ไม่ควรทำ
- เรียก database
- รู้จัก framework
- มี side effect ภายนอก
Domain Service ควร:
- pure
- test ง่าย
- ย้ายได้โดยไม่กระทบ infrastructure
สรุปตอนที่ 6
- Domain Service = logic ธุรกิจที่ไม่มีตัวตน
- ใช้เมื่อ entity ไม่เหมาะจะรับภาระนั้น
- อย่าใช้แทน entity แบบพร่ำเพรื่อ
ถ้า service ของคุณรู้จัก database
นั่นไม่ใช่ Domain Service
ตอนต่อไป
ตอนที่ 7: Application Service / Use Case – หัวใจของการไหลของระบบ
เราจะประกอบ domain ทั้งหมดเข้าด้วยกัน และดูว่า controller ควรบางแค่ไหน

