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

รักษา​ความ​บริสุทธิ์​ของ Model ด้วย Bounded Context

ใน app ขนาด​ใหญ่​และ​ซับซ้อน คุณ​จะ​พบ​ว่า​มี modelModelระบบ​ของ​แนวคิด​ที่​อธิบาย​แง่​มุม​สำคัญ​ของ Domain เพื่อ​ใช้​แก้​ปัญหา ไม่ใช่​ภาพ​สะท้อน​ความ​จริง​ทั้งหมด แต่​เป็น “การ​ตีความ​ที่​มี​ประโยชน์” ของ​ความ​จริงStrategic Design หลาย​ตัว​ทำงาน​อยู่​พร้อม​กัน แต่ละ​ตัว​ถูก​สร้าง​เพื่อ​แทน​พื้นที่​หนึ่ง​ของ problem domain โดย​เฉพาะ ใน​อุดมคติ​เรา​อยาก​ให้​มี1 model ต่อ​หนึ่ง SubdomainSubdomainส่วน​ย่อย​ของ Domain ใหญ่ เช่น Domain e-commerce แตก​เป็น subdomain การ​สั่ง​ซื้อ การ​ชำระ​เงิน คลัง​สินค้า ฯลฯ อยู่​ใน “พื้นที่​ปัญหา” (problem space)Strategic Design แต่​ใน​ความ​เป็น​จริง​ไม่​เป็น​เช่น​นั้น​เสมอ​ไป — บาง subdomain ที่​ซับซ้อน​อาจ​มี​หลาย model และ​บาง model ก็​คร่อม​หลาย subdomain

ไม่​ว่า​จะ​มี​กี่ model สุดท้าย​แล้ว​ทั้งหมด​ต้อง​สื่อสาร​กัน​เพื่อ​ทำให้​ระบบ​ทำงาน​สำเร็จ และ​จุด​ที่​อันตราย​ที่สุด​คือ​เมื่อ​ทีม นำ model มา​รวม​กัน​โดย​ไม่​เข้าใจ​ว่า​แต่ละ​ตัว​ใช้ได้​ใน​บริบท​ไหน แนวคิด​และ​ตรรกะ​จึง​ปน​เปื้อน​กัน ทำให้ model เริ่ม​พร่า​เลือน​และ​สูญเสีย​ความ​ชัดเจน บท​นี้​จะ​พา​ไป​รู้จัก​เครื่องมือ​ที่ DDD ใช้​ปกป้อง​ความ​บริสุทธิ์​ของ​แต่ละ model นั่น​คือ Bounded ContextBounded Contextขอบเขต​ที่​ชัดเจน (มัก​เป็น​ระบบ​ย่อย​หรือ​ทีม​หนึ่ง) ที่ model หนึ่ง​ใช้ได้​และ​มี​ความหมาย​แน่นอน ภายใน​ขอบเขต​นี้ Ubiquitous Language สอดคล้อง​กัน 100%Strategic Design


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

ทุก​ครั้ง​ที่​เพิ่ม use case หรือ insight ใหม่​เข้าไป จำนวน​แนวคิด​และ​การ​พึ่งพา (dependency) ภายใน model ก็​เพิ่ม​ขึ้น ความ​ซับซ้อน​พุ่ง​สูง​ขึ้น จัด​วาง​แนวคิด​ผิด​ที่​ได้​ง่าย และ​ค้นหา​สิ่ง​ที่​ต้องการ​ก็​ยาก​ขึ้น ยิ่ง​ระบบ​โต ปัญหา​ยิ่ง​รุนแรง ความเร็ว​ใน​การ​เพิ่ม feature ใหม่​ก็​ช้า​ลง​เรื่อยๆ — นี่​คือ​เส้นทาง​ตรง​สู่ Big Ball of MudBig Ball of Mudระบบ​ที่​ปนเป​กัน​ยุ่งเหยิง​ไม่มี​ขอบเขต​ชัด ให้​ลาก​เส้น​ล้อม​ไว้​แล้ว​ยอมรับ​สภาพ ไม่​พยายาม​ทำ model ละเอียด​ใน​นั้น และ​ระวัง​ไม่​ให้​ลาม​ไป context อื่นStrategic Design

code ที่​ซับซ้อน​เป็น​เพียง​ปัญหา​เดียว อีก​ปัญหา​ใหญ่​คือ ต้นทุน​การ​ประสาน​งาน (collaboration overhead) เมื่อ​ทีม​หนึ่ง​จะ​ปล่อย feature ต้อง​ไป​เช็กกับ​ทีม​อื่น​ว่า deploy ได้​ไหม ทีม​แรก​ต้อง​รอ หรือ​ไม่​ก็​ต้อง​ใช้​กลยุทธ์ branching ที่​ซับซ้อน ยิ่ง​มี​หลาย​ทีม​ทำงาน​บน model เดียว ความ​ชัดเจน​ยิ่ง​เจือจาง

code ซ้ำ​กัน​ได้ ถ้า​แนวคิด​เป็น​คนละ​เรื่อง

หลาย​คน​กังวล​ว่าการ​มี​หลาย model จะ​ทำให้​เกิด code ซ้ำซ้อน แต่​ให้​โฟกัส​ที่​ประโยชน์​ของ​การ ตัด dependency ระหว่าง​ทีม ออก​ไป — การ​มี code ที่​ดู​คล้าย​กัน​ใน2 model นั้น​ยอมรับ​ได้ เพราะ​แท้จริง​แล้ว​มัน​คือ​คนละ​แนวคิด​กัน

หนึ่ง​ใน “บทเรียน​แห่ง​การ​ตื่น​รู้” ของ​คน​ที่​ทำ DDD คือ​การ​ตระหนัก​ว่า บาง​แนวคิด​ใน​ระบบ​ฟัง​ดู​คล้าย​กัน​มาก — บาง​ครั้ง​ใช้​ชื่อ​เดียวกัน​ทุก​ตัว​อักษร — แต่​กลับ หมาย​ถึง​คนละ​สิ่ง​สำหรับ​คนละ​แผนก เช่น​คำ​ว่า “Ticket” ใน​แผนก​ขาย (จำนวน​ที่นั่ง วัน​ที่ ราคา สถานที่) กับ “Ticket” ใน​แผนก​บริการ​ลูกค้า (ความ​รุนแรง เวลา​ที่​แจ้ง หมวด​หมู่)

รูปที่ 6-3: คำว่า Ticket หมายถึงคนละสิ่งในคนละบริบท — ฝั่งขายสนใจที่นั่ง/ราคา/สถานที่ ส่วนฝั่งบริการลูกค้าสนใจความรุนแรง/เวลา/หมวดหมู่ ทีมพัฒนาจึงต้องเลือกว่ากำลังพูดถึง Ticket ในบริบทไหน

เมื่อยอมรับ​ว่า​ชื่อ​เดียวกัน​มี​ความหมาย​ต่าง​กัน​ใน​ต่าง​บริบท ก็​จะ​เข้าใจ​ได้​ง่าย​ขึ้น​ว่า หลาย model เล็กๆ ดี​กว่า model ใหญ่​ก้อน​เดียว และ​คุย​กับ domain expert ได้​ตรง​ประเด็น​กว่า — คุย​กับ​ผู้จัดการ​ฝ่าย​ขาย​เรื่อง Ticket คุณ​รู้​ว่า​เขา​สนใจ​ราคา​กับ​สถานที่ ส่วน​ผู้จัดการ​ฝ่าย​บริการ​สนใจความ​รุนแรง​และ​หมวด​หมู่​ของ​ปัญหา

กับดัก​คลาสสิก​คือ Product entity ทาง​กายภาพ​หนึ่ง​ตัว​มัก​ถูก​จับ​เป็น “1 class” ใน code ทั้งที่จริงๆ มัน​แทน​หลาย​แนวคิด​ที่หมาย​ถึง​คนละ​เรื่อง​ใน​คนละ​บริบท สำหรับ​ทีม​จัด​ซื้อ (Procurement) “product” คือ​สิ่ง​ที่​ต้อง​ซื้อ​เข้า​มา​โดย​คำนึง​ถึง​กำไร​และ lead time ที่​ยอมรับ​ได้ ส่วน​สำหรับ​ทีม​ขาย (Sales) “product” คือ​สิ่ง​ที่​มี​รูปภาพ ตาราง​ขนาด และ​หมวด​หมู่​การ​ขาย — ซึ่ง​ทีม​จัด​ซื้อ​ไม่​ได้​ให้​ความ​สนใจ​เลย แม้​จะ​เป็น entity ตัว​เดียวกัน​ใน​โลก​จริง

เมื่อ developer ยัด​ทุก​ความ​ต้องการ​ลง​ใน class Product เดียว ผล​คือ god object — class ยักษ์​ที่​ทุก​บริบท​อยาก​แก้​ด้วย​เหตุผล​ต่าง​กัน​โดย​สิ้นเชิง ละเมิด Single Responsibility Principle (SRP) อย่าง​จัง

รูปที่ 6-6: class Product ในรูปแบบ god object antipattern — ทุกบริบท (Inventory, Fulfillment, Pricing, Shipping, Procurement, Personalization) ดึง method ของตัวเองมายัดใน class เดียว การแก้ตรรกะของ subdomain หนึ่งจึงกระเพื่อมไปกระทบ subdomain อื่นที่ไม่เกี่ยวข้อง

class ที่​ทุก​บริบท​ใช้​ร่วม​กัน​คือ BBoM ขนาด​ย่อม

class Product ที่ share ร่วม​กัน​ใน​รูป​ที่ 6-6 คือ​ตัวอย่าง​ชั้น​ดี​ของ Big Ball of Mud การ​แก้​ตรรกะ​ใน subdomain หนึ่ง​จะ​มี ripple effect ที่​ไม่​พึง​ประสงค์​ไป​ยัง subdomain อื่น​ที่​ไม่​เกี่ยวข้อง​กัน​เลย เพราะ code ถัก​ทอ​พัน​กัน​และ​ไม่มี​ขอบเขต​ความ​รับผิดชอบ​ที่​ชัดเจน

ทางออก​ที่​ดี​กว่า​คือ​ให้​แต่ละ​บริบท — Promotion, Allocation, Loyalty, Shipping — มี model ของ​ตัวเอง แต่ละ model มี​ตัวแทน​ของ product ที่​ไม่​ซ้ำ​กัน และ​ตอบ​เฉพาะ​ความ​ต้องการ​ของ​บริบท​ตัวเอง​เท่านั้น

🛒 Product คนละ​ตัว​ใน​แต่ละ​บริบท e-commerce

ใน​ระบบ​ช้อป​ปิ้ง​ออนไลน์ “สินค้า” ตัว​เดียว​มี​ได้​หลาย​แง่​มุม: ฝั่ง Catalog/Sales สนใจ​รูปภาพ รายละเอียด รีวิว และ​ราคา​ขาย; ฝั่ง Inventory สนใจ​จำนวน​คง​เหลือ​และ​ตำแหน่ง​จัด​เก็บ​ใน​คลัง; ฝั่ง Pricing สนใจ​ส่วนลด​และ​กฎ​โปรโมชัน; ฝั่ง Shipping สนใจ​น้ำหนัก​และ​ขนาด​เพื่อ​คำนวณ​ค่า​จัด​ส่ง เมื่อใด​ที่​พยายาม​ยัด​ทุก​อย่าง​ลง​ใน class Product เดียว ทุก​ทีม​จะ​แย่ง​กัน​แก้ file เดียวกัน​ทันที

  • การ​ต่อ​กับ legacy หรือ third-party — เมื่อ model มี​ขนาด​เล็ก ไม่ใช่​ทุก model ที่​ต้อง​ไป​แตะ code เก่า model ใหม่​ที่​สะอาด​จึง​ไม่​ถูก​ข้อ​จำกัด​ของ legacy มา​บีบรัด​ความ​ชัดเจน​ของ design
  • domain model ไม่ใช่ enterprise model — การ​มี model เดียว​ของ​ทั้ง​องค์กร​มี​ประโยชน์​กับ​งาน BI และ reporting แต่​ไม่​เหมาะ​กับ​การ​สร้าง domain model ที่​วิวัฒน์​ได้​และ​ส่ง​มอบ​คุณค่า​ได้​บ่อยๆ เรา​ใช้​ทั้ง​สอง​อย่าง​คู่​กัน​ได้ — model เฉพาะ​ของ​แต่ละ​บริบท บวก​กับ enterprise model สำหรับ reporting

Bounded ContextBounded Contextขอบเขต​ที่​ชัดเจน (มัก​เป็น​ระบบ​ย่อย​หรือ​ทีม​หนึ่ง) ที่ model หนึ่ง​ใช้ได้​และ​มี​ความหมาย​แน่นอน ภายใน​ขอบเขต​นี้ Ubiquitous Language สอดคล้อง​กัน 100%Strategic Design คือ​สิ่ง​ที่ นิยาม​ขอบเขต​การ​ใช้งาน​ของ model มัน​บอก​ชัด​ว่า model นี้​ใช้​ทำ​อะไร ควร​สอดคล้อง​ที่ไหน และ​ควร เพิกเฉย ต่อ​อะไร แนวคิด​ของ domain ที่​อยู่​นอก​บริบท​จะ​ไม่​มากวน​ปัญหา​ที่ model นี้​ถูก​ออกแบบ​มา​แก้ มัน​ทำให้​ชัดเจน​ต่อ​ทุก​ทีม​ว่า model รับผิดชอบ​อะไร​และ​ไม่​รับผิดชอบ​อะไร

คำ​ว่า “context” สำคัญ​มาก​ใน DDD เมื่อ​พูด​ถึง product ใน​บริบท​ของ subdomain คลัง​สินค้า เรา​ไม่​จำเป็น​ต้อง​เรียก​มัน​ว่า “product ที่​ส่ง​มอบ​ได้” — มัน​ก็​คือ “product” ใน​บริบท​ที่​นิยาม​ไว้​ชัดเจน​แล้ว สิ่ง​สำคัญ​คือ​เวลา​คุย​กับ domain expert หรือ​เพื่อน​ร่วม​ทีม ทุก​คน​ต้อง​รู้​ว่า​กำลัง​พูด​อยู่​ใน​บริบท​ไหน

ใน Model-Driven Design เรา​ได้​รู้จัก Ubiquitous LanguageUbiquitous Languageภาษา​กลาง​ที่​ทุก​คนใน​ทีม (ทั้ง​นัก​พัฒนา​และ​ผู้เชี่ยวชาญ​ธุรกิจ) ใช้​ร่วม​กัน อิง​กับ domain model โดยตรง คำ​เดียวกัน​ต้อง​หมาย​ถึง​สิ่ง​เดียวกัน และ​สะท้อน​ลง​ใน codeStrategic Design (UL) และ​ความ​สำคัญ​ของ model ที่​นิยาม​ใน​บริบท​ซึ่ง​ปลอด​จาก​ความ​กำกวม​ทาง​ภาษา Bounded Context ต่อยอด​แนวคิด “model in context” ด้วย​การ ห่อ​มัน​ไว้​ใน​ขอบเขต​ความ​รับผิดชอบ​ที่​เป็น implementation จริง​ใน​เชิง​เทคนิค ต่าง​จาก context ที่​เป็น​นามธรรม Bounded Context บังคับ​ให้การ​สื่อสาร​เกิด​ขึ้น​ใน​แบบ​ที่​ไม่​ลด​ทอน​ความ​บริสุทธิ์​ของ model

Bounded Context ก่อน​สิ่ง​อื่น​ใด​คือ ขอบเขต​ทาง​ภาษา (linguistic boundary) — เวลา​คุย​กับ domain expert หาก​คุณ​รู้สึก​ว่า​ประโยค​หนึ่ง​จำเป็น​ต้อง​มี “บริบท” มา​กำกับ​เสมอ นั่น​คือ​สัญญาณ​ชัดเจน​ว่า​คุณ​ต้อง​แยก model นั้น​ออก​มา​ไว้​ใน bounded context ของ​มัน​เอง

Bounded Context = ด่าน​ตรวจ​ชายแดน

มอง Bounded Context ให้​เหมือน​พรมแดน​ของ​ประเทศ — ไม่มี​สิ่ง​ใด​ผ่าน​เข้า​มา​ได้​หาก​ไม่​ผ่าน​ด่าน​ตรวจ​และ​ถูกต้อง​ตาม​กฎ เช่น​เดียว​กับ​ที่​คนละ​ประเทศ​พูด​คนละ​ภาษา code ใน​แต่ละ bounded context ก็​พูด UL ของ​ตัวเอง หนึ่ง​ใน​หัวใจ​สำคัญ​ที่สุด​ของ DDD คือ การ​ปกป้อง​ขอบเขต (protecting the boundary) — เพราะ​หาก​ไม่​บังคับ​ใช้​ขอบเขต​รอบ model สุดท้าย​แล้ว​คุณ​จะ​ได้​กอง code ที่​ลุกลาม​ไป​ทุก​ทิศ​จน​กลาย​เป็น BBoM

รูปที่ 6-9: นิยาม model แต่ละตัวภายในบริบทของมันเอง — "product" กลายเป็นแนวคิดที่เล็กลงและโฟกัสกว่าเมื่อถูกจำกัดให้อยู่ในบริบทเฉพาะ (Sales, Procurement, Pricing, Inventory, Fulfillment) แทนที่จะเป็นก้อนเดียวที่บิดเบี้ยวเพื่อรองรับทุกสถานการณ์


ความ​จำเป็น​ของ bounded context ชัดเจน​ใน​ระบบ​ใหญ่ แต่​การ หา ว่า​ขอบเขต​ควร​อยู่​ตรง​ไหน​นั้น​ท้าทาย ข่าวดี​คือ​มัน​ไม่ใช่​การ​ตัดสิน​ใจ​ล่วง​หน้าที่​ต้อง​ถูกต้อง​แม่นยำ​ตั้งแต่​แรก เมื่อ​เรียนรู้ domain มาก​ขึ้น เรา​ปรับ​ขอบเขต​ได้​เสมอ ปัจจัย​ที่​ใช้​เป็น​แนวทาง​มี​ดังนี้

นี่​คือ​ไกด์​ที่​สำคัญ​ที่สุด ถ้า​คำ​หรือ​วลี​หนึ่ง​เปลี่ยน​ความหมาย​เมื่อ​ข้าม​ไป​อีก​พื้นที่ ตรง​รอย​ต่อ​นั้น​มัก​คือ​ขอบเขต​ของ bounded context — ถ้า​คำ​ว่า product มี​หลาย​ความหมาย​ภายใน model เดียว ก็​ควร​แยก​เป็น​อย่าง​น้อย2 bounded context ที่​แต่ละ​อัน​มี​นิยาม​ของ product เพียง​หนึ่ง​เดียว (ดู​รูป​ที่ 6-9) ใน​ทำนอง​กลับ​กัน คำ​เดียว​ที่หมาย​ถึง​หลาย​แนวคิด (เช่น Ticket ใน​รูป​ที่ 6-3) ก็​เป็น​ขอบเขต​ทาง​ภาษา​เช่น​กัน linguistic boundary คือ bounded context boundary

องค์กร​คือ​ระบบ​นิเวศ​ของ​บริการ​ที่​พึ่งพา​กัน แต่ละ​บริการ​มี​คำ​ศัพท์​ของ​ตัวเอง business capability จึง​มัก​เป็น​ตัว​บ่ง​ชี้​ขอบเขต​ทาง​ภาษา​ที่​ดี เช่น​ถ้า​มี​แผนก​ขาย​และ​แผนก​บริการ​ลูกค้า ก็​มี​แนวโน้ม​สูง​ว่า​จะ​มี sales context และ customer service context

ระวัง​กฎ​ของ Conway

อย่า model ตาม​โครงสร้าง​องค์กร​อย่าง​ไม่​ไตร่ตรอง — บาง​ครั้ง business capability ก็​ไม่​ได้​สอดคล้อง​กับ problem domain อย่าง​พอ​ดิบ​พอดี Melvin Conway เตือน​ไว้​ว่า

ระบบใดๆ ที่​องค์กร​ออกแบบ จะ​มี​โครงสร้าง​ที่​เป็น​สำเนา​ของ​โครงสร้าง​การ​สื่อสาร​ของ​องค์กร​นั้น​อย่าง​หลีก​เลี่ยง​ไม่​ได้

— Conway’s Law

ใช้​กฎ​นี้​เป็น​แนวทาง​ได้​สอง​ทาง: รู้​เท่าทัน​มัน​เพื่อ​ไม่​ให้​เผลอ model ตาม​โครงสร้าง​องค์กร​ล้วนๆ หรือ ปรับ​โครงสร้าง​องค์กร​ใหม่ ให้​สอดคล้อง​กับ​สถาปัตยกรรม​ที่​ต้องการ ทั้ง​สอง​ทาง​ต้อง​ใช้​ความ​พยายาม​สูง จึง​ต้อง​วางแผน​อย่าง​รอบคอบ

โครงสร้าง​ทีม​และ​ที่​ตั้ง​ทาง​กายภาพ​มี​อิทธิพล​ต่อ​ขอบเขต​มาก หลักการ​คือ หนึ่ง​ทีม​เป็น​เจ้าของ1 bounded context จัด​ทีม​เป็นกลุ่ม product/service รอบๆ bounded context (ไม่ใช่​ลอก​โครงสร้าง​แผนก) และ​ให้​ทีม​รับผิดชอบ​ครบ​ตั้งแต่ presentation → domain logic → persistence

ทีม​ขนาด 'สอง​พิซซ่า' ของ Amazon

Amazon มีนโยบาย​ไม่​ให้​ทีม​พัฒนา​ใหญ่​จน​พิซซ่า​สอง​ถาด​เลี้ยง​ไม่​อิ่ม เพราะ​ทีม​เล็ก​และ​โฟกัส​จะ ตัดสิน​ใจ​ได้​ดี​ขึ้น​และ​เร็ว​ขึ้น เมื่อ​ทีม​ดูแล​ทั้ง product และ​การ​ตัดสิน​ใจ​เชิง​เทคนิค​ด้วย​ตัวเอง ก็ iterate ได้​รวดเร็ว​โดย​ไม่​ต้อง​กังวล​ว่า​จะ​กระทบ​ทีม​อื่น — แต่​อย่า​ตัด​การ​สื่อสาร​ระหว่าง​ทีม​ทิ้ง​ทั้งหมด ควร​คง​ไว้​บ้าง​เพื่อ​แลกเปลี่ยน​ความ​รู้ เช่น cross-team pair programming และ session แบ่งปัน​สิ่ง​ที่​แต่ละ​ทีม​กำลัง​ทำ​อยู่

การ​ต่อ​กับ legacy หรือ​ระบบ​ภายนอก​ก็​เป็น​เหตุผล​ที่​ลาก​เส้น​ได้ แต่​ขอ​นำ​เสนอ​สิ่ง​ที่ ไม่ใช่ เกณฑ์: ขนาด ไม่มี​ตัวเลข​สัมบูรณ์​หรือ​สัมพัทธ์​ใด​บอก​ได้​ว่า bounded context ควร​มี​กี่ class หรือ​กี่​บรรทัด ขนาด​ขึ้น​อยู่​กับ problem domain ล้วนๆ บาง context จึง​ใหญ่ บาง context ก็​เล็ก

เพื่อ​เผย​ให้​เห็น​ว่า​มี​หลาย model ซ่อน​อยู่ Greg Young เสนอ​เกม​ชื่อ Context Game: เมื่อ​สงสัย​ว่า​คำ​หนึ่ง​กำกวม​หรือ​แบก​ความหมาย​เกิน​ตัว ให้​แบ่ง​คน​เป็นกลุ่ม​เล็ก (developer + business expert) แยก​ตาม​แผนก ให้​เวลา 20 นาที​นิยาม​ว่า​คำ​นั้น​หมาย​ถึง​อะไร​ใน​พื้นที่​ของ​เขา แล้วกลับ​มา​นำ​เสนอ จุด​ที่​แต่ละ​ฝ่าย​เห็น​ต่าง​กัน คือ​จุด​ที่​ต้อง​ลาก​เส้น​บริบท​และ​สร้าง model ใหม่


สอง​คำ​นี้​สับสน​กัน​บ่อย​ที่สุด สรุปสั้นๆ คือ subdomain อยู่​ใน problem space ส่วน bounded context อยู่​ใน solution space

SubdomainSubdomainส่วน​ย่อย​ของ Domain ใหญ่ เช่น Domain e-commerce แตก​เป็น subdomain การ​สั่ง​ซื้อ การ​ชำระ​เงิน คลัง​สินค้า ฯลฯ อยู่​ใน “พื้นที่​ปัญหา” (problem space)Strategic DesignBounded ContextBounded Contextขอบเขต​ที่​ชัดเจน (มัก​เป็น​ระบบ​ย่อย​หรือ​ทีม​หนึ่ง) ที่ model หนึ่ง​ใช้ได้​และ​มี​ความหมาย​แน่นอน ภายใน​ขอบเขต​นี้ Ubiquitous Language สอดคล้อง​กัน 100%Strategic Design
อยู่​ในProblem space (ปัญหา)Solution space (คำ​ตอบ)
ธรรมชาติพื้นที่​เชิง​ตรรกะ​ของ domainimplementation จริง​ใน​เชิง​เทคนิค
หน้าที่แบ่ง problem domain เผย​ให้​เห็น core/supporting/genericบังคับ​ขอบเขต​ระหว่าง model จริงๆ ใน code
ความ​สัมพันธ์อุดมคติ​คือ 1 ต่อ 1 กับ modelแต่​จริง: subdomain หนึ่ง​อาจ​มี​หลาย model หรือ model หนึ่ง​คร่อม​หลาย subdomain

ใน​อุดมคติ​จะ​มี mapping แบบ 1-ต่อ-1 ระหว่าง model กับ subdomain แต่​ใน​ความ​เป็น​จริง model ถูก​นิยาม​จาก​ปัจจัย​อย่าง​โครงสร้าง​ทีม ความ​กำกวม​ของ​ภาษา การ​จัด​กระบวนการ​ธุรกิจ (business process) หรือ​การ deploy — โดย​เฉพาะ​ใน​สภาพ​แวดล้อม legacy

flowchart LR
  subgraph PS["Problem Space"]
    SD1["Subdomain: Sales (core)"]
    SD2["Subdomain: Shipping (supporting)"]
  end
  subgraph SS["Solution Space"]
    BC1["Sales Bounded Context"]
    BC2["Pricing Bounded Context"]
    BC3["Shipping Bounded Context"]
  end
  SD1 --> BC1
  SD1 --> BC2
  SD2 --> BC3

Bounded Context เป็น​เจ้าของ vertical slice ของ function ทั้ง​ชิ้น — ตั้งแต่ presentation layer ผ่าน domain logic ลง​ไป​ถึง persistence และ​แม้แต่ data storage เมื่อ​นำ​แนวคิด​นี้​ไป​ใช้​กับ​ระบบ god object เดิม ผล​ที่​ได้​คือ​แต่ละ bounded context ดูแล presentation, domain logic และ persistence ของ​ตัวเอง การ​เปลี่ยน​ใน context หนึ่ง​จึง​ไม่​กระเพื่อม​ไป​กระทบ context อื่น​อีก​ต่อ​ไป

รูปที่ 6-10: ใช้ Layered Architecture ต่อ1 bounded context (ไม่ใช่ต่อ1 app) — แต่ละ subdomain (Shipping, Loyalty, Promotion, Allocation) มี bounded context ที่ครบทั้ง presentation/domain logic/persistence และมี database ของตัวเอง class Product จึงอยู่ได้หลายที่ โดยแต่ละที่มีเฉพาะ attribute และ logic ของบริบทนั้น

class Product ตัว​เดียวกัน​จึง​ปรากฏ​ใน​หลาย model แต่​ถูก​นิยาม​ด้วย​บริบท​ที่​มัน​อยู่ — ใน Loyalty มัน​รู้​แค่​วิธี​คิด​แต้ม ส่วน​ใน Promotion มัน​รู้​แค่​วิธี​รวม​เข้า​โปรโมชัน:

Product ใน​สอง​บริบท
namespace Loyalty
{
public class Product
{
// คิดแต้มสะสมที่ลูกค้าจะได้จากสินค้านี้
public Points Earns() { /* ... */ }
}
}
namespace Promotion
{
public class Product
{
// รวมสินค้านี้เข้ากับข้อเสนอ/โปรโมชัน
public void IncludeIn(Offer offer) { /* ... */ }
}
}

ไม่​จำเป็น​ต้อง​ใช้​สถาปัตยกรรม​เดียวกัน​ทุก context

หัวข้อ​ที่​มีชื่อ​ว่า “ไม่​จำเป็น​ต้อง​ใช้​สถาปัตยกรรม​เดียวกัน​ทุก context”

นี่​คือ​พลัง​ที่แท้​จริง​ของ​การ​แยก​ขอบเขต: architectural pattern ถูก​เลือก​ที่​ระดับ bounded context ไม่ใช่​ระดับ app ถ้า context หนึ่ง​เป็น supporting/generic ที่​มี​ตรรกะ​น้อย ก็​ใช้​สไตล์ CRUD เรียบ​ง่าย​ได้ (อย่า​กลัว​คำ​ว่า CRUD — ยึด​หลัก KISS ไว้​ก่อน) แต่​ถ้า​ตรรกะ​ซับซ้อน​พอ ก็​สร้าง rich object-oriented model หรือ​แม้แต่ CQRSCQRSCommand Query Responsibility Segregation — แยก model สำหรับ “เขียน/แก้ไข” (command) ออก​จาก model สำหรับ “อ่าน” (query) เหมาะ​กับ domain ซับซ้อน แต่​เพิ่ม​ความ​ซับซ้อน ใช้​อย่าง​ระมัดระวังArchitecture เฉพาะ context นั้น แล้ว​ประกอบ​ทุก context เข้า​ด้วย​กัน​ผ่าน composite UI ที่​ดึง​ข้อมูล​จาก​หลาย bounded context มา​แสดง​ใน​หน้า​เดียว

🚢 เลือก​สถาปัตยกรรม​ตาม​ความ​ซับซ้อน​ของ​แต่ละ context

ใน​ระบบ​ขนส่ง​ตู้ container บริบท Routing (วางแผน​เส้นทาง​เรือ​และ​ท่าเรือ) มี​ตรรกะ​ธุรกิจ​เข้มข้น​มาก จึง​คุ้ม​ค่าที่​จะ​ลงทุน​ทำ rich domain model เต็ม​รูปแบบ ส่วน​บริบท Customer Management (ข้อมูล​ผู้​ส่ง/ผู้รับ) แทบ​ไม่มี​กฎ ใช้ CRUD ก็​เพียงพอ และ​บริบท Tracking ที่​มี​การ​อ่าน​สถานะ​หนักหน่วง อาจ​เลือก CQRS เพื่อ​แยก read model ออก​มา — สาม​บริบท​ใน​ระบบ​เดียว สาม​สถาปัตยกรรม นี่​คือ​สิ่ง​ที่ bounded context เปิด​ทาง​ให้​ทำได้

วิธี​ปกป้อง​ความ​บริสุทธิ์​ของ model คือ​เปิด​เฉพาะ Application Service เป็น API แบบ​หยาบ (coarse-grained) ที่​ห่อ​หุ้ม​รายละเอียด​ของ domain model ไว้​ข้าง​ใน endpoint นี้​รับ input จาก model อื่น​แล้ว​แปล​ให้​เป็น​ภาษา​ที่ model ภายใน​เข้าใจ ป้องกัน​ไม่​ให้​เส้น​แบ่ง​ความ​รับผิดชอบ​พร่า​เลือน — autonomy (ความ​เป็น​อิสระ​ใน​การ​ปกครอง​ตนเอง) จึง​เป็น​คุณสมบัติ​หลัก​ที่​แยก​ทีม​ออก​จาก​สิ่ง​รบกวน​ภายนอก และ​แยก model ออก​จาก​แนวคิด​ที่​ไม่​เกี่ยวข้อง

รูปที่ 6-13: กายวิภาคของ Bounded Context — ขอบเขตที่ชัดเจนล้อม User Interface, Application Service, Domain Model และ Infrastructure ไว้ด้วยกัน โลกภายนอก (clients และ bounded context อื่น) เข้าถึงได้ผ่าน API ของ Application Service เท่านั้น ซึ่งทำหน้าที่รักษา domain model ให้บริสุทธิ์

หัวใจ​ของ​บท​นี้

การ​ฝืน​ใช้ model เดียว​กับ problem domain ที่​ซับซ้อน​มัก​ลาก code ให้​กลาย​เป็น Big Ball of Mud ทาง​แก้​คือ นิยาม​บริบท​ของ​แต่ละ model ให้​ชัดเจน แล้ว​ล้อม​ด้วย Bounded Context ซึ่ง​เป็น ขอบเขต​ทาง​ภาษา (linguistic boundary) ที่​ปกป้อง​ความ​บริสุทธิ์​ของ domain model จำ​ให้​ขึ้นใจ​ว่า subdomain อยู่​ใน problem space ส่วน bounded context อยู่​ใน solution space; หนึ่ง​ทีม​ควร​เป็น​เจ้าของ1 bounded context อย่าง autonomous ครบ​ทั้ง stack; และ​เลือก architectural pattern ที่​ระดับ bounded context ไม่ใช่​ระดับ app


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

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

  • Bounded Context — นิยาม​และ​ตัวอย่าง​เพิ่มเติม​ของ​ขอบเขต​ที่​ล้อม model ให้​บริสุทธิ์ ตรง​กับ​แก่น​ของ​บท​นี้
  • Ubiquitous Language — ขยาย​ความ​ว่า​ทำไม linguistic boundary ถึง​เป็น​สัญญาณ​สำคัญ​ที่สุด​ใน​การ​ลาก​เส้น bounded context
  • Subdomain — เปรียบเทียบ problem space กับ solution space ให้​ชัด​ขึ้น ต่อยอด​จาก​ตาราง​เทียบ​ใน​บท​นี้
  • Context Mapping — ก้าว​ถัด​ไป​หลัง​แบ่ง bounded context แล้ว คือ​การ​นิยาม​ความ​สัมพันธ์​ระหว่าง​บริบท​เหล่า​นั้น
  • CQRS — ตัวอย่าง​สถาปัตยกรรม​ที่​เลือก​ใช้​เฉพาะ​บาง bounded context ที่​มี​ตรรกะ​ซับซ้อน​หรือ​อ่าน​หนัก ที่​กล่าว​ถึง​ถึง​ใน​บท​นี้

เช็กความเข้าใจ — บทที่ 6

ข้อ 1 / 4

ทำไม DDD จึงสนับสนุนให้แตกระบบใหญ่ออกเป็นหลาย code model แทนที่จะใช้ model เดียว?