Entity vs Value Object – อะไรควรมีตัวตนในระบบพนักงาน

ตอนที่ 4 ของซีรีส์ Domain-Driven Design ฉบับระบบธุรกิจจริง

หลังจากตอนที่ 3 เราได้ภาษาเดียวกันระหว่างโค้ดกับธุรกิจแล้ว

ตอนนี้ถึงเวลาลงมือ สร้าง Domain Model จริง ๆ

คำถามแรกที่สำคัญมากคือ:

สิ่งนี้ในระบบ ควรเป็น Entity หรือ Value Object ?

ถ้าตัดสินใจผิดตั้งแต่ตรงนี้ Domain จะเริ่มบิดทันที


ความเข้าใจผิดที่พบบ่อย

นักพัฒนาระบบธุรกิจจำนวนมากมักคิดว่า:

“อะไรก็ตามที่มาจาก database = Entity”

ผลลัพธ์คือ:

  • ทุกอย่างมี id
  • ทุกอย่างแก้ค่าได้หมด
  • Domain กลายเป็นถุงข้อมูล (Anemic Model)

DDD แยกสิ่งเหล่านี้ออกชัดเจนกว่า


Entity คืออะไร

Entity คือสิ่งที่:

  • มีตัวตนเฉพาะ (identity)
  • เปลี่ยนค่าได้ แต่ยังเป็น “สิ่งเดิม”
  • ธุรกิจสนใจว่า เป็นใคร มากกว่า มีค่าอะไร

ในระบบพนักงาน ตัวอย่าง Entity ได้แก่:

  • Staff
  • ShiftRequest
  • Department

แม้ข้อมูลจะเปลี่ยน แต่ identity ยังเดิม


ตัวอย่าง Entity: Staff

class Staff {
  final StaffId id;
  String name;
  StaffStatus status;

  void resign() {
    if (status == StaffStatus.resigned) {
      throw Exception('Staff already resigned');
    }
    status = StaffStatus.resigned;
  }
}

ประเด็นสำคัญ:

  • id คือหัวใจของ entity
  • behavior อยู่กับ entity
  • entity ปกป้องกฎของตัวเอง

Value Object คืออะไร

Value Object คือสิ่งที่:

  • ไม่มี identity
  • วัดค่าด้วย “ความเท่ากันของข้อมูล”
  • มักเป็น immutable

ในระบบพนักงาน ตัวอย่าง Value Object ได้แก่:

  • EmailAddress
  • PhoneNumber
  • WorkingPeriod
  • ShiftTime

ถ้าค่าเหมือนกัน = ถือว่าเหมือนกัน


ตัวอย่าง Value Object: WorkingPeriod

class WorkingPeriod {
  final DateTime start;
  final DateTime end;

  WorkingPeriod(this.start, this.end) {
    if (!end.isAfter(start)) {
      throw Exception('End must be after start');
    }
  }

  bool overlaps(WorkingPeriod other) {
    return start.isBefore(other.end) && end.isAfter(other.start);
  }
}

จุดสังเกต:

  • ไม่มี id
  • สร้างแล้วแก้ไม่ได้
  • กฎอยู่ในตัวมันเอง

ทำไม Value Object ถึงสำคัญกับระบบธุรกิจ

ถ้าคุณไม่ใช้ Value Object:

  • validation จะกระจัดกระจาย
  • logic ซ้ำ ๆ เต็มระบบ
  • test ยาก

Value Object ทำให้:

  • กฎถูกเขียนครั้งเดียว
  • intent ชัดเจน
  • domain อ่านง่ายขึ้นมาก

Entity + Value Object ทำงานร่วมกัน

Entity ไม่ควรเก็บ primitive ดิบ ๆ เต็มไปหมด แต่ให้ใช้ชนิดข้อมูลที่เป็น value object มาทำงานร่วมกัน

แทนที่จะเขียน:

String email;
DateTime shiftStart;
DateTime shiftEnd;

ให้เขียน:

EmailAddress email;
WorkingPeriod shiftPeriod;

ตอนนี้ domain:

  • ปลอดภัยขึ้น เพราะ ค่า email หรือ shiftPeriod จะถูกตรวจสอบความถูกต้องของข้อมูลจาก value object ซึ่งก็คือ class EmailAddress และ WorkingPeriod เป็นต้น
  • อ่านเหมือนภาษาธุรกิจ
  • ลด bug เชิงกฎ

Checklist ตัดสินใจ: Entity หรือ Value Object

ถามตัวเองทีละข้อ:

  1. ธุรกิจสนใจ identity ของมันไหม?
  2. ถ้าข้อมูลเหมือนกัน ถือว่าเป็นอันเดียวกันได้ไหม?
  3. มันควรถูกแก้ไข หรือควรถูกแทนที่?

ถ้า:

  • สนใจ identity → Entity
  • สนใจค่า → Value Object

สรุปตอนที่ 4

  • Entity = ตัวตน + behavior
  • Value Object = ค่า + กฎ + immutability
  • ใช้ให้ถูกตั้งแต่ต้น = domain แข็งแรง

ถ้าทุกอย่างในระบบเป็น Entity

แปลว่าคุณยังไม่ได้ออกแบบ Domain จริง ๆ


ตอนต่อไป

ตอนที่ 5: Aggregate & Aggregate Root – กำแพงป้องกัน Domain ไม่ให้เละ

เราจะเริ่มควบคุมขอบเขตของการแก้ไข และพูดถึง invariants ของระบบพนักงาน