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 ฯลฯ — สองมิตินี้ต้องสอดคล้องกันเสมอ ไม่ใช่แค่คล้ายกัน
บทบาทใน DDD
หัวข้อที่มีชื่อว่า “บทบาทใน DDD”วิวัฒนาการของ 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
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- ที่มา · deviq.com/domain-driven-design/domain-model
- Domain Model — Martin Fowler, Patterns of Enterprise Application Architecture
- Domain-Driven Design: Tackling Complexity in the Heart of Software — Eric Evans
- Implementing a microservice domain model with .NET — Microsoft Learn
- Implementing Domain-Driven Design — Vaughn Vernon (dddcommunity.org)
- bliki: Domain-Driven Design — Martin Fowler