ข้าม​ไป​ยัง​เนื้อหา

Domain Model

ภาพ​แทน​เชิง​แนวคิด​ของ​หัวใจ domain ที่​ทีม​และ​ผู้เชี่ยวชาญ​เข้าใจ​ร่วม​กัน

Domain model ใน DDD คือ ภาพ​แทน​เชิง​แนวคิด​ของ​แนวคิด​หลัก entity และ​ความ​สัมพันธ์ ภายใน domain หนึ่ง ๆ มัน​ทำ​หน้าที่​เป็น​ความ​เข้าใจ​ร่วม​ระหว่าง​ผู้เชี่ยวชาญ domain กับ​นัก​พัฒนา ช่วย​การ​สื่อสาร​และ​การ​ทำงาน​ร่วม​กัน domain model เป็น เอกสาร​ที่​มี​ชีวิต และ​ควร​วิวัฒน์​ต่อ​เนื่อง​เมื่อ​ความ​เข้าใจ domain ของ​เรา​ลึก​ขึ้น

Eric Evans นิยาม model (model) ไว้​ใน​หนังสือ DDD ว่า​เป็น “ระบบ​ของ​นามธรรม (abstractions) ที่​อธิบาย​แง่​มุม​ที่​เลือก​มา​ของ domain และ​ใช้​แก้​ปัญหา​ที่​เกี่ยวข้อง​กับ domain นั้น​ได้” model จึง​ไม่ใช่​ตัว domain จริง​ทั้งหมด แต่​เป็น การ​ตีความ ที่​ตัด​รายละเอียด​ที่​ไม่​เกี่ยวข้อง​ออก​ไป และ​เก็บ​เฉพาะ​สิ่ง​ที่​จำเป็นต่อ​การ​แก้​ปัญหา​ทาง​ธุรกิจ​ที่​กำลัง​พิจารณา​อยู่ model ที่​ดี​จึง​ไม่ใช่​แผนภาพ​หรือ​เอกสาร​ที่​ถูกต้อง​ตาม​ความ​เป็น​จริง​มาก​ที่สุด แต่​เป็น model ที่ มี​ประโยชน์ ที่สุด​สำหรับ​ปัญหา​ที่​กำลัง​แก้

Martin Fowler ให้​นิยาม​ใน​เชิง​สถาปัตยกรรม​ซอฟต์แวร์​ไว้​ใน Patterns of Enterprise Application Architecture ว่า Domain Model pattern คือ “an object model of the domain that incorporates both behavior and data” — model เชิง​วัตถุ​ของ domain ที่​รวม​ทั้ง​พฤติกรรม (behavior) และ​ข้อมูล (data) เข้า​ไว้​ด้วย​กัน​ใน​ที่​เดียว ตรง​ข้าม​กับ​แนวทาง Transaction Script ที่​แยก​ตรรกะ​ธุรกิจ​ออก​จาก​ข้อมูล​แบบ​เดิม ๆ Fowler เปรียบเทียบ Domain Model กับ Table Module ไว้​ชัดเจน: ถ้า​มี order จำนวน​มาก Domain Model จะมี1 object ต่อ1 order ใน​ขณะ​ที่ Table Module จะมี object เดียว​จัดการ order ทั้งหมด

สรุป​ได้​ว่า domain model ใน​ความหมาย​ของ DDD มี​สอง​มิติ​ที่​ทำงาน​ร่วม​กัน: (1) เป็นแนวคิด​ร่วม ที่​ทีม​และ​ผู้เชี่ยวชาญ domain พูด​คุย​และ​ตกลง​กัน (สอดคล้อง​กับ Ubiquitous Language) และ (2) เป็นโครงสร้าง code จริง​ที่ implement แนวคิด​นั้น​ด้วย entity, value object, aggregate ฯลฯ — สอง​มิติ​นี้​ต้อง​สอดคล้อง​กัน​เสมอ ไม่ใช่​แค่​คล้าย​กัน

วิวัฒนาการ​ของ domain model เกิด​ใน 2 เฟส: strategic design เปรียบ​เหมือน​การ​สร้าง​พิมพ์เขียว (การ​สนทนา​สร้าง model เชิง​แนวคิด) และ tactical design คือ​การ implement พิมพ์เขียว​นั้น​พร้อม​รายละเอียด​ทั้งหมด ทั้ง​สอง​เฟส​เป็น iterative และ​ควร​ถูก​ขัดเกลา​ต่อ​เนื่อง​เมื่อ​เรียนรู้ domain มาก​ขึ้น

ใน​เชิง​ปฏิบัติ domain model ทำ​หน้าที่​เป็น​จุด​ตัด​กลาง​ของ DDD หลาย​แนวคิด:

  • model ถูกสร้าง​และ​กลั่นกรองผ่าน​การ​สนทนา​กับ​ผู้เชี่ยวชาญ domain โดย​ใช้ Ubiquitous Language เป็น​ภาษา​กลาง — คำ​ศัพท์​ใน code ต้อง​ตรง​กับ​คำ​ศัพท์​ที่​ผู้เชี่ยวชาญ​ใช้​จริง ไม่ใช่​ศัพท์​เทคนิค​ที่​นัก​พัฒนา​คิด​ขึ้น​เอง
  • model มี​ขอบเขต​ชัดเจน​อยู่​ภายใน Bounded Context หนึ่ง ๆ คำ​เดียวกัน (เช่น “Order”) อาจ​มี​ความหมาย​และ​โครงสร้าง​ต่าง​กัน​ใน​แต่ละ context — เพราะ​แต่ละ model ถูก​ออกแบบ​มา​แก้​ปัญหา​เฉพาะ​ของ context นั้น
  • ใน​ระดับ tactical model ถูก implement ด้วย building block เช่น Entity (มี​เอกลักษณ์​และ​วงจร​ชีวิต), Value Object (นิยาม​ด้วย​แอตทริบิวต์ ไม่มี​เอกลักษณ์) และ Aggregate (กลุ่ม entity/value object ที่​รักษา​ความ​สอดคล้อง​ร่วม​กัน​ผ่าน aggregate root)
  • Microsoft Learn เน้น​ว่า model domain ที่​ดี​ควร​เป็น POCO (Plain Old CLR Object) ที่​ไม่มี dependency ต่อ ORM หรือ infrastructure ใด ๆ — ตรรกะ​ทาง​ธุรกิจ​ต้อง​อยู่​ใน model ไม่ใช่​กระจาย​อยู่​ใน application layer หรือ command handler
  • Vaughn Vernon เน้น​ย้ำ​ว่า model ที่​ดี​ต้อง​เป็น rich domain model ที่​มี​พฤติกรรม (method ที่​บังคับ invariant) ไม่ใช่ Anemic Model ที่​มี​แต่ getter/setter — model แบบ rich มัก​มี code โดย​รวม น้อย​กว่า และ​ทดสอบ​ได้​ง่าย​กว่า model แบบ anemic เพราะ​พฤติกรรม​ถูก​ห่อ​หุ้ม​ไว้​ใน​ที่​เดียว ทดสอบ​ผ่าน public method ได้​ตรง​ไป​ตรง​มา

แผนภาพ​ต่อ​ไป​นี้​แสดง​ว่า domain model เป็น​ศูนย์กลาง​ที่​เชื่อม ubiquitous language เข้า​กับ building block เชิง tactical:

graph TD
  UbiquitousLanguage --> DomainModel
  DomainModel --> Entity
  DomainModel --> ValueObject
  DomainModel --> DomainService
  DomainModel --> DomainEvent
  DomainModel --> Aggregate
  Aggregate -->|ประกอบด้วย| Entity
  Aggregate -->|ประกอบด้วย| ValueObject

สมมติ domain ของ​ระบบ​สั่ง​ซื้อ​สินค้า (ordering) ผู้เชี่ยวชาญ domain อธิบาย​ว่า “order หนึ่ง​ใบ​ต้อง​มี address สำหรับ​จัด​ส่ง และ​สามารถ​เพิ่ม​รายการ​สินค้า​ได้ ตราบ​ใด​ที่ order ยัง​ไม่​ถูก​ยืนยัน (submitted)” — ประโยค​นี้​แหละ​คือ ubiquitous language ที่​ต้อง​แปลง​เป็น model โดยตรง ไม่ใช่​แค่​เป็น comment:

// Order คือ Entity และเป็น Aggregate Root ของ order aggregate
// สังเกตว่าไม่มี public setter ใด ๆ — การเปลี่ยนแปลงทุกอย่างต้องผ่าน method
// ที่สื่อความหมายทางธุรกิจ (ubiquitous language) เท่านั้น
public class Order
{
private readonly List<OrderItem> _orderItems = new();
public int Id { get; private set; }
public OrderStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
public IReadOnlyCollection<OrderItem> Items => _orderItems.AsReadOnly();
private Order() { } // สำหรับ ORM เท่านั้น
public static Order Create(Address shippingAddress)
{
if (shippingAddress is null)
throw new ArgumentNullException(nameof(shippingAddress));
return new Order
{
Status = OrderStatus.Draft,
ShippingAddress = shippingAddress
};
}
// Method นี้คือหัวใจของ rich domain model:
// ตรรกะทางธุรกิจ (invariant) ถูกบังคับใช้ในที่เดียว ไม่กระจายไปที่ application layer
public void AddItem(string productId, string productName, decimal unitPrice, int quantity)
{
if (Status != OrderStatus.Draft)
throw new InvalidOperationException("ไม่สามารถเพิ่มสินค้าได้เมื่อ order ถูกยืนยันแล้ว");
var existing = _orderItems.SingleOrDefault(i => i.ProductId == productId);
if (existing is not null)
{
existing.IncreaseQuantity(quantity);
return;
}
_orderItems.Add(new OrderItem(productId, productName, unitPrice, quantity));
}
public void Submit()
{
if (!_orderItems.Any())
throw new InvalidOperationException("ไม่สามารถยืนยัน order ที่ยังไม่มีสินค้า");
Status = OrderStatus.Submitted;
}
}
// Address เป็น Value Object — เท่ากันด้วยค่า ไม่มีเอกลักษณ์ของตัวเอง
public record Address(string Street, string City, string PostalCode, string Country);
public enum OrderStatus { Draft, Submitted, Shipped, Cancelled }

code นี้​แสดง domain model จริง​ของ “ordering” ที่​ผูก​กับ ubiquitous language (“Submit”, “AddItem”, “Draft”) โดยตรง ตรง​ข้าม​กับ model แบบ anemic ที่​จะ​มี​แต่ public List<OrderItem> Items { get; set; } แล้ว​ปล่อย​ให้ application layer เขียน​ตรรกะ “ห้าม​เพิ่มสินค้า​หลัง​ยืนยัน​แล้ว” กระจัดกระจาย​อยู่​หลาย​ที่ — ซึ่ง​เสี่ยง​ต่อ​ความ​ไม่​สอดคล้อง​ของ​ข้อมูล​และ​ซ้ำ code โดย​ไม่​จำเป็น

  • Ubiquitous Language เป็น​วัตถุดิบ​ที่​ใช้​สร้าง model — คำ​นาม​และ​กริยา​ใน​ภาษา​นี้ (Order, Submit, ShippingAddress) ควร​ปรากฏ​ตรง ๆ ใน​ชื่อ class และ method ของ model
  • Bounded Context เป็นกรอบ​ที่​จำกัด​ขอบเขต​ความ​ถูกต้อง​ของ model model เดียวกัน​ไม่​จำเป็น​ต้อง​ใช้ได้​กับ​ทุก context; แต่ละ context มี​สิทธิ์​มี model “Order” ของ​ตัวเอง​ที่​ต่าง​กัน
  • Entity, Value Object, Aggregate คือ​หน่วย​ก่อสร้าง (building block) ระดับ tactical ที่​ประกอบ​กัน​เป็น domain model — model ไม่ใช่ class เดียว แต่​เป็น​เครือข่าย​ของ object เหล่า​นี้​ที่​ทำงาน​ร่วม​กัน
  • Anemic Domain Model คือ​ภาวะ​ตรง​ข้าม​ที่​ควร​หลีก​เลี่ยง: เมื่อ model มี​แต่ data ไม่มี behavior ตรรกะ​ทาง​ธุรกิจ​จะ​รั่วไหล​ไป​อยู่​นอก model ทำให้​เสีย​จุด​ประสงค์​หลัก​ของ DDD ไป
  • Domain Service (ตรรกะ​ที่​ไม่​เหมาะสม​กับ entity/value object ใด​ตัว​หนึ่ง​เป็นการ​เฉพาะ) และ Domain Event (การ​ประกาศ​ว่า​มี​บาง​อย่าง​เกิด​ขึ้น​ใน domain) เป็น​ส่วน​เสริม​ของ model เมื่อ​พฤติกรรม​ข้าม​หลาย aggregate