Context Mapping
มองให้เห็นว่า bounded context ต่าง ๆ เชื่อมต่อกันอย่างไร
Context Mapping คือเทคนิคเชิงกลยุทธ์ (strategic design) ใน Domain-Driven Design ที่ใช้วาด “แผนที่” แสดงว่า bounded context ต่าง ๆ ในระบบเชื่อมโยงและผสานรวม (integrate) กันอย่างไร ผลลัพธ์ที่ได้เรียกว่า context map ซึ่งเป็นไดอะแกรมง่าย ๆ ที่ระบุทั้งความสัมพันธ์เชิงเทคนิค (model ของ context หนึ่งไปสัมพันธ์กับอีก context หนึ่งอย่างไร) และความสัมพันธ์เชิงองค์กร (ทีมไหนดูแล context ไหน ทีมต้องประสานงานกันแค่ไหน)
Eric Evans ผู้บัญญัติศัพท์นี้ใน Domain-Driven Design อธิบายว่าระบบขนาดใหญ่มักประกอบด้วยหลาย model ที่ทับซ้อนกันโดยไม่ได้ตั้งใจ การไม่ระบุขอบเขตและความสัมพันธ์เหล่านี้อย่างชัดเจนจะนำไปสู่ความสับสนและ bug ที่ตามรอยยาก Context map จึงทำหน้าที่เป็นเอกสารกลางที่ทำให้ทุกคนในทีม “เห็นภาพเดียวกัน” ว่า model ใดคุยกับ model ใด ผ่านกลไกอะไร และใครเป็นผู้ควบคุมการเปลี่ยนแปลง
Martin Fowler สรุปไว้สั้น ๆ ว่า bounded context จะ “ชัดเจนเรื่องความสัมพันธ์ระหว่างกัน” เสมอ และแนะนำว่าควรวาด context map ออกมาเพื่อสื่อสารความสัมพันธ์เหล่านั้น ส่วน Vaughn Vernon ใน Implementing Domain-Driven Design (บทที่ 3) ขยายความรูปแบบความสัมพันธ์ (context mapping patterns) ให้ละเอียดขึ้นเป็นชุดรูปแบบมาตรฐานที่ใช้กันแพร่หลายในทุกวันนี้
บทบาทใน DDD
หัวข้อที่มีชื่อว่า “บทบาทใน DDD”Context Mapping เป็นเครื่องมือหลักของ Strategic Design — ระดับการออกแบบที่มองภาพรวมของระบบและองค์กร ตรงข้ามกับ tactical design ที่เน้นรายละเอียดภายใน context เดียว (entity, value object, aggregate) บทบาทของมันคือ:
- ทำให้ขอบเขตชัดเจน — เมื่อระบุ bounded context ได้แล้ว context map จะบันทึกว่าขอบเขตเหล่านั้นเชื่อมกันตรงไหน แทนที่จะปล่อยให้ integration point คลุมเครือ
- สื่อสารกับผู้มีส่วนได้ส่วนเสีย — ทีมพัฒนา สถาปนิก และแม้แต่ผู้บริหารสามารถใช้ context map เป็นจุดเริ่มต้นคุยเรื่องแผนงาน การพึ่งพา และความเสี่ยง
- สะท้อน Conway’s Law — โครงสร้างการสื่อสารของทีมมักกำหนดโครงสร้างของระบบ context map จึงมักเผยให้เห็นทั้งสถาปัตยกรรมซอฟต์แวร์และโครงสร้างองค์กรไปพร้อมกัน (ดู Conway’s Law)
- เลือกกลยุทธ์การผสานรวมที่เหมาะสม — แต่ละคู่ context อาจต้องการรูปแบบความสัมพันธ์ที่ต่างกัน ขึ้นกับอำนาจต่อรอง ความสำคัญเชิงธุรกิจ และคุณภาพของ model ฝั่งตรงข้าม
รูปแบบความสัมพันธ์ (context mapping patterns)
หัวข้อที่มีชื่อว่า “รูปแบบความสัมพันธ์ (context mapping patterns)”รูปแบบที่ Vernon และ DDD community ใช้กันทั่วไปแบ่งเป็นสามกลุ่มใหญ่:
1) พึ่งพากันทั้งสองฝ่าย (mutually dependent)
- Partnership — ทีมทั้ง2 context มีเป้าหมายร่วมกัน ต้องวางแผนและปล่อย release ประสานกัน ล้มเหลวด้วยกันหรือสำเร็จด้วยกัน
- Shared Kernel — 2 context ตกลง share model ย่อยบางส่วนร่วมกัน (code, schema) ต้องคุยกันก่อนแก้ไขส่วนที่ share ควรทำ kernel ให้เล็กที่สุด
2) Upstream/Downstream — ทีม upstream มีอิทธิพลต่อ downstream แต่ downstream ไม่กระทบ upstream กลับ
- Customer/Supplier — downstream (ลูกค้า) มีสิทธิ์ต่อรองความต้องการกับ upstream (ผู้จัดหา) ผ่านกระบวนการวางแผนร่วมกันอย่างเป็นทางการ
- Conformist — downstream ยอมรับ model ของ upstream ทั้งหมดโดยไม่ต่อรอง ตัดความซับซ้อนของการแปลง model ออกไป แลกกับการเสียอิสระในการออกแบบ
- Anticorruption Layer (ACL) — downstream สร้างชั้นแปลภาษา (translation layer) เพื่อป้องกันไม่ให้ model ที่ไม่ดีหรือไม่เข้ากันของ upstream “รั่วไหล” เข้ามาปนเปื้อน model ของตน (ดู Anti-Corruption Layer)
- Open Host Service (OHS) — upstream เปิด service หรือ API ที่ออกแบบมาให้ integrate ง่ายสำหรับ downstream หลายราย แทนที่จะ custom ทีละคู่
- Published Language — ใช้ภาษากลางที่มีเอกสารชัดเจน (เช่น รูปแบบข้อมูลมาตรฐาน) เป็นสื่อกลางแปลระหว่าง model มักใช้คู่กับ OHS
3) อิสระต่อกัน (free)
- Separate Ways — 2 context ไม่มีความสัมพันธ์ที่คุ้มค่าจะเชื่อมต่อ ปล่อยให้แยกกันไปเลยดีกว่าฝืนผสาน
- Big Ball of Mud — ใช้เป็นเส้นแบ่งเขต (demarcation) รอบระบบเก่าที่ model ปนเปกันมั่ว เพื่อกันไม่ให้ความยุ่งเหยิงลามเข้ามาในส่วนที่ออกแบบดีแล้ว
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”สมมติระบบ e-commerce มี Sales context เป็น upstream ที่ให้บริการราคาและสถานะคำสั่งซื้อ, Shipping context เป็น downstream ที่ต้อง conform ตาม model ของ Sales, และมี Legacy Inventory context (ระบบเก่า) ที่ Sales ต้องคุยด้วยผ่าน anticorruption layer เพราะ model ไม่เข้ากัน ส่วน Billing กับ Sales share แนวคิดเรื่องราคาผ่าน shared kernel
graph LR
SalesCtx[Sales Context]
ShippingCtx[Shipping Context]
BillingCtx[Billing Context]
PricingKernel[Shared Pricing Kernel]
LegacyCtx[Legacy Inventory Context]
WarehouseCtx[Warehouse Context]
SalesCtx -->|Open Host Service| ShippingCtx
ShippingCtx -->|Conformist| SalesCtx
SalesCtx --- PricingKernel
BillingCtx --- PricingKernel
SalesCtx -->|Anticorruption Layer| LegacyCtx
ShippingCtx -.->|Separate Ways| WarehouseCtx
code ตัวอย่าง C# ด้านล่างจำลอง context map เป็นโครงสร้างข้อมูลอย่างง่าย เพื่อใช้ตรวจสอบหรือสร้างเอกสารความสัมพันธ์ระหว่าง bounded context โดยอัตโนมัติ:
// enum แทนรูปแบบความสัมพันธ์ตาม Vernon (IDDD บทที่ 3)public enum ContextRelationshipPattern{ Partnership, SharedKernel, CustomerSupplier, Conformist, AnticorruptionLayer, OpenHostService, PublishedLanguage, SeparateWays, BigBallOfMud}
// ตัวแทนของ bounded context หนึ่งจุดบน context mappublic class BoundedContextNode{ public string Name { get; } public string OwningTeam { get; }
public BoundedContextNode(string name, string owningTeam) { Name = name; OwningTeam = owningTeam; }}
// เส้นเชื่อมหนึ่งเส้นบน context map: upstream ไป downstream ด้วยรูปแบบหนึ่งpublic class ContextRelationship{ public BoundedContextNode Upstream { get; } public BoundedContextNode Downstream { get; } public ContextRelationshipPattern Pattern { get; }
public ContextRelationship( BoundedContextNode upstream, BoundedContextNode downstream, ContextRelationshipPattern pattern) { Upstream = upstream; Downstream = downstream; Pattern = pattern; }}
// context map ทั้งชุด รวบรวมทุกความสัมพันธ์ในระบบpublic class ContextMap{ private readonly List<ContextRelationship> _relationships = new();
public void Connect( BoundedContextNode upstream, BoundedContextNode downstream, ContextRelationshipPattern pattern) { _relationships.Add(new ContextRelationship(upstream, downstream, pattern)); }
// ใช้หาว่ามี anticorruption layer ป้องกัน context ใดบ้าง public IEnumerable<ContextRelationship> WhereAnticorruptionLayerIsUsed() { return _relationships.Where(r => r.Pattern == ContextRelationshipPattern.AnticorruptionLayer); }}
// การประกอบ context map ของตัวอย่างข้างต้นvar sales = new BoundedContextNode("Sales", "Team Falcon");var shipping = new BoundedContextNode("Shipping", "Team Comet");var legacyInventory = new BoundedContextNode("LegacyInventory", "Ops Team");
var map = new ContextMap();map.Connect(sales, shipping, ContextRelationshipPattern.OpenHostService);map.Connect(sales, legacyInventory, ContextRelationshipPattern.AnticorruptionLayer);ตัวอย่างนี้แสดงให้เห็นว่า context map ไม่จำเป็นต้องเป็นแค่ภาพวาดบนกระดาน — ทีมสามารถทำให้มันเป็น living document ที่ผูกกับ code จริง เช่น ใช้ตรวจสอบว่าทุก integration ที่ประกาศไว้มี ACL ป้องกันครบตามที่ตกลงกันหรือไม่
ความสัมพันธ์กับแนวคิดอื่น
หัวข้อที่มีชื่อว่า “ความสัมพันธ์กับแนวคิดอื่น”- Context map สร้างขึ้นหลังจากระบุ Bounded Context แล้ว มันคือขั้นตอนถัดไปที่อธิบายว่าขอบเขตเหล่านั้น “คุยกัน” อย่างไร
- รูปแบบ Anticorruption Layer และ Shared Kernel ที่ปรากฏบน context map มีสถานะเป็นแนวคิดของตัวเองใน DDD (Anti-Corruption Layer เป็นอีกหน้าหนึ่งในชุดนี้)
- ความสัมพันธ์แบบ upstream/downstream มักสอดคล้องกับโครงสร้างทีมจริงตาม Conway’s Law — การจัดทีมใหม่บางครั้งคือวิธีแก้ปัญหา context map ที่ยุ่งเหยิงกว่าการ refactor code
- แต่ละ context บนแผนที่ยังคงมี Ubiquitous Language เป็นของตัวเอง คำศัพท์เดียวกัน (เช่น “Customer”) อาจมีความหมายต่างกันคนละ context ซึ่งเป็นเหตุผลที่ต้องมีชั้นแปลระหว่างกัน
- Context Mapping เป็นหนึ่งในกิจกรรมของ Strategic Design โดยรวม ซึ่งยังครอบคลุมการแบ่ง subdomain และการจัดลำดับความสำคัญเชิงธุรกิจด้วย
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- ที่มา · deviq.com/domain-driven-design/context-mapping
- Martin Fowler — bliki: Bounded Context
- Microsoft Learn — Identifying domain-model boundaries for each microservice
- DDD Crew — Context Mapping (GitHub)
- Vaughn Vernon, Implementing Domain-Driven Design — บทที่ 3 ว่าด้วย Context Maps (สรุปผ่าน Context Mapper docs)