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

Tactical Design

เมื่อ​รู้​ขอบเขต​แล้ว เรา​ลงมือ​สร้าง Domain ModelDomain ModelModel ของ Domain ที่​ฝัง​กฎ​และ​กระบวนการ​ทาง​ธุรกิจ​ไว้​ใน code จริง หัวใจ​ของ DDD คือ​การ​พัฒนา domain model ที่ “เข้าใจ​ธุรกิจ​อย่าง​ลึก​ซึ้ง”Strategic Design ที่ “รวย​พฤติกรรม” ด้วย building blocks ของ DDD Fowler จัด​กลุ่ม​หลัก​ไว้​สาม​อย่าง (Evans Classification): EntityEntityอ็อบเจ็กต์​ที่​มี “ตัว​ตน” (identity) ต่อ​เนื่อง​ตาม​เวลา แม้​ค่า​ภายใน​เปลี่ยน ก็​ยัง​เป็น​สิ่ง​เดิม เช่น ลูกค้า, คำ​สั่ง​ซื้อ — แยกแยะ​ด้วย id ไม่ใช่​ด้วย​ค่าTactical Design, Value ObjectValue Objectอ็อบเจ็กต์​ที่​สำคัญ​ที่ “ค่า” ของ​มัน ไม่มี​ตัว​ตน​เฉพาะ สอง​ตัว​ที่​ค่า​เท่า​กัน​ถือว่า​เท่า​กัน ควร​เป็น immutable (เปลี่ยน​ค่า​ไม่​ได้) เช่น เงิน, วัน​ที่, พิกัดTactical Design, และ service

EntityEntityอ็อบเจ็กต์​ที่​มี “ตัว​ตน” (identity) ต่อ​เนื่อง​ตาม​เวลา แม้​ค่า​ภายใน​เปลี่ยน ก็​ยัง​เป็น​สิ่ง​เดิม เช่น ลูกค้า, คำ​สั่ง​ซื้อ — แยกแยะ​ด้วย id ไม่ใช่​ด้วย​ค่าTactical Design คือ​อ็อบเจ็กต์​ที่​มี IdentityIdentity“ตัว​ตน” ที่​ทำให้ Entity เป็น​สิ่ง​เดิม​ตลอด​อายุ แม้​ค่า​เปลี่ยน เช่น เลข​ที่​คำ​สั่ง​ซื้อ หรือ id ที่​ระบบ​สร้าง​ให้Tactical Design ต่อ​เนื่อง​ตาม​เวลา แม้​ค่า​ภายใน​เปลี่ยน ก็​ยัง​เป็น “สิ่ง​เดิม” เช่น ลูกค้า​คน​หนึ่ง​เปลี่ยน​ชื่อ–ที่​อยู่​ได้ แต่​ก็​ยัง​เป็น​ลูกค้า​คน​เดิม เรา​แยกแยะ​ด้วย id ไม่ใช่​ด้วย​ค่า และ​ที่​สำคัญ Entity ต้อง “มี​พฤติกรรม” ไม่ใช่​แค่​เก็บ​ข้อมูล

Value ObjectValue Objectอ็อบเจ็กต์​ที่​สำคัญ​ที่ “ค่า” ของ​มัน ไม่มี​ตัว​ตน​เฉพาะ สอง​ตัว​ที่​ค่า​เท่า​กัน​ถือว่า​เท่า​กัน ควร​เป็น immutable (เปลี่ยน​ค่า​ไม่​ได้) เช่น เงิน, วัน​ที่, พิกัดTactical Design คือ​อ็อบเจ็กต์​ที่​เรา​สนใจ​แค่ “ค่า” ของ​มัน ไม่มี​ตัว​ตน​เฉพาะ สอง​ตัว​ที่​ค่า​เท่า​กัน​ถือว่า​เท่า​กัน เช่น เงิน 100 บาท ก็​คือ 100 บาท ไม่​ว่า​จะ​เป็น​วัตถุ​ไหน กฎ​สำคัญ​คือ​ควร​เป็น ImmutableImmutableเปลี่ยนแปลง​ไม่​ได้​หลัง​สร้าง ถ้า​จะ “เปลี่ยน​ค่า” ต้อง​สร้าง​อ็อบเจ็กต์​ใหม่​แทน หลัก​การ​สำคัญ​ของ Value Object เพื่อ​เลี่ยง bug จาก​การ​ใช้​อ้างอิง​ร่วม​กันTactical Design — ถ้า​จะ​เปลี่ยน​ค่า ให้​สร้าง​ตัว​ใหม่​แทน เพื่อ​เลี่ยง bug จาก​การ​ใช้​อ้างอิง​ร่วม​กัน

Value Object ที่​ดี​จะ​รวม validation ไว้​ใน​ตัว ทำให้ “ค่าที่​ไม่​ถูก​ต้อง” เป็น​ไป​ไม่​ได้​ตั้งแต่​แรก
// Value Object: เทียบกันด้วยค่า, เปลี่ยนแปลงไม่ได้, validate ตอนสร้าง
class Money {
private constructor(
readonly amountSatang: number, // เก็บเป็นสตางค์ (เลขจำนวนเต็ม) เลี่ยงปัญหาทศนิยม
readonly currency: string,
) {}
static of(amountSatang: number, currency: string): Money {
if (!Number.isInteger(amountSatang)) throw new Error('ต้องเป็นจำนวนเต็ม');
if (amountSatang < 0) throw new Error('เงินติดลบไม่ได้');
return new Money(amountSatang, currency);
}
add(other: Money): Money {
if (other.currency !== this.currency) throw new Error('สกุลเงินไม่ตรงกัน');
return Money.of(this.amountSatang + other.amountSatang, this.currency); // คืนตัวใหม่
}
equals(other: Money): boolean { // เท่ากันเมื่อค่าทุกตัวเท่ากัน
return this.amountSatang === other.amountSatang && this.currency === other.currency;
}
}

AggregateAggregateกลุ่ม​ของ Entity และ Value Object ที่​ถูก​มอง​เป็น​หนึ่ง​หน่วย​เดียว​เพื่อ​รักษา​ความ​ถูก​ต้อง​ของ​ข้อมูล มี​ขอบเขต​ชัดเจน และ​เป็น​หน่วย​ของ transactionTactical Design คือ​กลุ่ม​ของ EntityEntityอ็อบเจ็กต์​ที่​มี “ตัว​ตน” (identity) ต่อ​เนื่อง​ตาม​เวลา แม้​ค่า​ภายใน​เปลี่ยน ก็​ยัง​เป็น​สิ่ง​เดิม เช่น ลูกค้า, คำ​สั่ง​ซื้อ — แยกแยะ​ด้วย id ไม่ใช่​ด้วย​ค่าTactical Design และ Value ObjectValue Objectอ็อบเจ็กต์​ที่​สำคัญ​ที่ “ค่า” ของ​มัน ไม่มี​ตัว​ตน​เฉพาะ สอง​ตัว​ที่​ค่า​เท่า​กัน​ถือว่า​เท่า​กัน ควร​เป็น immutable (เปลี่ยน​ค่า​ไม่​ได้) เช่น เงิน, วัน​ที่, พิกัดTactical Design ที่​ถูก​มอง​เป็น “หนึ่ง​หน่วย​เดียว” เพื่อ​รักษา​ความ​ถูก​ต้อง​ของ​ข้อมูล มี Aggregate RootAggregate RootEntity หนึ่ง​ตัว​ที่​เป็น “ประตู​เดียว” เข้า​สู่ Aggregate การ​อ้างอิง​จาก​ภายนอก​ทำได้​กับ root เท่านั้น root จึง​ควบคุม​กฎ​ความ​ถูก​ต้อง​ของ​ทั้ง​กลุ่มTactical Design เป็น​ประตู​เดียว​เข้า​สู่​กลุ่ม การ​อ้างอิง​จาก​ภายนอก​ทำได้​กับ root เท่านั้น Fowler ย้ำ​กฎ​สำคัญ: “transaction ไม่​ควร​ข้าม​ขอบเขต​ของ Aggregate”

Cargo Aggregate — ขอบเขตความถูกต้อง

aggregate
Cargo Aggregate✓ consistent ภายใน 1 transactionCargoAggregate RootRouteSpecificationValue ObjectItineraryValue ObjectDeliveryValue ObjectHandlingEvent — Aggregate แยกHandlingEventAggregate RootLOAD · UNLOAD · CLAIM …by idTracking ID
Aggregate RootValue Objectขอบเขต (boundary)
กฎข้อ 1 & 2: สิ่งที่อยู่ ภายใน ขอบเขต Cargo ต้องถูกต้องตาม invariant พร้อมกันภายใน หนึ่ง transaction — และควรออกแบบให้เล็กที่สุด
ไดอะแกรม Aggregate ของ Cargo — ขอบเขตความถูกต้อง (invariant) ภายในหนึ่ง transaction
สี่​กฎ​การ​ออกแบบ Aggregate (Vaughn Vernon)

1. จำลอง InvariantInvariantกฎ​ทาง​ธุรกิจ​ที่ “ต้อง​เป็น​จริง​เสมอ” ภายใน Aggregate เช่น ยอด​รวม​คำ​สั่ง​ซื้อ​ต้อง​เท่ากับ​ผล​รวม​ของ​รายการ​สินค้า — ต้อง​ถูก​ต้อง​ภายใน transaction เดียวTactical Design ที่แท้​จริง​ไว้​ใน​ขอบเขต​ความ​ถูก​ต้อง — กฎ​ที่​ต้อง​ตรง​กัน​ทันที​ต้อง​อยู่​ใน Aggregate เดียวกัน​และ​ถูก​ต้อง​ภายใน transaction เดียว

2. ออกแบบ Aggregate ให้​เล็ก — จำกัด​ให้​เหลือ root กับ​สิ่ง​จำเป็น​น้อย​ที่สุด Aggregate ใหญ่​จะ “ไม่มี​วัน​เร็ว​หรือ​สเกล​ได้​ดี”

3. อ้างอิง Aggregate อื่น​ด้วย IdentityIdentity“ตัว​ตน” ที่​ทำให้ Entity เป็น​สิ่ง​เดิม​ตลอด​อายุ แม้​ค่า​เปลี่ยน เช่น เลข​ที่​คำ​สั่ง​ซื้อ หรือ id ที่​ระบบ​สร้าง​ให้Tactical Design — เก็บ​แค่ id ของ​อีก Aggregate ไม่ใช่​อ้างอิง​ตรง ป้องกัน​การ​แก้​หลาย Aggregate ใน transaction เดียว และ map เข้า​กับ​ขอบเขต microservice ได้​ดี

4. ใช้ Eventual ConsistencyEventual Consistencyความ​ถูก​ต้อง​ที่ “ตาม​มา​ทีหลัง” ไม่ใช่​ทันที ใช้​กับ​การ​อัปเดต​ข้าม Aggregate ผ่าน Domain Event แทน​การ​บังคับ​ให้​ตรง​กัน​ใน​ทันที​ภายใน transaction เดียวTactical Design นอก​ขอบเขต — เมื่อ​กระบวนการ​ธุรกิจ​กระทบ​หลาย Aggregate ให้​ส่ง Domain EventDomain Eventสิ่ง​ที่ “เกิด​ขึ้น​แล้ว” ใน domain ซึ่ง​ส่วน​อื่น​สนใจ แทน​ด้วย​อ็อบเจ็กต์​ที่​เปลี่ยนแปลง​ไม่​ได้ ตั้ง​ชื่อ​เป็น​อดีต เช่น CargoWasRouted ใช้​สื่อสาร​ข้าม AggregateTactical Design แล้ว​ประมวล​ผล​ตาม​มา​แบบ async

ภายนอก​แก้ไข​รายการ​สินค้า​ได้​ผ่าน Order เท่านั้น — root จึง​รับ​ประกัน invariant ได้​เสมอ
// Order = Aggregate Root; OrderLine อยู่ภายในขอบเขต
class Order { // <- Aggregate Root
private lines: OrderLine[] = [];
private constructor(readonly id: OrderId, private status: OrderStatus) {}
// พฤติกรรมอยู่บน root และคอย “บังคับ invariant”
addLine(product: ProductId, qty: number, price: Money): void {
if (this.status !== 'DRAFT') throw new Error('แก้คำสั่งซื้อที่ยืนยันแล้วไม่ได้');
this.lines.push(new OrderLine(product, qty, price));
}
total(): Money { // invariant: ยอดรวม = ผลรวมของรายการ
return this.lines.reduce((sum, l) => sum.add(l.subtotal()), Money.of(0, 'THB'));
}
confirm(): DomainEvent {
if (this.lines.length === 0) throw new Error('คำสั่งซื้อว่างเปล่ายืนยันไม่ได้');
this.status = 'CONFIRMED';
return new OrderConfirmed(this.id, this.total()); // ส่ง event ออกไป (กฎข้อ 4)
}
}
class OrderLine { // ภายใน Aggregate — ไม่มีใครอ้างจากนอกได้
constructor(readonly product: ProductId, readonly qty: number, readonly price: Money) {}
subtotal(): Money { return this.price.times(this.qty); }
}
🚢 ทำไม Handling Event จึง​เป็น Aggregate แยก?

ใน​ระบบ​ขนส่ง Cargo (ระบุ​ตัว​ด้วย Tracking IDTracking IDรหัส​ติดตาม​ที่​ไม่​ซ้ำ​ของ Cargo ใน​ตัวอย่าง​การ​ขนส่ง ใช้​เป็น identity ของ Cargo ตั้งแต่​ตอน​จอง​จน​ส่ง​มอบTactical Design ตั้งแต่​จอง​จน​ส่ง​มอบ) เป็น Aggregate RootAggregate RootEntity หนึ่ง​ตัว​ที่​เป็น “ประตู​เดียว” เข้า​สู่ Aggregate การ​อ้างอิง​จาก​ภายนอก​ทำได้​กับ root เท่านั้น root จึง​ควบคุม​กฎ​ความ​ถูก​ต้อง​ของ​ทั้ง​กลุ่มTactical Design (มี Route SpecificationRoute SpecificationValue Object ที่​ระบุ “ความ​ต้องการ​ของ​ลูกค้า”: ต้นทาง ปลาย​ทาง และ​กำหนด​เวลา​ถึง — มี method isSatisfiedBy() ตรวจ​ว่า​แผน​เดิน​ทาง​ตรง​ตาม​นี้​หรือ​ไม่Tactical Design, ItineraryItineraryValue Object ที่​เป็น “แผนการ​เดิน​ทาง​จริง” ของ Cargo ประกอบ​ด้วย​ลำดับ​ของ Leg (ช่วง​การ​ขนส่ง​แต่ละ​ช่วง)Tactical Design, Delivery อยู่​ภายใน) ส่วน Handling EventHandling Eventการ​บันทึก​การ​จัดการ​สินค้า​จริง (LOAD, UNLOAD, RECEIVE, CLAIM) เป็น Aggregate “แยก​ต่าง​หาก” จาก Cargo เพราะ​มี​ปริมาณ​มาก​และ​ต้อง​ประมวล​ผล​แบบ asyncTactical Design — การ​บันทึก​ว่า​สินค้า​ถูก​ยก/ลง/รับ ณ ท่า​ใด — เป็น Aggregate แยก​ต่าง​หาก

เหตุผล​คือ “ประสิทธิภาพ”: Handling Event หลั่ง​ไหล​เข้า​มา​ปริมาณ​มหาศาล​จาก​ระบบ​ท่าเรือ/คลัง​ภายนอก และ​ต้อง​ประมวล​ผล​เร็ว​แบบ async จึง​ไม่​ควร​โหลด​มา​พร้อม​โครงสร้าง Cargo ที่​ใหญ่ — ตรง​กับ​กฎ “ออกแบบ​ให้​เล็ก” และ “อ้างอิง​ด้วย id” พอดี


RepositoryRepositoryตัว​ที่​ทำให้​เรา​เข้า​ถึง Aggregate ราวกับ​เป็น collection ใน​หน่วย​ความ​จำ ซ่อน​ราย​ละเอียด​ฐาน​ข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design ทำให้​เรา​เข้า​ถึง AggregateAggregateกลุ่ม​ของ Entity และ Value Object ที่​ถูก​มอง​เป็น​หนึ่ง​หน่วย​เดียว​เพื่อ​รักษา​ความ​ถูก​ต้อง​ของ​ข้อมูล มี​ขอบเขต​ชัดเจน และ​เป็น​หน่วย​ของ transactionTactical Design ราวกับ​เป็น collection ใน​หน่วย​ความ​จำ โดย​ซ่อน​ราย​ละเอียด​ฐาน​ข้อมูล หลัก​การ​คือ 1 repository ต่อ​หนึ่ง Aggregate RootAggregate RootEntity หนึ่ง​ตัว​ที่​เป็น “ประตู​เดียว” เข้า​สู่ Aggregate การ​อ้างอิง​จาก​ภายนอก​ทำได้​กับ root เท่านั้น root จึง​ควบคุม​กฎ​ความ​ถูก​ต้อง​ของ​ทั้ง​กลุ่มTactical Design และ interface ควร​นิยาม​ไว้​ใน​ชั้น domain ส่วน implementation อยู่​ชั้น infrastructure เพื่อ​ไม่​ให้ ORM มา “ปน​เปื้อน” model (เรา​จะ​เห็น​เหตุผล​ชัด​ขึ้น​ใน​เรื่อง​สถาปัตยกรรม)

domain ขึ้น​กับ “interface” ที่​ตัว​เอง​นิยาม ไม่​ขึ้น​กับ​ฐาน​ข้อมูล — นี่​คือ Port ใน​สถาปัตยกรรม Hexagonal
// interface อยู่ในชั้น domain — domain บอกแค่ว่า “ต้องการอะไร”
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
nextId(): OrderId;
}
// implementation จริง (เช่น Drizzle/Postgres) อยู่ชั้น infrastructure แยกต่างหาก

เมื่อ​ตรรกะ​ธุรกิจ​สำคัญ “ไม่​เข้า” กับ Entity หรือ Value Object ตัว​ใด​ตัว​หนึ่ง​โดย​ธรรมชาติ ให้​ทำ​เป็น Domain ServiceDomain Serviceบริการ​ที่​ถือ “ตรรกะ​ธุรกิจ” ซึ่ง​ไม่​เข้า​กับ Entity หรือ Value Object ตัว​ใด​ตัว​หนึ่ง​โดย​ธรรมชาติ เช่น การ​โอน​เงิน​ที่​เกี่ยวข้อง​กับ​สอง​บัญชี — ไร้​สถานะ (stateless)Tactical Design เช่น การ​หา​เส้น​ทาง หรือ​การ​โอน​เงิน​ที่​กระทบ​สอง​บัญชี ส่วน Application ServiceApplication Serviceบริการ​ที่ “ประสาน​งาน” use case — จัดการ transaction, ความ​ปลอดภัย, การ​บันทึก, แปลง DTO — แต่​ไม่มี​กฎ​ธุรกิจ​อยู่​ใน​ตัว​มัน​เองTactical Design ทำ​หน้าที่ “ประสาน​งาน” use case — เปิด transaction, จัดการ​สิทธิ์, เรียก repository — แต่ ไม่มี​กฎ​ธุรกิจ​ใน​ตัว​เอง

เส้น​แบ่ง​ที่​จำ​ง่าย

Domain ServiceDomain Serviceบริการ​ที่​ถือ “ตรรกะ​ธุรกิจ” ซึ่ง​ไม่​เข้า​กับ Entity หรือ Value Object ตัว​ใด​ตัว​หนึ่ง​โดย​ธรรมชาติ เช่น การ​โอน​เงิน​ที่​เกี่ยวข้อง​กับ​สอง​บัญชี — ไร้​สถานะ (stateless)Tactical Design = ถือ “การ​ตัดสิน​ใจ​ทาง​ธุรกิจ” · Application ServiceApplication Serviceบริการ​ที่ “ประสาน​งาน” use case — จัดการ transaction, ความ​ปลอดภัย, การ​บันทึก, แปลง DTO — แต่​ไม่มี​กฎ​ธุรกิจ​อยู่​ใน​ตัว​มัน​เองTactical Design = ประสาน​งานการ​ตัดสิน​ใจ​เหล่า​นั้น (เหมือน​ที่​มัน​ประสาน​งานการ​เรียก method บน Entity) ถ้า Application Service เริ่ม​มี​กฎ​ธุรกิจ นั่น​คือ​สัญญาณ​ว่า​กำลัง​จะ​กลาย​เป็น Anemic Domain ModelAnemic Domain Modelanti-pattern ที่​อ็อบเจ็กต์​มี​แต่ getter/setter ไร้​พฤติกรรม ตรรกะ​ถูก​ดึง​ไป​ไว้​ใน service ทั้งหมด — จ่าย​ค่า domain model แต่​ไม่​ได้​ประโยชน์​ของ​มัน​เลยTactical Design

Domain EventDomain Eventสิ่ง​ที่ “เกิด​ขึ้น​แล้ว” ใน domain ซึ่ง​ส่วน​อื่น​สนใจ แทน​ด้วย​อ็อบเจ็กต์​ที่​เปลี่ยนแปลง​ไม่​ได้ ตั้ง​ชื่อ​เป็น​อดีต เช่น CargoWasRouted ใช้​สื่อสาร​ข้าม AggregateTactical Design แทน “สิ่ง​ที่​เกิด​ขึ้น​แล้ว” ใน domain ที่​ส่วน​อื่น​สนใจ ตั้ง​ชื่อ​เป็น​อดีต​เสมอ (เช่น CargoWasRouted, OrderConfirmed) เป็น ImmutableImmutableเปลี่ยนแปลง​ไม่​ได้​หลัง​สร้าง ถ้า​จะ “เปลี่ยน​ค่า” ต้อง​สร้าง​อ็อบเจ็กต์​ใหม่​แทน หลัก​การ​สำคัญ​ของ Value Object เพื่อ​เลี่ยง bug จาก​การ​ใช้​อ้างอิง​ร่วม​กันTactical Design และ​มัก​พก timestamp กับ id ของ​สิ่ง​ที่​เกี่ยวข้อง ใช้​เพื่อ​ทำ side-effect ข้าม Aggregate และ​เปิด​ทาง Eventual ConsistencyEventual Consistencyความ​ถูก​ต้อง​ที่ “ตาม​มา​ทีหลัง” ไม่ใช่​ทันที ใช้​กับ​การ​อัปเดต​ข้าม Aggregate ผ่าน Domain Event แทน​การ​บังคับ​ให้​ตรง​กัน​ใน​ทันที​ภายใน transaction เดียวTactical Design ส่วน FactoryFactoryตัว​ที่​ห่อ​หุ้ม​ตรรกะ​การ “สร้าง” Aggregate ที่​ซับ​ซ้อน และ​รับ​ประกัน​ว่า​อ็อบเจ็กต์​ใหม่​ถูก​ต้อง​ตาม invariant ตั้งแต่​แรก​เกิด — “สร้าง​ของ​ใหม่” (repository หา “ของ​เก่า”)Tactical Design ห่อ​หุ้ม​การ “สร้าง” Aggregate ที่​ซับ​ซ้อน​ให้​ถูก​ต้อง​ตาม invariant ตั้งแต่​แรก — Evans เปรียบ​ว่า “factory สร้าง​ของ​ใหม่ ส่วน repository หา​ของ​เก่า” ส่วน ModuleModuleการ​จัด​กลุ่ม​แนวคิด​ที่​เกี่ยวข้อง​กัน​ไว้​ด้วย​กัน (เช่น package) ตั้ง​ชื่อ​ให้​เป็น​ส่วน​หนึ่ง​ของ Ubiquitous Language จัด​ตาม​ธุรกิจ ไม่ใช่​ตาม​ชนิด​ทาง​เทคนิคTactical Design คือ​การ​จัด​กลุ่ม​แนวคิด​ที่​เกี่ยวข้อง​กัน​ไว้​ด้วย​กัน (เช่น package) โดย​ตั้ง​ชื่อ​กลุ่ม​ด้วย​คำ​ใน Ubiquitous LanguageUbiquitous Languageภาษา​กลาง​ที่​ทุก​คนใน​ทีม (ทั้ง​นัก​พัฒนา​และ​ผู้​เชี่ยวชาญ​ธุรกิจ) ใช้​ร่วม​กัน อิง​กับ domain model โดยตรง คำ​เดียวกัน​ต้อง​หมาย​ถึง​สิ่ง​เดียวกัน และ​สะท้อน​ลง​ใน codeStrategic Design ไม่ใช่​ตั้ง​ตาม​ชนิด​ทาง​เทคนิค


Anemic Domain ModelAnemic Domain Modelanti-pattern ที่​อ็อบเจ็กต์​มี​แต่ getter/setter ไร้​พฤติกรรม ตรรกะ​ถูก​ดึง​ไป​ไว้​ใน service ทั้งหมด — จ่าย​ค่า domain model แต่​ไม่​ได้​ประโยชน์​ของ​มัน​เลยTactical Design คือ​กับ​ดัก​ที่​พบ​บ่อย​ที่สุด: อ็อบเจ็กต์​ดู​เหมือน domain model มีชื่อ​ตรง​กับ​คำ​ใน​ธุรกิจ แต่ “แทบ​ไม่มี​พฤติกรรม” เป็น​เพียง “ถุง​ใส่ getter/setter” ส่วน​ตรรกะ​ทั้งหมด​ถูก​ผลัก​ไป​ไว้​ใน​ชั้น service Fowler เรียก​ว่า​ขัด​กับ​แก่น​ของ OOP โดย​สิ้น​เชิง เพราะ OOP คือ​การ​รวม​ข้อมูล​กับ​พฤติกรรม​เข้า​ด้วย​กัน

ปัญหา​ของ anemic domain model คือ​มัน​แบก​ต้นทุน​ทั้งหมด​ของ domain model โดย​ไม่​ได้​ประโยชน์​ของ​มัน​เลย

— ใจความ​จาก Martin Fowler

แต่ — ไม่ใช่​ทุก​ที่​ต้อง​รวย

Microsoft ตั้ง​ข้อ​สังเกต​ว่า ถ้า Bounded Context นั้น​ง่าย​มาก (เป็น CRUD ล้วน เช่น catalog สินค้า) anemic model ก็ “ดีพอ” ได้ ไม่​ต้อง​ฝืน​ใช้ DDD เต็ม​รูป​แบบ​ทุก​ที่ — ขึ้น​กับ​ว่า​กำลัง​สร้าง​อะไร (เรื่อง​นี้​เชื่อม​กับ Stage 5)


🔗 อ้างอิง​เพิ่ม​เติม​ใน DevIQ

เจาะ​ลึก​แนวคิด​ใน​บท​นี้​ต่อ​ได้ที่​คลัง​อ้างอิง DevIQ:

  • Entity — ขยาย​ความ​เรื่อง​การ​แยกแยะ​ด้วย identity และ​ทำไม Entity ต้อง “มี​พฤติกรรม” ไม่ใช่​แค่​เก็บ​ข้อมูล
  • Value Object — หลัก​การ immutability และ self-validation ที่​ตัวอย่าง Money ใช้
  • Aggregate — ราย​ละเอียด​เรื่อง Aggregate Root และ​ขอบเขต​ความ​ถูก​ต้อง (invariant boundary) ที่​ต่อย​อด​จาก​สี่​กฎ​ของ Vernon
  • Repository — pattern เต็ม​รูป​แบบ​เบื้อง​หลัง interface OrderRepository และ​เหตุผล​ที่ domain ไม่​ควร​ขึ้น​กับ ORM
  • Anemic Domain Model — วิเคราะห์ anti-pattern นี้​ลึก​ขึ้น พร้อม​วิธี​สังเกต​และ​หลีก​เลี่ยง

เช็กความเข้าใจ — Stage 3

ข้อ 1 / 5

อะไรคือความต่างหลักระหว่าง Entity กับ Value Object?