Prerequisite 1: DDD Strategic Design — แบ่งระบบให้ถูกก่อนเขียนโค้ดบรรทัดแรก
Prerequisite 1: DDD Strategic Design
แบ่งระบบให้ถูกก่อนเขียนโค้ดบรรทัดแรก
เมื่อทุกอย่างผูกกัน จนไม่มีใครกล้าแตะ
บริษัทผลิตสินค้าแห่งหนึ่งมีระบบที่ทำงานได้ดีในแต่ละแผนกมาหลายปี
- ฝ่าย Sales มีระบบจัดการราคาขายและ discount ของตัวเอง
- ฝ่าย Procurement มีระบบจัดการใบสั่งซื้อและข้อมูล supplier ของตัวเอง
- ฝ่าย Cost Accounting มีระบบคำนวณต้นทุนและ overhead ของตัวเอง
แต่ละแผนกทำงานได้ดี ทุกคนพอใจ
จนวันหนึ่ง management ตัดสินใจว่า “เรามีระบบแยกกันเยอะเกินไป ควรรวมเป็น ERP เดียวเพื่อให้ข้อมูลเชื่อมกัน” — ฟังดูสมเหตุสมผล ทุกคนเห็นด้วย
เดือนที่ 1 — BA เริ่มเก็บ requirement
BA นัดคุยกับเจ้าหน้าที่แต่ละแผนกทีละคน ใช้เวลาหลายสัปดาห์ ทุกคนอธิบายได้ชัดเจนและถูกต้องในมุมของตัวเอง
เจ้าหน้าที่ฝ่าย Sales (Domain Expert ของระบบ Sales) บอกว่า
“เราต้องการจัดการข้อมูล สินค้า โดยแต่ละสินค้าต้องมีราคาขายและ discount ที่ปรับได้ตามกลุ่มลูกค้า บางกลุ่มได้ส่วนลด 10% บางกลุ่มได้ 20% เราต้องการให้ Sales team ปรับได้เองโดยไม่ต้องรอ IT”
เจ้าหน้าที่ฝ่าย Procurement (Domain Expert ของระบบ Procurement) บอกว่า
“เราต้องการจัดการข้อมูล สินค้า โดยแต่ละสินค้าต้องรู้ว่าซื้อจาก supplier ไหน ราคาซื้อเท่าไหร่ lead time นานแค่ไหน และมีเงื่อนไขสัญญาอะไรบ้าง เพราะเราต้องวางแผนการจัดซื้อล่วงหน้า”
เจ้าหน้าที่ฝ่าย Cost Accounting (Domain Expert ของระบบ Cost Accounting) บอกว่า
“เราต้องการจัดการข้อมูล สินค้า โดยแต่ละสินค้าต้องรู้ต้นทุนทั้งหมด ทั้งราคาวัตถุดิบ ค่าแรง overhead และ margin เพราะเราต้องรายงาน CFO ทุกเดือน”
BA จดบันทึกครบทุกคน อ่านกลับไปกลับมา ทุกอย่างดูสอดคล้องกัน ทุกแผนกต้องการ “ข้อมูลสินค้า” เหมือนกัน BA จึงสรุป requirement ส่งต่อให้ Dev
สิ่งที่ BA ไม่รู้ในตอนนั้น — แต่ละแผนกใช้คำว่า “สินค้า” เหมือนกัน แต่ในหัวของแต่ละคนหมายถึงคนละเรื่องโดยสิ้นเชิง เพราะแต่ละคนมองจาก context ของตัวเอง และทำงานแยกกันมาตลอด ไม่เคยต้องนิยามร่วมกันมาก่อน
เดือนที่ 3 — Dev ออกแบบและพัฒนาระบบ
Dev อ่าน requirement แล้วเห็นว่าทุกแผนกต้องการ “ข้อมูลสินค้า” Dev จึงออกแบบ Entity “สินค้า” ตัวเดียว แล้วรวม field ของทุกแผนกเข้าไปด้วยกัน
สินค้า {
รหัสสินค้า
ชื่อสินค้า
ราคาขาย ← ของ Sales
discount ← ของ Sales
ราคาซื้อ ← ของ Procurement
supplierId ← ของ Procurement
leadTime ← ของ Procurement
ต้นทุน ← ของ Cost Accounting
overhead ← ของ Cost Accounting
margin ← ของ Cost Accounting
}
ดูสมเหตุสมผล ทุก field มีเจ้าของชัดเจน Dev เริ่มพัฒนาระบบต่อไป
แต่ไม่นานก็เริ่มรู้สึกว่ามีบางอย่างไม่ปกติ — ทุกครั้งที่ฝ่ายใดฝ่ายหนึ่งขอแก้ไข field ของตัวเอง Dev ต้องนัดประชุมกับอีก 2 แผนกก่อนเสมอ เพราะไม่แน่ใจว่าการเปลี่ยนแปลงจะกระทบอะไรบ้าง งานที่ควรใช้เวลาไม่กี่ชั่วโมงกลายเป็นสัปดาห์ และไม่มีใครรู้ว่าทำไม
ยิ่งระบบโตขึ้น ยิ่งเปลี่ยนอะไรได้ยากขึ้น ทั้งที่ทุกคนรู้ดีว่าตัวเองต้องการอะไร
เดือนที่ 6 — ระบบเริ่มใช้งานจริง ปัญหาเริ่มปรากฏ
วันแรกที่ระบบ go-live ทุกอย่างดูเรียบร้อยดี แต่ไม่ถึง 2 สัปดาห์ ticket เริ่มทยอยเข้ามา
เจ้าหน้าที่ฝ่าย Sales เปิด ticket
“ราคาขายสินค้า A เปลี่ยนจาก 1,500 เป็น 1,200 บาทโดยไม่มีใครแจ้ง ทีม Sales กำลังเสนอราคาลูกค้าด้วยราคาเก่าอยู่ ลูกค้าเริ่มสับสน”
Dev สืบสาเหตุพบว่า ฝ่าย Cost Accounting เพิ่งอัปเดต overhead ของสินค้าตัวเดียวกัน แต่เพราะทุกอย่างอยู่ใน Entity เดียวกัน ระบบคำนวณราคาขายใหม่โดยอัตโนมัติ
เจ้าหน้าที่ฝ่าย Procurement เปิด ticket
“ข้อมูล supplier และ lead time ของสินค้า B หายไปหมดเลย ทำให้วางแผนการสั่งซื้อไม่ได้ เดือนหน้าสินค้าอาจขาด stock”
Dev สืบสาเหตุพบว่า ฝ่าย Sales เพิ่งอัปเดต discount ของสินค้าตัวเดียวกัน แต่ form ที่ใช้เป็น form เดียวกัน ทำให้ field ของ Procurement ถูก override ไปโดยไม่ตั้งใจ
เจ้าหน้าที่ฝ่าย Cost Accounting เปิด ticket
“รายงานต้นทุนประจำเดือนผิดพลาดทั้งหมด ตัวเลขที่ได้ไม่ตรงกับความเป็นจริง CFO ขอคำอธิบาย”
Dev สืบสาเหตุพบว่า ราคาขายที่ Sales อัปเดตไปส่งผลต่อสูตรคำนวณต้นทุนของ Cost Accounting โดยตรง เพราะทั้งสองอยู่ใน Entity เดียวกัน
ทีม IT ประชุมฉุกเฉิน 3 แผนก ใช้เวลากว่า 2 สัปดาห์ถึงพบว่าปัญหาไม่ได้อยู่ที่โค้ดผิด ไม่ได้อยู่ที่ใครทำงานผิดพลาด
ปัญหาอยู่ที่สิ่งที่ซ่อนอยู่ตั้งแต่เดือนแรก — คำว่า “สินค้า” ของแต่ละแผนกไม่ได้หมายถึงสิ่งเดียวกัน แต่ไม่มีใครรู้ เพราะไม่เคยต้องนิยามร่วมกันมาก่อน จนกว่าจะพยายามรวมทุกอย่างเข้าด้วยกัน
ถ้าถามแต่ละแผนกว่า “สินค้าของคุณคืออะไร?” ทุกคนจะตอบถูกในมุมของตัวเอง แต่นั่นคือปัญหา — คำตอบที่ถูกในแต่ละมุม ไม่ได้หมายความว่ามันคือสิ่งเดียวกัน
DDD Strategic Design คือเครื่องมือที่ช่วยค้นพบและวางขอบเขตนั้น ก่อนที่จะเขียนโค้ดบรรทัดแรก
DDD คืออะไร — ที่มาและเป้าหมาย
ปี 2003 Eric Evans นักพัฒนาซอฟต์แวร์ที่ทำงานกับระบบใหญ่มาหลายปี ออกหนังสือชื่อ Domain-Driven Design
สิ่งที่เขาเจอซ้ำๆ ในทีมใหญ่คือ — ยิ่งทีมโตขึ้น ยิ่งมีคนมากขึ้น ระบบก็ยิ่งพัวพันกันมากขึ้น และที่แย่กว่านั้นคือ ภาษาที่ Dev ใช้กับภาษาที่ Business ใช้ไม่ตรงกัน
เหมือนกับที่เกิดขึ้นใน scenario ก่อนหน้า — เจ้าหน้าที่ฝ่าย Sales พูดว่า “สินค้า” หมายถึงราคาขาย แต่ Dev ที่ได้ยิน requirement เข้าใจว่าหมายถึง Entity กลางที่ทุกแผนกใช้ร่วมกัน ไม่มีใครรู้ว่าตัวเองเข้าใจคนละอย่างจนกว่าระบบจะพัง
พอสองฝั่งใช้ภาษาต่างกัน requirement แปลผิดพลาด ระบบที่สร้างไม่ตรงกับสิ่งที่ Business ต้องการ และเมื่อแก้อะไรก็กระทบทุกที่
Insight หลักของ Evans คือ: “ถ้า Dev กับ Business ใช้ภาษาเดียวกัน และแบ่งขอบเขตให้ชัด — ระบบจะสะท้อน business จริงๆ”
DDD แบ่งเป็น 2 ระดับ บทความนี้พูดถึงระดับแรก
Strategic Design ทำงานใน 2 โลกพร้อมกัน
ก่อนอื่นมันวิเคราะห์ Problem Space — ว่ากำลังแก้ปัญหาอะไร ปัญหาแบ่งเป็นส่วนไหนได้บ้าง ส่วนไหนคือหัวใจของธุรกิจ ส่วนไหนเป็นแค่ของที่ต้องมี จากนั้นมันกำหนด Solution Space — ว่าจะแบ่งระบบเป็น Bounded Context อะไรบ้าง แต่ละ Context รับผิดชอบอะไร และ Context ต่างๆ สัมพันธ์กันยังไง
Bounded Context ที่ได้ออกมาไม่ใช่แค่ “แผนสำหรับขั้นต่อไป” — มันคือ Solution แล้ว เพียงแต่ยังไม่ได้พูดถึงโค้ดเลย
Tactical Design จึงเข้ามาทีหลัง เพื่อออกแบบ implementation ภายใน Bounded Context แต่ละอัน ด้วย design patterns ที่สะท้อนโครงสร้างของปัญหา — ยังไม่ผูกกับ framework หรือ programming language ใดๆ ทั้งสิ้น
| ระดับ | โลกที่ทำงาน | คำถามที่ตอบ |
|---|---|---|
| Strategic Design | Problem Space + Solution Space | ”ปัญหาคืออะไร และแบ่งขอบเขตการแก้ปัญหาอย่างไร?” |
| Tactical Design | Solution Space (Implementation) | “ออกแบบ implementation ภายใน Context ยังไง — โดยไม่ผูกกับ framework?” |
วันนี้เราอยู่ที่ Strategic Design — วิเคราะห์ปัญหาและกำหนดขอบเขตให้ชัด ก่อนจะลงมือเขียนโค้ดบรรทัดแรก
องค์ประกอบของ DDD Strategic Design
Strategic Design ประกอบด้วย 5 concept หลักที่ทำงานร่วมกัน
| # | Concept | อยู่ใน | หน้าที่ |
|---|---|---|---|
| 1 | Domain | Problem Space | ระบุว่ากำลังแก้ปัญหาอะไร และส่วนไหนสำคัญแค่ไหน |
| 2 | Sub-Domain | Problem Space | แบ่ง Domain ย่อยลงมาตาม area of business ที่ต่างกัน |
| 3 | Ubiquitous Language | ทั้งสอง | กำหนดภาษากลางที่ทุกคนในทีมใช้ร่วมกัน ทั้งในการพูดและในโค้ด |
| 4 | Bounded Context | Solution Space | แบ่งขอบเขตความรับผิดชอบ กำหนดว่าภาษาและ model แต่ละชุดมีความหมายในขอบเขตไหน |
| 5 | Context Mapping | Solution Space | กำหนดว่าแต่ละ Bounded Context เชื่อมต่อและพึ่งพากันอย่างไร |
ทั้ง 5 concept นี้ต้องใช้ร่วมกัน — Domain และ Sub-Domain บอกว่าปัญหาคืออะไรและแบ่งออกเป็นส่วนไหนบ้าง, Ubiquitous Language สร้างภาษากลางในแต่ละส่วน, Bounded Context ลากเส้นขอบเขตใน Solution Space และ Context Mapping เชื่อมทุกอย่างเข้าด้วยกัน
บทความนี้จะอธิบายทีละ concept ตามลำดับนี้
Domain — “พื้นที่ของปัญหา”
ก่อนอื่น ต้องทำความเข้าใจให้ชัดว่า “Domain” ในระดับ Strategic ไม่ใช่เรื่องโค้ด
Domain = “พื้นที่ของปัญหา” (Problem Space) ใช้เพื่อระบุว่ากำลังแก้ปัญหาอะไร — ยังไม่เกี่ยวกับการเขียนโค้ดใดๆ ทั้งสิ้น
ถ้าสร้างระบบ ERP Domain ไม่ใช่ “โปรแกรมบริหารองค์กร” แต่คือ “กระบวนการจัดซื้อ การจัดการ stock การบริหารการขาย การคิดต้นทุน” — เป็นคำอธิบายโลกของปัญหาที่กำลังแก้
แบ่งประเภท Domain
Domain ไม่ได้มีความสำคัญเท่ากันทุกส่วน Eric Evans แบ่งไว้ 3 ประเภท
Core Domain — หัวใจของธุรกิจ
สิ่งที่ทำให้ธุรกิจแตกต่างจากคู่แข่ง ถ้าไม่มีส่วนนี้ก็ไม่มีเหตุผลที่ลูกค้าจะเลือก
→ ลงทุนที่นี่มากที่สุด สร้างเองทุกอย่าง ทีมที่เก่งที่สุดควรอยู่ตรงนี้
สำหรับ Grab — Core Domain คือ matching algorithm ระหว่าง rider กับ driver ไม่ใช่ระบบ login
สำหรับ ERP ของบริษัทผลิตสินค้า — Core Domain อาจมีมากกว่า 1 module ขึ้นอยู่กับว่าบริษัทแข่งขันด้วยอะไร บริษัทที่แข่งด้วยกลยุทธ์ราคาอาจมี Sales Management เป็น Core แต่บริษัทที่แข่งด้วยการควบคุมต้นทุนการผลิตอาจมี Cost Accounting เป็น Core แทน — ไม่มีคำตอบตายตัว ขึ้นอยู่กับ business ของแต่ละองค์กร
Supporting Domain — จำเป็นแต่ไม่ใช่จุดแข็ง
ระบบที่ต้องมี มี business logic เฉพาะของบริษัทอยู่บ้าง แต่ถ้าคู่แข่งทำได้เหมือนกันก็ไม่เป็นไร
→ อาจ outsource หรือใช้ solution สำเร็จรูปที่ customize ได้ — แค่อย่าใส่พลังงานเท่า Core
ตัวอย่างใน ERP: Fixed Asset Management — ทุกบริษัทต้องติดตามทรัพย์สิน มีวิธีคิดค่าเสื่อมราคาที่แตกต่างกันบ้าง แต่ไม่ได้ทำให้แข่งขันได้
Generic Domain — ทุกบริษัทต้องมีเหมือนกัน
สิ่งที่ทุกธุรกิจต้องการเหมือนกัน ไม่ได้สร้างความได้เปรียบ และ logic ข้างในไม่ได้ต่างกันเลยระหว่างบริษัท
→ ซื้อ off-the-shelf ก่อนเสมอ อย่าเสียเวลาสร้างเอง
ตัวอย่างใน ERP: Authentication, Notification, Audit Log — การ login ของบริษัทผลิตสินค้ากับธนาคารไม่ได้ต่างกันเลย ซื้อสำเร็จรูปแล้วจบ
สรุปเปรียบเทียบ 3 ประเภท
| Core | Supporting | Generic | |
|---|---|---|---|
| ต้องมีไหม | ✅ | ✅ | ✅ |
| ซื้อสำเร็จรูปได้เลย | ❌ | ⚠️ อาจต้อง customize | ✅ |
| มี business logic เฉพาะบริษัท | ✅ มาก | ✅ บางส่วน | ❌ |
| ทำให้แข่งขันได้ | ✅ | ❌ | ❌ |
| ควร invest แค่ไหน | มากที่สุด | ปานกลาง | น้อยที่สุด |
มุมของแต่ละบทบาท
Dev: ก่อน implement อะไร ถามก่อนว่า “นี่คือ Core Domain ไหม?” ถ้าไม่ใช่ — มีโอกาสสูงที่จะมี solution สำเร็จรูปที่ดีกว่าสร้างเอง
BA: ช่วยระบุได้ว่า requirement ที่มาใหม่นั้นอยู่ใน Domain ไหน และควรลงทุนขนาดไหน ถ้า requirement อยู่ใน Generic Domain — ลองหาว่ามี off-the-shelf solution ที่ตอบโจทย์ได้ไหมก่อน
Domain Expert: ถ้าพบว่าทีม IT ใช้เวลากับ module ที่ไม่ได้สร้างความได้เปรียบให้บริษัทมากเกินไป นั่นคือ signal ว่าการจัดลำดับความสำคัญของ Domain ยังไม่ชัดเจน
Sub-Domain — “แบ่ง Domain ย่อยลงมาตาม Area of Business”
รู้จัก Domain แล้ว แต่ Domain ของ ERP นั้นใหญ่มาก — “กระบวนการจัดซื้อ การจัดการ stock การบริหารการขาย การคิดต้นทุน” ไม่สามารถวิเคราะห์ทั้งหมดพร้อมกันได้ในครั้งเดียว
Sub-Domain คือการแบ่ง Domain ย่อยลงมาตาม area of business ที่มีความรับผิดชอบชัดเจน มีผู้เชี่ยวชาญของตัวเอง และมีกฎของตัวเอง
Sub-Domain อยู่ใน Problem Space เสมอ — เป็นการอธิบายว่า Business มีส่วนอะไรบ้าง ยังไม่ใช่การออกแบบ solution ใดๆ ทั้งสิ้น
Hierarchy ที่ครบถ้วน
Domain (Problem Space)
└── Sub-Domain (Problem Space)
└── Bounded Context (Solution Space)
| ระดับ | อยู่ใน | คืออะไร | ตัวอย่าง |
|---|---|---|---|
| Domain | Problem Space | พื้นที่ปัญหาทั้งหมด | ”ระบบ ERP บริษัทผลิตสินค้า” |
| Sub-Domain | Problem Space | ส่วนย่อยของ Business | ”การขาย”, “การจัดซื้อ”, “การบัญชีต้นทุน” |
| Bounded Context | Solution Space | วิธีที่เรา model ใน code | sales-management/, procurement/ |
Sub-Domain ของ ERP
แต่ละ Sub-Domain ยังแบ่งประเภทได้เหมือน Domain หลัก — Core, Supporting หรือ Generic ขึ้นอยู่กับว่า Sub-Domain นั้นสร้างความได้เปรียบทางธุรกิจไหม
Domain: ระบบ ERP บริษัทผลิตสินค้า
│
├── Sales (Sub-Domain) ← Core Domain*
│ "กลยุทธ์ราคาและการขาย"
│ Domain Expert: เจ้าหน้าที่ฝ่าย Sales
│
├── Procurement (Sub-Domain) ← Supporting Domain
│ "การจัดซื้อและจัดการ supplier"
│ Domain Expert: เจ้าหน้าที่ฝ่าย Procurement
│
├── Stock (Sub-Domain) ← Supporting Domain
│ "การจัดการ stock และ warehouse"
│ Domain Expert: เจ้าหน้าที่ฝ่าย Warehouse
│
├── Cost Accounting (Sub-Domain) ← Supporting Domain
│ "การคำนวณต้นทุนและรายงาน CFO"
│ Domain Expert: เจ้าหน้าที่ฝ่าย Cost Accounting
│
└── MDM (Sub-Domain) ← Generic Domain
"ข้อมูลกลางที่ทุกแผนกใช้อ้างอิง"
Domain Expert: IT / Data team
*Core Domain ขึ้นอยู่กับว่าบริษัทแข่งขันด้วยอะไร บริษัทที่แข่งด้วยกลยุทธ์ราคาอาจมี Sales เป็น Core แต่บริษัทที่แข่งด้วยการควบคุมต้นทุนอาจมี Cost Accounting เป็น Core แทน
ทำไมต้องรู้จัก Sub-Domain
Dev — เวลาตั้งชื่อ package หรือ module ถ้าใช้ชื่อ Sub-Domain เป็นฐาน เช่น sales/, procurement/ จะสื่อสารกับ BA และ Domain Expert ได้ตรงกว่าการใช้ชื่อ technical
BA — เวลาเก็บ requirement ให้ถามก่อนว่า requirement นี้อยู่ใน Sub-Domain ไหน เพราะแต่ละ Sub-Domain มี Domain Expert คนละคน การเอา requirement จาก Sub-Domain ต่างกันมารวมกันโดยไม่แยกคือต้นเหตุของปัญหาใน scenario ที่เล่าไว้ตอนต้น
Domain Expert — ถ้า Dev หรือ BA บอกว่า “เราจะวาง logic ของ Sales ไว้ที่เดียวกับ Procurement” นั่นคือ signal ว่ากำลังผสม Sub-Domain สองตัวเข้าด้วยกัน ซึ่งนำไปสู่ปัญหาเดิมที่เกิดใน scenario
Ubiquitous Language — “ภาษากลางที่ทุกคนใช้ร่วมกัน”
ย้อนกลับไปที่ scenario — ปัญหาทั้งหมดเริ่มต้นจากคำว่า “สินค้า” ที่แต่ละแผนกใช้เหมือนกัน แต่หมายถึงคนละอย่าง ถ้า BA, Domain Expert และ Dev ตกลงกันตั้งแต่แรกว่า “สินค้าของแต่ละแผนกคืออะไร” และใช้คำนั้นทุกที่ — ปัญหานี้ไม่เกิดขึ้น
นั่นคือสิ่งที่ Ubiquitous Language แก้
Ubiquitous Language ไม่ใช่ภาษาเทคนิคของ Dev และไม่ใช่ภาษาธุรกิจของ Domain Expert — แต่คือภาษาที่ทุกคนตกลงใช้ร่วมกัน แล้วใช้จริงทุกที่
ทุกที่ หมายถึง:
- ในการประชุมระหว่าง Domain Expert กับ BA
- ในเอกสาร requirement ที่ BA ส่งให้ Dev
- ในโค้ด — ชื่อ function, ชื่อ variable, ชื่อ type
❌ ไม่มี Ubiquitous Language
BA เขียน requirement ว่า “ระบบต้องจัดการข้อมูลสินค้า” — Dev ได้ยินแค่ “สินค้า” แล้วออกแบบ Entity เดียวให้ทุกแผนกใช้ร่วมกัน เพราะไม่รู้ว่า “สินค้า” ของแต่ละแผนกหมายถึงคนละอย่าง
Product {
id
name
supplierId ← Procurement ใช้
leadTime ← Procurement ใช้
sellingPrice ← Sales ใช้
discount ← Sales ใช้
costPrice ← Cost Accounting ใช้
overhead ← Cost Accounting ใช้
}
ทุกแผนกได้ข้อมูลที่ต้องการ แต่ทุกแผนกก็แตะ Entity เดียวกัน — แก้อะไรก็กระทบทุกที่
✅ มี Ubiquitous Language
BA คุยกับ Domain Expert แต่ละแผนกแล้วพบว่า “สินค้า” มีความหมายต่างกันใน Context ต่างๆ จึงนิยามให้ชัดก่อนส่ง requirement
MDM เป็นเจ้าของ ProductMaster และออก ProductId ที่ทุก Context ใช้อ้างอิง แต่ละ Context มี Entity ของตัวเองที่เก็บเฉพาะข้อมูลที่ตัวเองสนใจ
MDM Context:
ProductMaster { ProductId, name, unit, category }
→ แหล่งกำเนิด ProductId — ทุก Context อ้างอิงที่นี่
Procurement Context:
ProcurementProduct { ProductId, supplierId, leadTime, minOrderQty }
→ สนใจแค่การจัดซื้อ
Sales Context:
SalesProduct { ProductId, sellingPrice, discount }
→ สนใจแค่ราคาขายและกลยุทธ์
Cost Accounting Context:
CostProduct { ProductId, costPrice, overhead, margin }
→ สนใจแค่ต้นทุนและกำไร
เมื่อ Cost Accounting อัปเดต costPrice มันไม่กระทบ sellingPrice ของ Sales เลย เพราะคนละ Entity กัน — ปัญหาใน scenario ไม่เกิดขึ้น
ProductId คือสิ่งเดียวที่ share ข้าม Context ผ่าน ProductMaster ใน MDM ส่วนที่เหลือแต่ละ Context own เองทั้งหมด
การสื่อสารหลังมี Ubiquitous Language
ลองเปรียบเทียบให้เห็นชัดว่าการสื่อสารในทีมเปลี่ยนไปยังไง
ก่อน — BA บอก Dev ว่า
“ช่วยแก้ข้อมูลสินค้าให้หน่อย”
Dev ไม่รู้ว่าหมายถึง Product ใน Context ไหน ต้องนัดประชุมถามก่อนทุกครั้ง
หลัง — BA บอก Dev ว่า
“ช่วยเพิ่ม field
minOrderQtyให้ProcurementProductหน่อย”
Dev รู้ทันทีว่าต้องแก้อะไร ใน Context ไหน และไม่กระทบ Context อื่นแน่นอน
หลัง — Domain Expert ฝ่าย Sales คุยกับ BA ว่า
“
SalesProductของเราต้องรองรับ discount ตามกลุ่มลูกค้าด้วย”
BA เขียน requirement ได้ทันทีโดยไม่ต้องแปลหรืออธิบายซ้ำ และ Dev ก็รู้ว่าต้องไปแก้ที่ SalesProduct ไม่ใช่ Product กลาง
⚠️ Ubiquitous Language ไม่ได้เกิดขึ้นเอง — ต้องผ่านกระบวนการที่ BA นั่งคุยกับ Domain Expert แต่ละแผนกหลายรอบจนตกลงกันได้ว่าจะเรียกว่าอะไร และต้องมีคนคอย enforce ให้ทุกคนใช้คำเดิมสม่ำเสมอ ถ้าวันหนึ่ง Domain Expert เริ่มใช้คำต่างกัน ความสับสนก็กลับมาได้
มุมของแต่ละบทบาท
Dev: เมื่อ BA กำหนด Ubiquitous Language ชัดแล้ว Dev อ่าน requirement แล้วรู้ทันทีว่าต้องสร้างอะไร เช่น “สินค้าใน Sales Context” กับ “สินค้าใน Procurement Context” คือคนละ Entity ไม่ต้องเดาและไม่ต้องประชุมถามซ้ำ
BA: ก่อนสรุป requirement ต้องถาม Domain Expert แต่ละฝ่ายให้ชัดว่า “คำนี้ใน Context ของคุณหมายถึงอะไรกันแน่?” แล้วใช้คำนั้นตลอดทั้งเอกสาร requirement — ถ้า BA ใน scenario ก่อนหน้าทำแบบนี้ตั้งแต่แรก ปัญหาไม่เกิดขึ้น
Domain Expert: เมื่อ Dev ใช้ภาษาเดียวกับที่คุณใช้ในการประชุม คุณสามารถ review requirement และโค้ดได้โดยตรง ไม่ต้องรอให้ BA แปลกลับมาให้ฟัง
Bounded Context — “ขอบเขตที่ภาษามีความหมาย”
ตอนนี้เรามี Ubiquitous Language แล้ว ทุกคนใช้คำว่า ProcurementProduct, SalesProduct, CostProduct แทนที่จะพูดว่า “สินค้า” กลางๆ
แต่ยังมีคำถามที่ Ubiquitous Language เพียงอย่างเดียวตอบไม่ได้ — ขอบเขตของแต่ละคำนั้นอยู่ที่ไหน? และจะรู้ได้ยังไงว่าควรแบ่ง Context ตรงไหน?
นั่นคือสิ่งที่ Bounded Context แก้
หลักที่ใช้แบ่ง Bounded Context
ก่อนจะแบ่งได้ถูก ต้องรู้ว่าอะไรคือ signal ที่บอกว่าควรแยก Context
1. ดูจากภาษาที่ใช้ ถ้าคำเดียวกันมีความหมายต่างกันในสองกลุ่ม — นั่นคือ signal ชัดเจนที่สุดว่าควรเป็นคนละ Context ใน scenario ของเรา คำว่า “สินค้า” มีความหมายต่างกันใน Procurement, Sales และ Cost Accounting — นั่นคือสัญญาณที่บอกว่าต้องแบ่งเป็น 3 Context
2. ดูจากเหตุผลที่เปลี่ยนแปลง ถ้าสองส่วนเปลี่ยนแปลงด้วยเหตุผลต่างกัน — ควรแยก Context ราคาขายเปลี่ยนตาม marketing strategy แต่ต้นทุนเปลี่ยนตามราคาวัตถุดิบ เหตุผลต่างกัน จึงควรอยู่คนละ Context ถ้าอยู่ Context เดียวกัน การเปลี่ยนแปลงฝั่งหนึ่งจะกระทบอีกฝั่งเสมอ
3. ดูจาก Domain Expert
แต่ละ Context ควรมี Domain Expert ที่รับผิดชอบชัดเจน ถ้าต้องถามหลายคนจากหลายแผนกเพื่อตอบคำถามเดียว — นั่นคือ signal ว่า Context ยังไม่ชัดพอ ใน scenario ของเรา เจ้าหน้าที่ฝ่าย Sales เป็น Domain Expert ของ Sales Context คนเดียวที่ตอบได้ว่า SalesProduct ควรมี field อะไรบ้าง
Analogy จากชีวิตจริง
ลองนึกถึงคำว่า “ราคา” ในบริษัทผลิตสินค้า — ซึ่งเป็นสิ่งที่เกิดขึ้นจริงใน scenario ของเรา
เจ้าหน้าที่ฝ่าย Sales พูดว่า “ราคา” หมายถึงราคาที่เสนอลูกค้า — ปรับได้ตาม promotion และกลุ่มลูกค้า
เจ้าหน้าที่ฝ่าย Procurement พูดว่า “ราคา” หมายถึงราคาที่ซื้อจาก supplier — ขึ้นอยู่กับเงื่อนไขสัญญา
เจ้าหน้าที่ฝ่าย Cost Accounting พูดว่า “ราคา” หมายถึงต้นทุนรวม overhead — ใช้คำนวณกำไร
คำเดียวกัน ความหมายต่างกันใน 3 Context — ไม่มีใครผิด แต่ละคนถูกในขอบเขตของตัวเอง นี่คือหลักข้อที่ 1 และ 2 ในทางปฏิบัติ ภาษาต่างกัน และเปลี่ยนแปลงด้วยเหตุผลต่างกัน
ตัวอย่าง ERP
นำหลักทั้ง 3 ข้อมาใช้กับ scenario ของเรา
ภาษาต่างกัน → แบ่ง Context “สินค้า” ของ Procurement, Sales และ Cost Accounting มีความหมายต่างกัน → 3 Context
เหตุผลที่เปลี่ยนต่างกัน → แบ่ง Context
sellingPrice เปลี่ยนตาม marketing strategy, costPrice เปลี่ยนตามราคาวัตถุดิบ → คนละ Context
Domain Expert ต่างกัน → แบ่ง Context
เจ้าหน้าที่ Sales รับผิดชอบ SalesProduct, เจ้าหน้าที่ Procurement รับผิดชอบ ProcurementProduct → คนละ Context
Procurement Context:
ProcurementProduct { ProductId, supplierId, leadTime, minOrderQty }
→ เปลี่ยนเมื่อ: เงื่อนไข supplier เปลี่ยน
→ Domain Expert: เจ้าหน้าที่ฝ่าย Procurement
Sales Context:
SalesProduct { ProductId, sellingPrice, discount }
→ เปลี่ยนเมื่อ: กลยุทธ์การขายเปลี่ยน
→ Domain Expert: เจ้าหน้าที่ฝ่าย Sales
Cost Accounting Context:
CostProduct { ProductId, costPrice, overhead, margin }
→ เปลี่ยนเมื่อ: ราคาวัตถุดิบหรือโครงสร้างต้นทุนเปลี่ยน
→ Domain Expert: เจ้าหน้าที่ฝ่าย Cost Accounting
สามชื่อ สาม Context สามชุดข้อมูล — แต่หมายถึงสินค้าชิ้นเดียวกันในโลกความเป็นจริง โดยมี ProductId จาก ProductMaster ใน MDM เป็นตัวเชื่อม
สิ่งที่ Bounded Context ไม่ใช่
⚠️ Bounded Context ไม่ใช่การแบ่ง folder หรือ module (อย่างน้อยไม่ใช่แค่นั้น)
มันคือการแบ่ง “ความหมาย” และ “ความรับผิดชอบ” โครงสร้างโค้ดตามมาทีหลัง
ถ้าแบ่ง folder แล้วแต่ทุกทีมยังดึงข้อมูลสินค้าจาก MDM ชุดเดียวกัน — นั่นยังไม่ใช่ Bounded Context แค่เป็น folder จัดระเบียบ
มุมของแต่ละบทบาท
Dev: แต่ละ Context มีข้อมูลของตัวเอง ทำให้ทีม Cost Accounting แก้ไขข้อมูลต้นทุนโดยไม่กระทบทีม Procurement ได้ — ไม่ต้องกลัวว่าแก้อะไรแล้วจะพังข้ามทีม ซึ่งตรงกันข้ามกับที่เกิดขึ้นใน scenario
BA: ก่อนเขียน requirement ถามก่อนว่า “นี่อยู่ใน Context ไหน?” ถ้า requirement เดียวกันส่งผลต่อ 2 Context — นั่นคือ signal ว่ามีขอบเขตที่ยังไม่ชัด ต้องคุยกับ Domain Expert แต่ละฝ่ายให้ชัดก่อน
Domain Expert: เมื่อแต่ละแผนกมี Context ของตัวเอง คุณสามารถปรับข้อมูลในส่วนของตัวเองได้โดยตรงโดยไม่ต้องรอ approval จากแผนกอื่น — เหมือนกับที่เจ้าหน้าที่ฝ่าย Sales ควรแก้ไขราคาขายได้เองโดยไม่กระทบ Cost Accounting
Sub-Domain กับ Bounded Context — ไม่จำเป็นต้อง 1:1
ตอนนี้รู้จักทั้ง Sub-Domain และ Bounded Context แล้ว ความสัมพันธ์ระหว่างสองสิ่งนี้มีได้หลายรูปแบบ
1:1 — รูปแบบที่ง่ายที่สุด เริ่มต้นด้วยแบบนี้เสมอ
Sales Sub-Domain → sales-management/ (Bounded Context)
1:หลาย — เมื่อ Sub-Domain ใหญ่เกินไปหรือทีมต้องการแยก
Sales Sub-Domain
├── sales-order-management/ ← Bounded Context (หัวข้อการแก้ปัญหา)
│ แยกเพราะ scale ต่างกัน หรือทีมแยก
└── sales-pricing/ ← Bounded Context (หัวข้อการแก้ปัญหา)
pricing logic ซับซ้อนพอจะแยก Context
หลาย:1 — เมื่อโปรเจกต์เล็กหรือทีมไม่พอ (ไม่แนะนำถ้าหลีกเลี่ยงได้)
Sales + Promotion Sub-Domain → sales-management/
← มักเกิดตอนเริ่มต้น แต่ต้องระวังว่า Ubiquitous Language จะปนกัน
กฎง่ายๆ: เริ่มต้นด้วย 1:1 เสมอ แยกเพิ่มเมื่อมีเหตุผลจริงๆ เท่านั้น เช่น ทีมต้องการแยก scale ต่างกัน หรือ Domain Expert คนละคนดูแลคนละส่วน
Context Mapping — “แต่ละ Context คุยกันยังไง”
Bounded Context ที่ดีมี boundary ชัดเจน แต่ระบบจริงๆ Context ต้องคุยกัน — Procurement ต้องรู้ว่า Stock มีพอสำหรับสั่งซื้อเพิ่มไหม, Cost Accounting ต้องรู้ว่า Procurement จัดซื้อมาในราคาเท่าไหร่
Context Mapping คือการกำหนดว่าแต่ละ Context เชื่อมต่อกันยังไง และ ใครพึ่งพาใคร
มี pattern อยู่หลายตัว แต่ 3 ตัวนี้คือที่พบบ่อยที่สุด
Shared Kernel
หลาย Context แชร์ข้อมูลบางส่วนร่วมกัน เปลี่ยนอะไรในส่วนนั้นต้องได้รับ agreement จากทุก Context ที่ใช้งาน
ใน ERP ของเรา ProductMaster ใน MDM คือ Shared Kernel — ทุก Context ใช้ ProductId จากที่นี่เพื่ออ้างอิงสินค้าชิ้นเดียวกัน
MDM Context
ProductMaster { ProductId, name, unit, category }
│
├──── ProductId ────→ ProcurementProduct { ProductId, supplierId, ... }
├──── ProductId ────→ SalesProduct { ProductId, sellingPrice, ... }
└──── ProductId ────→ CostProduct { ProductId, costPrice, ... }
ใช้เมื่อ: หลาย Context ต้องการ identity หรือข้อมูลพื้นฐานร่วมกัน เช่น ProductId ที่ใช้อ้างอิงข้าม Context
Risk: ถ้าต้องเปลี่ยน schema ของ ProductMaster เช่น เปลี่ยนรูปแบบ ProductId ทุก Context ที่ใช้งานจะได้รับผลกระทบพร้อมกัน ต้องประสานงานทุกครั้ง ดังนั้น Shared Kernel ควร share เฉพาะสิ่งที่จำเป็นจริงๆ เท่านั้น
Customer-Supplier
Scenario: Cost Accounting ต้องการราคาสั่งซื้อของสินค้ารายตัว เพื่อนำไปคำนวณต้นทุน แต่ข้อมูลนั้นอยู่ใน Procurement Context ทั้งหมด
ในความสัมพันธ์นี้
- Procurement คือ Supplier — เป็นเจ้าของข้อมูลราคาสั่งซื้อ
- Cost Accounting คือ Customer — ต้องการข้อมูลนั้นไปใช้งาน และสามารถบอก Procurement ได้ว่าต้องการในรูปแบบไหน
Procurement Context (Supplier) ───→ Cost Accounting Context (Customer)
ใช้เมื่อ: มี dependency ทิศทางเดียวที่ชัดเจน และ Customer มีอำนาจ negotiate ได้ว่าต้องการข้อมูลแบบไหน
ข้อแตกต่างจาก Shared Kernel: แต่ละ Context ยัง own ข้อมูลของตัวเองอยู่ ไม่มีการ share ข้อมูลกลาง — Procurement แค่ expose ข้อมูลให้ Cost Accounting ใช้งานเท่านั้น
Anti-Corruption Layer (ACL)
Scenario: Supplier ภายนอกส่งใบเสนอราคาเข้ามาในรูปแบบของตัวเอง โดยรวม VAT และค่าขนส่งไว้ด้วยกัน แต่ Procurement Context ต้องการเฉพาะ purchasePrice ที่ไม่รวม VAT และค่าขนส่งแยกต่างหาก
Supplier ภายนอก ──→ [ACL: แยก VAT และค่าขนส่ง] ──→ Procurement Context
{ แปลงให้ตรงกับ ProcurementProduct {
totalPrice, ProcurementProduct ProductId,
vatIncluded, ─────────────────────→ purchasePrice,
shippingFee shippingFee
} }
ใครเป็นคนทำ ACL — ฝั่งส่งหรือฝั่งรับ?
ACL เป็นความรับผิดชอบของ ฝั่งรับ เสมอ เพราะ Supplier ภายนอกไม่รู้และไม่ควรต้องรู้ว่า Procurement Context ต้องการข้อมูลในรูปแบบไหน การแปลงเป็นหน้าที่ของคนที่ต้องการใช้ข้อมูล ไม่ใช่คนที่ส่งมา
Supplier ภายนอก Procurement Context
(ส่งข้อมูลในรูปแบบตัวเอง) (รับผิดชอบ ACL เอง)
│ │
└──→ [ACL อยู่ตรงนี้] ──→ ProcurementProduct
ACL เหมาะกับความสัมพันธ์แบบไหน?
ACL เหมาะเมื่อมีครบ 3 เงื่อนไขนี้
| เงื่อนไข | ตัวอย่างในเรา |
|---|---|
| ฝั่งส่งเป็น external หรือ legacy ที่ควบคุมไม่ได้ | Supplier ภายนอกที่เราบังคับรูปแบบไม่ได้ |
| model ของสองฝั่งต่างกันมาก | Supplier ส่ง price รวม VAT แต่เราต้องการแยก |
| ไม่อยากให้ concept ของฝั่งส่งรั่วเข้า Context | ไม่อยากให้ Procurement Context รู้เรื่อง VAT structure ของ Supplier |
ถ้าไม่ครบ 3 เงื่อนไข — Customer-Supplier น่าจะเพียงพอแล้ว
Series Scale-Ready ใช้ ACL concept ในรูปแบบของ ServiceClient — แต่ละ Context คุยกันผ่าน interface ที่นิยามไว้ใน layer กลาง ไม่ depend กันตรงๆ
ตัวอย่างจริง — แบ่ง ERP ออกเป็น Sub-Domain และ Bounded Context
ลองดูภาพรวมของ ERP ที่แสดง hierarchy ครบตั้งแต่ระดับ Domain ลงไปถึง Bounded Context
ระดับ Sub-Domain (Problem Space) — แบ่งตาม Area of Business:
Domain: ระบบ ERP บริษัทผลิตสินค้า
│
├── MDM Sub-Domain (Generic Domain)
├── Procurement Sub-Domain (Supporting Domain)
├── Stock Sub-Domain (Supporting Domain)
├── Sales Sub-Domain (Core Domain*)
└── Cost Accounting Sub-Domain (Supporting Domain)
ระดับ Bounded Context (Solution Space) — วิธีที่เรา model ใน code:
Domain: ระบบ ERP บริษัทผลิตสินค้า
│
├── MDM Sub-Domain (Generic Domain)
│ └── mdm/ (Bounded Context)
│ Product: { id, name, unit, category }
│ → ข้อมูลพื้นฐานที่ทุกระบบใช้อ้างอิง
│ → พิจารณาซื้อ off-the-shelf ก่อนเสมอ
│
├── Procurement Sub-Domain (Supporting Domain)
│ └── procurement/ (Bounded Context)
│ Product: { id, supplierId, leadTime, minOrderQty }
│ Supplier: { id, name, contractTerms, rating }
│ → สนใจว่าซื้อจากใคร เงื่อนไขคืออะไร
│
├── Stock Sub-Domain (Supporting Domain)
│ └── stock/ (Bounded Context)
│ Product: { id, stockLevel, warehouseLocation, reorderPoint }
│ → สนใจว่ามีเหลือเท่าไหร่ เก็บอยู่ที่ไหน
│
├── Sales Sub-Domain (Core Domain*)
│ └── sales-management/ (Bounded Context)
│ Product: { id, sellingPrice, discount, description }
│ Customer: { id, name, creditLimit, salesHistory }
│ → กลยุทธ์ราคาและการขาย
│
└── Cost Accounting Sub-Domain (Supporting Domain)
└── cost-accounting/ (Bounded Context)
Product: { id, costPrice, overhead, margin }
→ สนใจแค่ต้นทุนและกำไร — ไม่สนใจ stock หรือ supplier
ในตัวอย่างนี้แต่ละ Sub-Domain มี 1 Bounded Context ซึ่งเป็นจุดเริ่มต้นที่ดีที่สุด เพิ่มความซับซ้อนเมื่อมีเหตุผลจริงๆ เท่านั้น
สังเกตว่า Product ปรากฏในทุก Context แต่มีข้อมูลต่างกัน
- Procurement สนใจ supplier และ lead time
- Stock สนใจ stockLevel และ warehouseLocation
- Sales สนใจ sellingPrice และ discount
- Cost Accounting สนใจ costPrice และ margin
ไม่มี MDM ที่รู้ทุกอย่าง — แต่ละ Context เปลี่ยนได้อิสระโดยไม่กระทบกัน
สรุปตามบทบาท
Developer — “สิ่งที่คุณทำได้จากวันนี้”
ก่อน implement อะไรใหม่ ให้ถามตัวเองก่อนเสมอว่า:
- “นี่อยู่ใน Context ไหน?” — ถ้าตอบไม่ได้ หยุดก่อน
- “ชื่อที่ BA และ Domain Expert ใช้คืออะไร?” — ใช้ชื่อนั้นในโค้ดด้วย อย่าแปล
- “Entity นี้ใน Context นี้มีอยู่แล้วหรือยัง?” — อย่าสร้างซ้ำ
ถ้าตอบคำถามเหล่านี้ไม่ได้ → คุยกับ BA ก่อนเขียนโค้ด เพราะถ้า BA ยังไม่ชัด Dev ก็จะเจอปัญหาเดิมกับที่เกิดใน scenario
Business Analyst — “สิ่งที่คุณทำได้จากวันนี้”
BA คือคนที่ป้องกันปัญหาใน scenario ได้ตั้งแต่แรก ถ้าทำสิ่งเหล่านี้ก่อนสรุป requirement
- นั่งคุยกับ Domain Expert แต่ละแผนกแยกกัน และถามให้ชัดว่า “คำนี้ใน Context ของคุณหมายถึงอะไรกันแน่?”
- ตรวจสอบว่าคำเดียวกันที่หลายแผนกใช้ หมายถึงสิ่งเดียวกันจริงไหม — ถ้าคำตอบต่างกัน นั่นคือ signal ว่ามีหลาย Context
- ระบุใน requirement ให้ชัดว่า requirement นี้อยู่ใน Context ไหน ไม่ใช่แค่บอกว่าต้องการอะไร
Domain Expert — “สิ่งที่คุณทำได้จากวันนี้”
Domain Expert คือคนที่รู้จัก business ลึกที่สุด และเป็นคนที่ DDD ให้ความสำคัญมากที่สุด
- อธิบายให้ชัดเสมอว่าคำที่ใช้หมายถึงอะไรใน context ของคุณ อย่าสมมติว่าทุกคนเข้าใจเหมือนกัน
- ถ้า BA เขียน requirement แล้วรู้สึกว่าไม่ตรงกับสิ่งที่ต้องการ — บอกทันที เพราะยิ่งปล่อยผ่านนานยิ่งแก้ยาก
- ถ้าพบว่าระบบที่ได้ไม่ตรงกับ business จริงๆ — ปัญหาอาจเริ่มมาจากขั้นตอน requirement ไม่ใช่ขั้นตอน development
บทความถัดไป
ตอนนี้รู้แล้วว่า Strategic Design ทำงานยังไง — แบ่ง Domain, ระบุ Sub-Domain, กำหนดหัวข้อการแก้ปัญหา, กำหนดขอบเขต Bounded Context, วาง Ubiquitous Language และวาด Context Mapping
สรุป flow ของ Strategic Design ที่ทำไปในบทความนี้
Strategic Design
1. ระบุ Domain
2. แบ่ง Sub-Domain + ประเภท (Core/Supporting/Generic)
3. กำหนดหัวข้อการแก้ปัญหา
4. กำหนดขอบเขต (Bounded Context)
5. วาง Ubiquitous Language เบื้องต้น
6. วาด Context Mapping
บทความถัดไปจะเดินเข้าไปใน Bounded Context แล้วออกแบบว่าข้างในมีอะไรบ้าง — Entity, Value Object, Aggregate, Domain Service, Repository, Application Service และ Domain Event รวมถึง Business Rule และ Invariant คืออะไรและต่างกันยังไง
→ Prerequisite 2: DDD Tactical Design
FAQ
Q: DDD กับ Microservices เกี่ยวกันยังไง?
DDD กับ Microservices ไม่ใช่สิ่งเดียวกัน และไม่ต้องใช้คู่กันเสมอ
- DDD คือ design decision — ว่าจะแบ่ง boundary ตรงไหน และภาษาที่ใช้คืออะไร
- Microservices คือ deployment decision — ว่าจะ deploy แยก process ไหม
ทำ DDD ได้โดยไม่ต้องทำ Microservices และทำ Microservices โดยไม่ใช้ DDD ก็ได้ (แต่มักเจ็บปวดกว่า) Bounded Context ที่ดีคือ input สำคัญเมื่อถึงเวลาตัดสินใจว่า service ไหนพร้อมแยก
Q: ต้องใช้ DDD ทั้งระบบไหม หรือแค่บางส่วนได้?
ทำบางส่วนได้ และนั่นคือแนวทางที่แนะนำด้วย
เริ่มจาก Core Domain ก่อน เพราะนั่นคือจุดที่ investment คุ้มค่าที่สุด ส่วน Generic Domain เช่น Authentication ซื้อ off-the-shelf แล้วไม่ต้อง apply DDD เลยก็ได้ ไม่จำเป็นต้อง migrate ทั้งระบบพร้อมกัน
Q: Ubiquitous Language ต้องเป็นภาษาอังกฤษเสมอไหม?
ไม่จำเป็น ต้องเป็น “ภาษาที่ทีมใช้จริงและสื่อสารกันได้ชัดเจน”
ถ้าทีมทำงานเป็นภาษาไทยและทุกคนใช้คำว่า “ใบสั่งซื้อ” ก็ใช้ได้ แต่ในทางปฏิบัติโค้ดส่วนใหญ่เป็นภาษาอังกฤษ การตกลงกันว่า “ใบสั่งซื้อ = PurchaseOrder” แล้วทุกคนใช้คำนี้ทั้งในการประชุมและในโค้ด คือ Ubiquitous Language แล้ว
Q: ทีมเล็ก 3-5 คน ควรใช้ DDD ไหม?
ขึ้นอยู่กับ scope และ lifecycle ของ product
- Prototype หรือ side project ที่ไม่แน่ใจว่าจะ production — ไม่ต้องยุ่ง
- Product ที่มีแผนโต — เริ่มจาก Ubiquitous Language ก่อน ค่าใช้จ่ายต่ำมาก แต่ลด miscommunication ได้ชัดเจน
- Bounded Context เริ่มมีประโยชน์ชัดขึ้นเมื่อทีมโตเกิน 5-7 คน หรือเมื่อระบบมีหลาย Domain ที่ชัดเจน
Q: Strategic Design กับ Tactical Design ต้องทำพร้อมกันไหม?
ไม่ต้อง และไม่ควรด้วย
เริ่ม Strategic Design ก่อนเสมอ — รู้ว่าจะแบ่ง boundary ตรงไหน ก่อนจะคิดว่าจะเขียนโค้ดในนั้นยังไง ถ้าเริ่ม Tactical Design โดยที่ยังไม่รู้ว่า Context คืออะไร มักจะได้โค้ดที่ดีในระดับไฟล์ แต่ระบบโดยรวมยังพัวพันกันอยู่
Q: ในโปรเจกต์จริงๆ ควรเริ่มแบ่ง Bounded Context จากตรงไหน?
เริ่มจากการนั่งคุยกับ Domain Expert แต่ละฝ่าย แล้วสังเกตว่าคำไหนมีความหมายต่างกันในแต่ละกลุ่ม — นั่นคือ signal ที่บอกว่าต้องแบ่ง Context
ใน scenario ของเรา คำว่า “สินค้า” ที่ Procurement, Sales และ Cost Accounting ใช้ต่างกัน คือสัญญาณแรกที่บอกว่าต้องแบ่งเป็น 3 Context ก่อนออกแบบระบบใดๆ
เทคนิคที่นิยมใช้ในการค้นหา Context คือ Event Storming — นั่งระดมสมองกับ Domain Expert ทุกฝ่ายพร้อมกัน แล้วดูว่า event ไหนที่แต่ละฝ่ายมองต่างกัน
Q: ถ้า Domain Expert แต่ละแผนกไม่เห็นด้วยกันเรื่อง Ubiquitous Language ต้องทำยังไง?
นั่นไม่ใช่ปัญหา — แต่เป็น signal ที่ดีมากที่บอกว่ากำลังเจอ boundary ระหว่าง Context
ถ้า Domain Expert ฝ่าย Sales และฝ่าย Procurement ไม่เห็นด้วยว่า “ราคา” คืออะไร นั่นหมายความว่าทั้งสองฝ่ายอยู่คนละ Context และควรมี Ubiquitous Language ของตัวเองในแต่ละ Context แทนที่จะบังคับให้ใช้คำเดียวกัน ความขัดแย้งเรื่องภาษาคือเครื่องมือที่ช่วย discover Bounded Context ได้เร็วที่สุด