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

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) ให้​ละเอียด​ขึ้น​เป็น​ชุด​รูปแบบ​มาตรฐาน​ที่​ใช้​กัน​แพร่หลาย​ใน​ทุก​วัน​นี้

Context Mapping เป็น​เครื่องมือ​หลัก​ของ Strategic Design — ระดับ​การ​ออกแบบ​ที่​มอง​ภาพ​รวม​ของ​ระบบ​และ​องค์กร ตรง​ข้าม​กับ tactical design ที่​เน้น​รายละเอียด​ภายใน context เดียว (entity, value object, aggregate) บทบาท​ของ​มัน​คือ:

  1. ทำให้​ขอบเขต​ชัดเจน — เมื่อ​ระบุ bounded context ได้​แล้ว context map จะบันทึก​ว่า​ขอบเขต​เหล่า​นั้น​เชื่อม​กัน​ตรง​ไหน แทนที่​จะ​ปล่อย​ให้ integration point คลุมเครือ
  2. สื่อสาร​กับ​ผู้​มี​ส่วน​ได้​ส่วน​เสีย — ทีม​พัฒนา สถาปนิก และ​แม้แต่​ผู้​บริหาร​สามารถ​ใช้ context map เป็น​จุด​เริ่มต้น​คุย​เรื่อง​แผนงาน การ​พึ่งพา และ​ความ​เสี่ยง
  3. สะท้อน Conway’s Law — โครงสร้าง​การ​สื่อสาร​ของ​ทีม​มัก​กำหนด​โครงสร้าง​ของ​ระบบ context map จึง​มัก​เผย​ให้​เห็น​ทั้ง​สถาปัตยกรรม​ซอฟต์แวร์​และ​โครงสร้าง​องค์กร​ไป​พร้อม​กัน (ดู Conway’s Law)
  4. เลือก​กลยุทธ์​การ​ผสาน​รวม​ที่​เหมาะสม — แต่ละ​คู่ context อาจ​ต้องการ​รูปแบบ​ความ​สัมพันธ์​ที่​ต่าง​กัน ขึ้น​กับ​อำนาจ​ต่อรอง ความ​สำคัญ​เชิง​ธุรกิจ และ​คุณภาพ​ของ model ฝั่ง​ตรง​ข้าม

รูปแบบ​ที่ 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 map
public 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 และ​การ​จัด​ลำดับ​ความ​สำคัญ​เชิง​ธุรกิจ​ด้วย