Strategic Design ด้วย Context Mapping
Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design แต่ละอันไม่ได้อยู่อย่างโดดเดี่ยว เกือบทุกระบบจริงต้องเชื่อมต่อกับ context อื่น — ทั้งของทีมเราเองและของระบบภายนอก เมื่อ2 model มาบรรจบกัน คำถามสำคัญจึงไม่ใช่แค่ “เชื่อมด้วยเทคโนโลยีอะไร” แต่คือ “ความสัมพันธ์ระหว่างทีมที่ดูแล2 context นี้เป็นแบบไหน” นี่คือหัวใจของ Context MappingContext Mappingกิจกรรมการระบุ Bounded Context และนิยามความสัมพันธ์ระหว่างกัน (เช่น Partnership, Shared Kernel, ACL) เพื่อจัดการการเชื่อมต่อระหว่างทีม/ระบบStrategic Design
Context MapContext Mapแผนภาพที่แสดง Bounded Context ทั้งหมดและความสัมพันธ์ระหว่างกัน ช่วยให้เห็นภาพรวมและจุดเชื่อมต่อ/แปลภาษาในระบบใหญ่Strategic Design คือแผนภาพที่แสดง Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ทั้งหลายและเส้นที่ลากเชื่อมระหว่างกัน เส้นแต่ละเส้นไม่ได้บอกแค่ “กลไกทางเทคนิค” ที่อยู่ตรงกลาง แต่ยัง นิยามความสัมพันธ์ระหว่างทีม ที่เป็นเจ้าของแต่ละ context ด้วย การเข้าใจความสัมพันธ์นี้สำคัญพอๆ กับการเขียน code เชื่อมต่อ เพราะมันกำหนดว่าฝ่ายไหนมีอำนาจต่อรอง ฝ่ายไหนต้องปรับตัว และความล้มเหลวของฝ่ายหนึ่งจะลามไปกระทบอีกฝ่ายแค่ไหน
ในเมื่อสอง Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ต่างมี Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ของตัวเอง เส้นที่ลากเชื่อมนี้จึงแทน การแปลภาษา ที่เกิดขึ้นระหว่างสองภาษานั้น ลองนึกถึงภาพว่า มีสองทีมที่ต้องทำงานร่วมกัน แต่ทั้งคู่อยู่กันคนละฝั่งของพรมแดนประเทศและพูดกันคนละภาษา ทางเลือกมีอยู่สองทาง — ไม่ทีมใดทีมหนึ่งต้องจ้าง ล่าม มาช่วย หรือทีมหนึ่ง (หรือทั้งสองทีม) ต้องลงแรงเรียนรู้ภาษาของอีกฝ่ายให้มากพอ การจ้างล่ามถือว่าเป็นงานที่เบากว่าสำหรับทั้งสองทีม แต่ก็อาจมี “ราคา” ที่ต้องจ่ายในหลายแง่ ลองนึกถึงเวลาที่เสียไปกับการที่ทีมหนึ่งต้องพูดกับล่ามก่อน แล้วล่ามจึงค่อยถ่ายทอดต่อไปยังอีกทีม (ทีม → ล่าม → อีกทีม) ตอนแรกๆ ก็ดูราบรื่นดี แต่นานวันเข้ากลับยุ่งยากขึ้นต่อเนื่อง ถึงอย่างนั้นมันก็ยังอาจดีกว่าการต้องคอยสลับไปมาระหว่างภาษาตลอดเวลา — และข้อแลกเปลี่ยน (trade-off) แบบเดียวกันนี้เองก็เกิดขึ้นเมื่อเราต้องแปล Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design หนึ่งให้กลายเป็นอีกภาษาหนึ่ง
แนวคิดหนึ่งที่จะเจอซ้ำตลอดบทนี้คือ ความสัมพันธ์แบบต้นน้ำ/ปลายน้ำUpstream / Downstreamความสัมพันธ์ที่การกระทำของฝ่ายต้นน้ำ (upstream) ส่งผลต่อฝ่ายปลายน้ำ (downstream) แต่ไม่ใช่ในทางกลับกัน — ทิศของอิทธิพลStrategic Design — ฝ่าย ต้นน้ำ (Upstream) คือฝ่ายที่ส่งอิทธิพลออกไป ส่วน ปลายน้ำ (Downstream) คือฝ่ายที่ได้รับผลกระทบ บนแผนภาพมักเขียนกำกับด้วยตัวอักษร U (upstream) และ D (downstream) ตรงปลายเส้นเชื่อม
ชนิดของความสัมพันธ์บน Context Map (Kinds of Mappings)
หัวข้อที่มีชื่อว่า “ชนิดของความสัมพันธ์บน Context Map (Kinds of Mappings)”Vernon ไล่เรียงรูปแบบความสัมพันธ์เก้าแบบที่พบได้บน Context Map แต่ละแบบบอกทั้งโครงสร้างทีมและกลยุทธ์การเชื่อมต่อที่เหมาะสม
Partnership
หัวข้อที่มีชื่อว่า “Partnership”PartnershipPartnershipสองทีมที่หากฝ่ายใดล้มเหลวอีกฝ่ายก็ส่งงานไม่ได้ จึงต้องวางแผนและจัดการการเชื่อมต่อร่วมกัน feature ที่พึ่งพากันถูกจัดให้เสร็จใน release เดียวกันStrategic Design เกิดขึ้นเมื่อสองทีมที่ดูแลคนละ Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design มีชะตาผูกกัน — ถ้าฝ่ายหนึ่งล้มเหลว อีกฝ่ายก็ส่งงานไม่ได้เช่นกัน ในกรณีนี้ทั้งสองทีมต้องร่วมมือกันอย่างใกล้ชิด ทั้งวางแผนการพัฒนาและจัดการการเชื่อมต่อร่วมกัน feature ที่พึ่งพากันต้องถูกวางให้เสร็จใน release เดียวกัน เพราะความสัมพันธ์แบบนี้ดูแลยาก จึงควรประคองไว้เท่าที่จำเป็น และพร้อมยุติเมื่อเหตุผลที่ผูกพันกันหมดไป
Shared Kernel
หัวข้อที่มีชื่อว่า “Shared Kernel”Shared KernelShared Kernelสองทีมตกลงแบ่ง model ส่วนเล็ก ๆ มาใช้ร่วมกันโดยมีขอบเขตชัดเจน ส่วนนี้ห้ามแก้โดยไม่ปรึกษากัน เพราะกระทบทั้งสองฝ่ายStrategic Design คือกรณีที่สองทีม (หรือมากกว่า) ตกลงแบ่ง model ส่วนเล็กๆ มา ใช้ร่วมกัน โดยขีดขอบเขตให้ชัดเจน (บนแผนภาพแสดงด้วยส่วนที่2 context ซ้อนทับกัน) ทั้งสองทีมต้อง ตกลงกันให้ชัดว่าจะแบ่ง element ใดของ model มาใช้ร่วม และเป็นไปได้ว่าจะมีเพียงทีมเดียวที่เป็นผู้ดูแล code, build และ test ของส่วนที่ใช้ร่วมกันนั้น Shared Kernel มักคิดให้ลงตัวได้ยากตั้งแต่แรกและดูแลยาก เพราะต้องอาศัยการสื่อสารแบบเปิดและการตกลงร่วมกันอย่างสม่ำเสมอ (ความผูกพันแบบ Partnership อยู่เบื้องหลัง) แต่ก็สำเร็จได้ถ้าทุกฝ่ายเชื่อว่าการมี kernel ร่วมดีกว่าการแยกทางเดิน (Separate WaysSeparate Waysประกาศว่า Bounded Context นี้ไม่เชื่อมต่อกับอันอื่นเลย เพื่อให้แต่ละฝ่ายหาทางแก้แบบง่าย ๆ เฉพาะของตน ใช้เมื่อค่าใช้จ่ายการเชื่อมต่อไม่คุ้มStrategic Design)
Customer-Supplier
หัวข้อที่มีชื่อว่า “Customer-Supplier”Customer-SupplierCustomer / Supplierความสัมพันธ์ที่ทีมต้นน้ำ (supplier) รับความต้องการของทีมปลายน้ำ (customer) เข้าไปวางแผนด้วย มีการเจรจาและจัดลำดับงานให้ชัดเจนStrategic Design เป็นรูปแบบที่พบบ่อยและดีต่อสุขภาพที่สุดอย่างหนึ่ง ทีม Supplier (ต้นน้ำ) เป็นฝ่ายผลิต ส่วนทีม Customer (ปลายน้ำ) เป็นฝ่ายบริโภค จุดเด่นคือ Supplier ยอมรับฟังความต้องการของ Customer แล้วนำเข้าไปวางแผนการพัฒนาด้วย — มีการเจรจาและจัดลำดับงานร่วมกันอย่างชัดเจน เพราะ Customer มีน้ำหนักในการต่อรองพอสมควร นี่คือความสัมพันธ์ที่ลงตัวเมื่อทั้งสองฝ่ายเต็มใจร่วมมือ
Conformist
หัวข้อที่มีชื่อว่า “Conformist”ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design เกิดเมื่อทีมปลายน้ำพึ่งพาทีมต้นน้ำ แต่ต้นน้ำ ไม่มีแรงจูงใจ จะรองรับความต้องการเฉพาะของปลายน้ำเลย ด้วยเหตุผลหลายอย่าง ทีมปลายน้ำอาจประคองความพยายามในการแปล Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ของ model ต้นน้ำให้เข้ากับความต้องการเฉพาะของตัวเองไม่ไหว ทีมจึงเลือก “ยอมตามใจ” — รับเอา Ubiquitous Language ของต้นน้ำมาใช้ทั้งดุ้นใน model ของตัวเอง ทีมมักกลายเป็น Conformist เมื่อต้อง บูรณาการกับ model ขนาดใหญ่ ซับซ้อน และตั้งมั่นมานานแล้ว ตัวอย่างเช่น การต้องยอมตาม model ของ Amazon.com เมื่อเข้าร่วมเป็นหนึ่งในผู้ขายแบบ affiliate ของ Amazon การยอมเป็น Conformist ตัดความซับซ้อนในการแปลภาษาออกไป แต่ก็แลกมาด้วยการที่ model ของเราถูกผูกติดกับ model ของคนอื่น
Anticorruption Layer
หัวข้อที่มีชื่อว่า “Anticorruption Layer”Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design (ACL) คือทางเลือกป้องกันตัวที่แข็งแรงที่สุดในกลุ่มความสัมพันธ์แบบบูรณาการ เป็น ชั้นกั้น ที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบต้นน้ำให้กลายเป็นภาษาของ model ตัวเอง บนแผนภาพมักเขียนกล่อง ACL คร่อมตรงรอยต่อฝั่งปลายน้ำ ประโยชน์คือป้องกันไม่ให้แนวคิดและคำศัพท์จากภายนอกมา “ปนเปื้อน” model ภายในของเรา
เมื่อใดก็ตามที่ต้องเชื่อมต่อกับระบบที่เราควบคุมไม่ได้ — โดยเฉพาะระบบ legacy ที่ยุ่งเหยิง — ให้สร้าง Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design กั้นไว้เสมอ มันเป็นวิธีที่ปลอดภัยที่สุดในการบูรณาการ เพราะรักษาความสะอาดของ model ภายในเอาไว้ได้ คำขวัญของบทนี้คือ “อย่าพูดภาษาของระบบนั้น” ปล่อยให้ ACL เป็นล่ามให้
Open Host Service
หัวข้อที่มีชื่อว่า “Open Host Service”Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design (OHS) คือมุมกลับของ ACL — แทนที่ปลายน้ำจะป้องกันตัว ฝ่าย ต้นน้ำ เป็นผู้เปิดบริการของตัวเองออกมาเป็นโพรโทคอลหรือ API ที่นิยามไว้อย่างดี ให้ทุกฝ่ายที่ต้องการเชื่อมต่อเข้ามาใช้ได้ตามมาตรฐานเดียวกัน บนแผนภาพมักเขียนกล่อง OHS คร่อมตรงรอยต่อฝั่งต้นน้ำ เหมาะกับ context ที่ต้องให้บริการผู้บริโภคหลายฝ่ายพร้อมกัน
Published Language
หัวข้อที่มีชื่อว่า “Published Language”Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design คือภาษากลางที่ จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลข้าม context ที่ผู้บริโภคจำนวนเท่าใดก็ตามนำไปใช้และแปลได้ง่าย ผู้บริโภคที่ทั้งอ่านและเขียนสามารถแปลเข้า/ออกจากภาษากลางนี้ได้อย่างมั่นใจว่าการบูรณาการของตนถูกต้อง Published Language แบบนี้นิยามได้ด้วย XML Schema, JSON Schema หรือ wire format ที่เหมาะสมกว่า เช่น Protobuf หรือ Avro Published Language มักไปคู่กับ Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design เสมอ — OHS คือ “ช่องทาง” ส่วน Published Language คือ “ภาษา” ที่ไหลผ่านช่องทางนั้น บนแผนภาพจึงมักเขียนคู่กันเป็น OHS / PL
Separate Ways
หัวข้อที่มีชื่อว่า “Separate Ways”Separate WaysSeparate Waysประกาศว่า Bounded Context นี้ไม่เชื่อมต่อกับอันอื่นเลย เพื่อให้แต่ละฝ่ายหาทางแก้แบบง่าย ๆ เฉพาะของตน ใช้เมื่อค่าใช้จ่ายการเชื่อมต่อไม่คุ้มStrategic Design คือการตัดสินใจว่า จะไม่เชื่อมต่อกันเลย เมื่อพิจารณาแล้วพบว่าค่าใช้จ่ายและความยุ่งยากของการบูรณาการไม่คุ้มกับประโยชน์ที่ได้ ทางที่ดีกว่าคือปล่อยให้แต่ละ Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design หาทางแก้ปัญหาแบบเฉพาะของตัวเองแบบตรงไปตรงมา ไป การยอมรับว่า “ไม่ต้องผูกกัน” ก็เป็นการตัดสินใจเชิงกลยุทธ์ที่ถูกต้องได้
Big Ball of Mud
หัวข้อที่มีชื่อว่า “Big Ball of Mud”Big Ball of MudBig Ball of Mudระบบที่ปนเปกันยุ่งเหยิงไม่มีขอบเขตชัด ให้ลากเส้นล้อมไว้แล้วยอมรับสภาพ ไม่พยายามทำ model ละเอียดในนั้น และระวังไม่ให้ลามไป context อื่นStrategic Design คือฝันร้ายที่พบได้ทั่วไป — ระบบที่ model หลายอันปนเปกันยุ่งเหยิง ไร้ขอบเขตชัดเจน กฎและข้อมูลที่ควรแยกกันกลับพันกันยุ่ง การแก้ส่วนหนึ่งทำให้อีกหลายส่วนพัง คำแนะนำคือให้ ลากเส้นล้อมมันไว้ แล้วยอมรับสภาพ ไม่พยายามสร้าง model ละเอียดอ่อนข้างใน และที่สำคัญที่สุดคือ ระวังไม่ให้ความเละเทะนั้นลามออกมาปนเปื้อน context อื่น
เผื่อคำเตือนข้างต้นยังไม่พอ นี่คือสิ่งที่จะเกิดขึ้นต่อเนื่อง เมื่อคุณต้องเป็นเจ้าของ Big Ball of Mud:
- จำนวน AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design ที่เพิ่มขึ้นต่อเนื่อง จะ ปนเปื้อนข้ามกัน (cross-contaminate) เพราะมีการเชื่อมโยงและการพึ่งพากันอย่างไม่สมควร
- การดูแลส่วนหนึ่งของ Big Ball of Mud ทำให้เกิดแรงกระเพื่อมไปทั่วทั้ง model นำไปสู่ปัญหาแบบ “ตีตัวตุ่น” (whack-a-mole) — กดตรงนี้โผล่ตรงนั้น
- มีเพียง ความรู้ในวงแคบ (tribal knowledge) และการลงแรงแบบวีรบุรุษ — พูดทุกภาษาพร้อมกันในคราวเดียว — เท่านั้นที่ยื้อระบบไว้ไม่ให้ล่มสลายทั้งหมด
ปัญหาคือทุกวันนี้โลกของซอฟต์แวร์เต็มไปด้วย Big Ball of Mud อยู่แล้ว และจำนวนมีแต่จะเพิ่มขึ้นทุกเดือน ต่อให้เราหลีกเลี่ยงการสร้าง Big Ball of Mud ขึ้นมาเองด้วยเทคนิค DDD ได้สำเร็จ ก็ยังอาจต้องเชื่อมต่อกับของเก่าที่มีอยู่ ถ้าจำเป็นต้องเชื่อม ให้สร้าง Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design กั้นไว้กับระบบ legacy ทุกตัว เพื่อปกป้อง model ของเราจากความเละเทะที่จะไหลเข้ามา ไม่ว่าจะทำอะไรก็ตาม — อย่าพูดภาษาของระบบนั้น!
graph LR BBM["Big Ball of Mud<br/>(ระบบ legacy เละเทะ)"] -->|"U"| Edge["ACL"] Edge -->|"D"| Clean["model ของเรา<br/>(สะอาด)"]
เลือกกลไกเชื่อมต่อให้เหมาะ (Making Good Use of Context Mapping)
หัวข้อที่มีชื่อว่า “เลือกกลไกเชื่อมต่อให้เหมาะ (Making Good Use of Context Mapping)”interface แบบไหนที่จะใช้เชื่อมต่อกับ Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design หนึ่งๆ ขึ้นอยู่กับว่าทีมที่เป็นเจ้าของ context นั้นเปิดอะไรให้ใช้ อาจเป็น RPC ผ่าน SOAP, interface แบบ RESTful ที่จัดการกับ resource หรืออาจเป็นการส่งข้อความ (messaging) ผ่านคิวหรือแบบ Publish-Subscribe ในสถานการณ์ที่เลวร้ายที่สุด เราอาจถูกบังคับให้เชื่อมต่อผ่านฐานข้อมูลหรือ file ระบบโดยตรง — ซึ่งควรหลีกเลี่ยงให้ได้มากที่สุด โดยเฉพาะการบูรณาการผ่านฐานข้อมูล และถ้าจำเป็นต้องทำจริงๆ ก็ต้องแยก model ที่บริโภคข้อมูลนั้นด้วย Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design เสมอ
ต่อไปจะดูสามวิธีที่น่าเชื่อถือกว่า เรียงจาก “ทนทานน้อยที่สุด” ไป “ทนทานที่สุด” คือ RPC, RESTful HTTP แล้วจึงเป็น messaging
RPC with SOAP
หัวข้อที่มีชื่อว่า “RPC with SOAP”Remote Procedure Call (RPC) ทำงานได้หลายแบบ หนึ่งในแบบยอดนิยมคือผ่าน SOAP (Simple Object Access Protocol) แนวคิดเบื้องหลัง RPC with SOAP คือทำให้การเรียกใช้บริการจากอีกระบบหนึ่ง ดูเหมือนการเรียก method ในเครื่องเรา (local call) ทั้งที่ความจริง request นั้นต้องเดินทางข้ามเครือข่าย ไปถึงระบบปลายทาง ทำงานสำเร็จ แล้วส่งผลกลับมาทางเครือข่ายอีกครั้ง
นี่จึงเปิดช่องให้เกิด ความล้มเหลวของเครือข่ายแบบสมบูรณ์ หรืออย่างน้อยก็เกิด latency ที่อาจคาดไม่ถึงตอนเริ่มออกแบบ นอกจากนี้ RPC over SOAP ยัง ผูกกันแน่น (strong coupling) ระหว่าง client Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design กับ context ที่ให้บริการ
graph LR
Client["Client<br/>Bounded Context"] -->|"SOAP (XML)"| Service["Service<br/>Bounded Context"]
WSDL["WSDL<br/>(บรรยายบริการ)"] -.->|describes| Service
Registry[("Service Registry")] -.->|"finds service"| Client
WSDL -.->|"points to description"| Registry
ปัญหาหลักของ RPC ไม่ว่าจะใช้ SOAP หรือวิธีอื่นคือมัน ขาดความทนทาน (lack robustness) ถ้าเครือข่ายมีปัญหา หรือระบบที่ host SOAP API มีปัญหา การเรียกที่ดูเหมือนง่ายๆ นั้นจะล้มเหลวทั้งหมด เหลือไว้แต่ผลลัพธ์ที่เป็น error อย่าหลงเชื่อความง่ายที่เห็นภายนอก
แต่เมื่อ RPC ทำงานได้ — และส่วนใหญ่มันก็ทำงานได้ — มันก็เป็นวิธีเชื่อมต่อที่มีประโยชน์มาก ถ้าเรามีอิทธิพลต่อการออกแบบ service context ก็ควรผลักดันให้มันมี API ที่ออกแบบดี เป็น Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design พร้อม Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design และไม่ว่าจะอย่างไร client context ของเราก็ควรออกแบบให้มี Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design กั้นไว้ เพื่อแยก model ของเราออกจากอิทธิพลภายนอกที่ไม่พึงประสงค์
graph LR Service["Service Bounded Context<br/>[OHS / PL]"] -->|"SOAP (XML) · U"| ACL["ACL"] ACL -->|"D"| Client["Client<br/>Bounded Context"]
RESTful HTTP
หัวข้อที่มีชื่อว่า “RESTful HTTP”การเชื่อมต่อด้วย RESTful HTTP เน้นไปที่ resource ที่แลกเปลี่ยนกันระหว่าง Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design รวมถึง4 operation หลัก: POST, GET, PUT และ DELETE หลายคนพบว่า REST ใช้งานได้ดีเพราะช่วยให้นิยาม API สำหรับระบบกระจาย (distributed computing) ได้อย่างเหมาะเจาะ — และจะเถียงข้อนี้ก็ยาก เมื่อดูจากความสำเร็จของอินเทอร์เน็ตและเว็บ
graph LR Service["Service Bounded Context<br/>[OHS / PL]"] -->|"RESTful HTTP · U"| ACL["ACL"] ACL -->|"D"| Client["Client<br/>Bounded Context"]
service Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ที่เปิด interface แบบ REST ควรจัดให้เป็น Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design พร้อม Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design — resource ทั้งหลายสมควรถูกนิยามให้เป็น Published Language และเมื่อรวมเข้ากับ REST URI ของเรา มันจะกลายเป็น Open Host Service ที่เป็นธรรมชาติ
RESTful HTTP มีแนวโน้มจะล้มเหลวด้วยเหตุผลเดียวกับ RPC — เครือข่ายล่ม ผู้ให้บริการล่ม หรือ latency ที่คาดไม่ถึง อย่างไรก็ตาม REST วางอยู่บนรากฐานของอินเทอร์เน็ต และคงยากที่จะหาข้อตำหนิประวัติของเว็บในแง่ความน่าเชื่อถือ ความสามารถในการขยายตัว และความสำเร็จโดยรวม
ความผิดพลาดที่พบบ่อยเมื่อใช้ REST คือการออกแบบ resource ให้สะท้อน AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design ใน model ของเราแบบตรงๆ การทำเช่นนี้บังคับให้ client ทุกตัวตกอยู่ในความสัมพันธ์แบบ ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design — เพราะเมื่อ model เปลี่ยนรูป resource ก็เปลี่ยนตาม
ทางที่ถูกคือออกแบบ resource แบบ “สังเคราะห์” (synthetic) ตาม use case ที่ขับเคลื่อนโดยฝั่ง client — คือ resource ที่ส่งให้ client ต้องมีรูปร่างและองค์ประกอบตรงกับ สิ่งที่ client ต้องการ ไม่ใช่ตามหน้าตาของ model ภายใน บางครั้ง model อาจบังเอิญหน้าตาเหมือนสิ่งที่ client ต้องการพอดี แต่ตัวขับเคลื่อนการออกแบบ resource ต้องเป็น “ความต้องการของ client” เสมอ ไม่ใช่องค์ประกอบปัจจุบันของ model
Messaging
หัวข้อที่มีชื่อว่า “Messaging”เมื่อใช้ asynchronous messaging ในการเชื่อมต่อ เราทำอะไรได้มากมายเพียงให้ client Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ไป subscribe กับ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ที่ context ของเราเองหรือ context อื่นเผยแพร่ออกมา การใช้ messaging เป็นหนึ่งในรูปแบบการบูรณาการที่ ทนทานที่สุด เพราะตัดการผูกพันด้านเวลา (temporal coupling) ที่มากับการเรียกแบบ blocking อย่าง RPC และ REST ออกไป ในเมื่อเราคาดหวัง latency ในการแลกเปลี่ยนข้อความอยู่แล้ว เราจึงมักสร้างระบบที่ทนทานกว่า เพราะไม่เคยคาดหวังว่าจะได้ผลทันที
graph LR
subgraph Pub["Publishing Bounded Context"]
PubAgg["Aggregate"] --> PubEvt["Domain Event"]
end
subgraph Sub["Subscribing Bounded Context"]
SubAgg["Aggregate"]
end
PubEvt --> MM["Messaging Mechanism"]
MM --> SubEvt["Domain Event"]
SubEvt --> SubAgg
AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design ตัวหนึ่งใน Bounded Context หนึ่งจะเผยแพร่ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ออกมา ซึ่งฝ่ายใดก็ตามที่สนใจสามารถนำไปบริโภคได้ เมื่อ subscribing Bounded Context รับ event นั้นมา ก็จะลงมือทำอะไรบางอย่างตามชนิดและค่าของ event — ปกติคือสร้าง Aggregate ใหม่ หรือแก้ไข Aggregate ที่มีอยู่ใน context ที่บริโภคนั้น
อาจสงสัยว่าทำไมการให้ context อื่นบริโภค Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design จึงไม่บังคับให้มันกลายเป็น ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design คำตอบ (ตามที่ Vernon แนะนำใน Implementing Domain-Driven Design บทที่ 13 “Integrating Bounded Contexts”) คือ ผู้บริโภคไม่ควรใช้ชนิด/class ของ event จากผู้เผยแพร่โดยตรง แต่ควรพึ่งพาเพียง schema ของ event เท่านั้น คือ Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design หมายความว่า ถ้า event เผยแพร่ออกมาเป็น JSON (หรือรูปแบบที่ประหยัดกว่านั้น) ผู้บริโภคควร parse มันเพื่อดึงค่าแอตทริบิวต์ออกมา แทนการ deserialize เป็น class ของผู้เผยแพร่
แน่นอนว่าทั้งหมดนี้ตั้งอยู่บนสมมติฐานว่า subscribing context ได้ประโยชน์จากเหตุการณ์ที่เกิดขึ้นเองในฝั่งผู้เผยแพร่เสมอ แต่บางครั้ง client context ก็จำเป็นต้อง ส่ง Command Message เชิงรุก ไปยัง service context เพื่อบังคับให้เกิดการกระทำบางอย่าง ในกรณีเช่นนี้ client context ก็ยังจะได้รับผลลัพธ์กลับมาในรูปของ Domain Event ที่ถูกเผยแพร่อยู่ดี
graph LR
Cmd["Command"] --> SvcAgg
subgraph Svc["Service Bounded Context"]
SvcAgg["Aggregate"] --> SvcEvt["Domain Event"]
end
subgraph Cli["Client Bounded Context"]
CliAgg["Aggregate"]
end
SvcEvt --> CliAgg
เราทำ asynchronous messaging ผ่าน REST ได้เช่นกัน ด้วยการ polling resource ที่เติบโตขึ้นต่อเนื่อง ตามลำดับ ใช้ background process ให้ client คอย poll resource ที่เป็น Atom feed ของบริการ ซึ่งให้ชุด Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ที่เพิ่มขึ้นไม่หยุด นี่เป็นวิธีที่ปลอดภัยในการดูแล operation แบบ asynchronous ระหว่างบริการกับ client พร้อมส่งมอบ event ล่าสุดอย่างต่อเนื่อง ถ้าบริการล่มไปด้วยเหตุใด client ก็แค่ลอง retry ตามรอบปกติ หรือถอยห่างแล้วค่อยลองใหม่ จนกว่า feed จะกลับมาใช้ได้ Vernon อธิบายวิธีนี้อย่างละเอียดใน Implementing Domain-Driven Design
หลีกเลี่ยง “Integration Train Wreck”
หัวข้อที่มีชื่อว่า “หลีกเลี่ยง “Integration Train Wreck””เมื่อ client context (เรียกว่า C1) เชื่อมต่อกับ service context (S1) โดยปกติ C1 ไม่ควร ส่ง request แบบ synchronous/blocking ไปยัง S1 ในฐานะผลพวงโดยตรงของการจัดการ request ที่เข้ามาหามัน คือ ขณะที่ client อีกตัว (C0) ส่ง blocking request มาที่ C1 อยู่ ก็อย่าปล่อยให้ C1 ส่ง blocking request ต่อไปยัง S1 — การทำเช่นนั้นมีโอกาสสูงมากที่จะเกิด “รถไฟชนกัน” (integration train wreck) ระหว่าง C0, C1 และ S1 หลีกเลี่ยงได้ด้วยการใช้ asynchronous messaging
ในทุกกรณีที่ใช้ messaging คุณภาพของทางออกโดยรวมจะขึ้นอยู่กับคุณภาพของกลไก messaging ที่เลือกอย่างมาก กลไกนั้นควรรองรับ At-Least-Once Delivery เพื่อรับประกันว่าทุกข้อความจะถูกส่งถึงในที่สุด ซึ่งก็แปลว่า subscribing Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ต้องถูกสร้างให้เป็น Idempotent Receiver ด้วย
graph LR MM["Messaging Mechanism"] -->|"Message"| Recv["Receiver"] Recv -.->|"ACK"| MM
At-Least-Once Delivery คือรูปแบบ messaging ที่กลไกจะ ส่งข้อความซ้ำเป็นระยะ ในกรณีที่ข้อความสูญหาย ผู้รับตอบสนองช้าหรือล่ม หรือผู้รับไม่ส่ง acknowledge กลับมา ด้วยการออกแบบเช่นนี้ จึงเป็นไปได้ที่ข้อความเดียวกันจะถูกส่งถึงมากกว่าหนึ่งครั้ง แม้ผู้ส่งจะส่งเพียงครั้งเดียวก็ตาม แต่นั่นไม่ใช่ปัญหา ถ้าผู้รับถูกออกแบบมาให้รับมือกับสถานการณ์นี้ได้
graph LR
MM["Messaging Mechanism"] -->|"Open"| Recv["Receiver<br/>{ Status == Open }"]
MM -->|"Open (ส่งซ้ำ)"| Recv
เมื่อใดที่ข้อความอาจถูกส่งมากกว่าหนึ่งครั้ง ผู้รับต้องถูกออกแบบให้จัดการได้อย่างถูกต้อง Idempotent Receiver อธิบายว่าผู้รับ request ทำ operation ในลักษณะที่ ให้ผลลัพธ์เหมือนเดิมเสมอ ไม่ว่าจะทำซ้ำกี่ครั้ง ดังนั้นถ้าข้อความเดียวกันมาถึงหลายครั้ง ผู้รับก็จัดการได้อย่างปลอดภัย — อาจใช้การกรองซ้ำ (de-duplication) แล้วเพิกเฉยข้อความที่ซ้ำ หรือทำ operation ซ้ำโดยให้ผลลัพธ์ตรงกับครั้งก่อนเป๊ะ
เนื่องจากกลไก messaging นำการสื่อสารแบบ asynchronous Request-Response เข้ามาเสมอ การมี latency อยู่บ้างจึงเป็นเรื่องปกติและคาดเดาได้ คำขอบริการ (เกือบ) ไม่ควร block จนกว่าบริการจะเสร็จ ดังนั้นการออกแบบโดยคำนึงถึง messaging แปลว่าเราวางแผนเผื่อ latency ไว้แต่ต้น ซึ่งทำให้ทางออกโดยรวมทนทานขึ้นมากตั้งแต่แรก
เมื่อลูกค้ากดสั่งซื้อ context “คำสั่งซื้อ” เผยแพร่ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ชื่อ OrderPlaced ออกมา context “คลังสินค้า” และ “การจัดส่ง” ต่าง subscribe event นี้เพื่อทำงานต่อ ถ้ากลไก messaging ส่ง OrderPlaced ซ้ำเพราะ ACK หาย context “คลังสินค้า” ในฐานะ Idempotent Receiver ต้องไม่ตัดสต็อกซ้ำ — เช็กจาก order id ก่อนว่าเคยประมวลผลแล้วหรือยัง
ตัวอย่างจริงของ Context Mapping (An Example in Context Mapping)
หัวข้อที่มีชื่อว่า “ตัวอย่างจริงของ Context Mapping (An Example in Context Mapping)”กลับมาที่ตัวอย่างระบบประกันภัยที่เคยคุยกันในบทก่อน มีคำถามว่า “Policy ตัวจริง” (policy of record) ควรอยู่ที่ context ไหน จำได้ว่ามีชนิด Policy อยู่ในสาม Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ที่ต่างกัน คือ Underwriting Context, Claims Context และ Inspections Context
graph TD UW["Underwriting Context<br/>(Policy)"] CL["Claims Context<br/>(Policy)"] IN["Inspections Context<br/>(Policy)"]
เป็นไปได้ว่า “policy ตัวจริง” ควรอยู่ที่ฝ่าย Underwriting เพราะเป็นจุดที่มันถือกำเนิด สมมติว่าใช่ คำถามต่อมาคือ — แล้ว context อื่นจะรู้ถึงการมีอยู่ของมันได้อย่างไร?
เมื่อ component ชนิด Policy ถูกออกใน Underwriting Context มันสามารถเผยแพร่ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ชื่อ PolicyIssued ออกมา ผ่านการ subscribe แบบ messaging context อื่นก็จะตอบสนองต่อ event นั้นได้ ซึ่งอาจรวมถึงการสร้าง component Policy ที่สอดคล้องกันขึ้นใน context ที่บริโภค
graph TD UWPolicy["Policy"] --> Issued["PolicyIssued"] Issued -->|"causes creation"| CLPolicy["Policy<br/>(Claims Context)"] Issued -->|"causes creation"| INPolicy["Policy<br/>(Inspections Context)"]
PolicyIssued Domain Event จะบรรจุ identity ของ Policy ตัวจริง ไว้ ซึ่งในที่นี้คือ policyId component ใดก็ตามที่ถูกสร้างขึ้นใน subscribing context จะ เก็บ identity นี้ไว้ เพื่อให้สาวกลับไปยัง Underwriting Context ต้นทางได้ ในตัวอย่างนี้ identity ถูกบันทึกไว้ในชื่อ issuedPolicyId
graph LR
subgraph SubC["Subscribing Context"]
SubPolicy["Policy<br/>- issuedPolicyId"]
end
subgraph UWC["Underwriting Context"]
UWPolicy["Policy<br/>- policyId"]
IssuedData["Issued Policy Data"]
end
SubPolicy -->|"Query issuedPolicyId"| IssuedData
ถ้าต้องการข้อมูล Policy มากกว่าที่ PolicyIssued Domain Event ให้มา subscribing context สามารถ query กลับ ไปที่ Underwriting Context ได้เสมอ โดยใช้ issuedPolicyId เป็นกุญแจในการ query
บางครั้งการ เติมข้อมูลให้อ้วน (enrichment) — ใส่ข้อมูลใน Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ให้พอกับความต้องการของผู้บริโภคทุกฝ่าย — ก็มีข้อดี เพราะให้ความเป็นอิสระ (autonomy) แก่ผู้บริโภคมากขึ้น ถ้า autonomy คือสิ่งที่ขับเคลื่อนการออกแบบ ให้เลือก enrichment
แต่อีกด้านหนึ่ง การคาดเดาทุกชิ้นข้อมูลที่ผู้บริโภคทุกรายจะต้องการนั้นยาก และอาจ enrich มากเกินไปจนกลายเป็นปัญหา เช่น การยัดข้อมูลเยอะๆ ลง event อาจเป็นการตัดสินใจที่แย่ด้านความปลอดภัย ถ้าเป็นเช่นนั้น การออกแบบ event แบบ บาง (thin) แล้วมี model สำหรับ query ที่อุดมและมีระบบ security ให้ผู้บริโภคร้องขอ ก็อาจเป็นทางเลือกที่ต้องการ — และบางสถานการณ์ก็เรียกหาส่วนผสมที่สมดุลของทั้งสองแบบ
แล้วการ query กลับไปยัง Underwriting Context จะทำงานอย่างไร? เราออกแบบ Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design แบบ RESTful พร้อม Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design บน Underwriting Context ได้ การยิง HTTP GET ด้วย issuedPolicyId ก็จะดึง IssuedPolicyData กลับมา
graph LR
subgraph UWC["Underwriting Context [OHS / PL]"]
IssuedData["Issued Policy Data"]
end
Sub["Subscribing Context"]
IssuedData -->|"GET /policies/{issuedPolicyId}"| Sub
ส่วนรายละเอียดข้อมูลของ PolicyIssued Domain Event เอง — เช่นว่าควรบรรจุอะไรบ้าง — Vernon ยกยอดไปอธิบายในบทถัดไปที่ว่าด้วย Tactical Design ด้วย Domain Events
อาจสงสัยว่าเกิดอะไรขึ้นกับตัวอย่าง Agile Project Management Context ที่เคยใช้ในบทก่อน การแวะไปยัง domain ธุรกิจประกันภัยเป็นการเปิดโอกาสให้สำรวจ DDD ผ่านหลายตัวอย่าง ซึ่งช่วยให้เข้าใจ DDD ได้ดีขึ้น ไม่ต้องห่วง — บทถัดไปจะกลับมาที่ Agile Project Management Context อีกครั้ง โดยตัวอย่างนั้นมี Core DomainCore Domainsubdomain ที่สำคัญและสร้างความได้เปรียบในการแข่งขันมากที่สุด เป็นเหตุผลที่เราต้องสร้างซอฟต์แวร์เองแทนการซื้อ ควรทุ่มทีมเก่งที่สุดลงตรงนี้Strategic Design เป็น Scrum (ประกอบด้วย Product, BacklogItem, Release, Sprint, Task, Team ฯลฯ) เชื่อมต่อกับ context สนับสนุนต่างๆ ผ่าน Context Mapping
สรุป (Summary)
หัวข้อที่มีชื่อว่า “สรุป (Summary)”บทนี้พาทำความรู้จัก Context MappingContext Mappingกิจกรรมการระบุ Bounded Context และนิยามความสัมพันธ์ระหว่างกัน (เช่น Partnership, Shared Kernel, ACL) เพื่อจัดการการเชื่อมต่อระหว่างทีม/ระบบStrategic Design อย่างรอบด้าน เริ่มจาก ความสัมพันธ์เก้าแบบ บน Context MapContext Mapแผนภาพที่แสดง Bounded Context ทั้งหมดและความสัมพันธ์ระหว่างกัน ช่วยให้เห็นภาพรวมและจุดเชื่อมต่อ/แปลภาษาในระบบใหญ่Strategic Design — PartnershipPartnershipสองทีมที่หากฝ่ายใดล้มเหลวอีกฝ่ายก็ส่งงานไม่ได้ จึงต้องวางแผนและจัดการการเชื่อมต่อร่วมกัน feature ที่พึ่งพากันถูกจัดให้เสร็จใน release เดียวกันStrategic Design ที่ผูกชะตาสองทีมเข้าด้วยกัน, Shared KernelShared Kernelสองทีมตกลงแบ่ง model ส่วนเล็ก ๆ มาใช้ร่วมกันโดยมีขอบเขตชัดเจน ส่วนนี้ห้ามแก้โดยไม่ปรึกษากัน เพราะกระทบทั้งสองฝ่ายStrategic Design ที่แบ่ง model ส่วนเล็กมาใช้ร่วม, Customer-SupplierCustomer / Supplierความสัมพันธ์ที่ทีมต้นน้ำ (supplier) รับความต้องการของทีมปลายน้ำ (customer) เข้าไปวางแผนด้วย มีการเจรจาและจัดลำดับงานให้ชัดเจนStrategic Design ที่ต้นน้ำรับฟังปลายน้ำ, ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design ที่ปลายน้ำยอมตามใจ, Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design ที่กั้นแปลภาษาเพื่อปกป้อง model ของเรา, Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design กับ Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design ที่ต้นน้ำเปิดบริการตามมาตรฐาน, Separate WaysSeparate Waysประกาศว่า Bounded Context นี้ไม่เชื่อมต่อกับอันอื่นเลย เพื่อให้แต่ละฝ่ายหาทางแก้แบบง่าย ๆ เฉพาะของตน ใช้เมื่อค่าใช้จ่ายการเชื่อมต่อไม่คุ้มStrategic Design ที่เลือกไม่เชื่อมต่อเลย และ Big Ball of MudBig Ball of Mudระบบที่ปนเปกันยุ่งเหยิงไม่มีขอบเขตชัด ให้ลากเส้นล้อมไว้แล้วยอมรับสภาพ ไม่พยายามทำ model ละเอียดในนั้น และระวังไม่ให้ลามไป context อื่นStrategic Design ที่ต้องล้อมเส้นและกันไม่ให้ลาม
จากนั้นได้เห็นวิธีนำ Context Mapping ไปใช้จริงด้วยกลไกสามแบบ เรียงจากทนทานน้อยไปมาก — RPC ผ่าน SOAP ที่ดูง่ายแต่ผูกกันแน่นและขาดความทนทาน, RESTful HTTP ที่เน้น resource และ4 operation หลัก (ระวังอย่าให้ resource สะท้อน AggregateAggregateกลุ่มของ Entity และ Value Object ที่ถูกมองเป็นหนึ่งหน่วยเดียวเพื่อรักษาความถูกต้องของข้อมูล มีขอบเขตชัดเจน และเป็นหน่วยของ transactionTactical Design ตรงๆ จนกลายเป็น ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design) และ Messaging ที่ทนทานที่สุดเพราะตัด temporal coupling ออก แต่ต้องอาศัย At-Least-Once Delivery คู่กับ Idempotent Receiver
สุดท้ายตัวอย่างประกันภัยร้อยทุกอย่างเข้าด้วยกัน — Policy ตัวจริงอยู่ที่ Underwriting Context เผยแพร่ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design PolicyIssued ให้ context อื่นสร้าง Policy ของตัวเองพร้อมเก็บ issuedPolicyId ไว้สาวกลับ และเมื่อต้องการข้อมูลเพิ่มก็ query กลับผ่าน Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design แบบ REST นี่คือรากฐานที่จะต่อยอดประสบการณ์ Context Mapping ของเราต่อไป
- Context MapContext Mapแผนภาพที่แสดง Bounded Context ทั้งหมดและความสัมพันธ์ระหว่างกัน ช่วยให้เห็นภาพรวมและจุดเชื่อมต่อ/แปลภาษาในระบบใหญ่Strategic Design นิยามทั้ง ความสัมพันธ์ระหว่างทีม และ กลไกทางเทคนิค ระหว่างสอง Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design ที่เชื่อมกัน — ทิศของอิทธิพลคือ ต้นน้ำ (U) → ปลายน้ำ (D)Upstream / Downstreamความสัมพันธ์ที่การกระทำของฝ่ายต้นน้ำ (upstream) ส่งผลต่อฝ่ายปลายน้ำ (downstream) แต่ไม่ใช่ในทางกลับกัน — ทิศของอิทธิพลStrategic Design
- ความสัมพันธ์เก้าแบบ: PartnershipPartnershipสองทีมที่หากฝ่ายใดล้มเหลวอีกฝ่ายก็ส่งงานไม่ได้ จึงต้องวางแผนและจัดการการเชื่อมต่อร่วมกัน feature ที่พึ่งพากันถูกจัดให้เสร็จใน release เดียวกันStrategic Design, Shared KernelShared Kernelสองทีมตกลงแบ่ง model ส่วนเล็ก ๆ มาใช้ร่วมกันโดยมีขอบเขตชัดเจน ส่วนนี้ห้ามแก้โดยไม่ปรึกษากัน เพราะกระทบทั้งสองฝ่ายStrategic Design, Customer-SupplierCustomer / Supplierความสัมพันธ์ที่ทีมต้นน้ำ (supplier) รับความต้องการของทีมปลายน้ำ (customer) เข้าไปวางแผนด้วย มีการเจรจาและจัดลำดับงานให้ชัดเจนStrategic Design, ConformistConformistทีมปลายน้ำยอม “ตามใจ” model ของทีมต้นน้ำทั้งหมดเพื่อตัดความซับซ้อนในการแปลภาษา ใช้เมื่อทีมต้นน้ำไม่มีแรงจูงใจจะรองรับเราStrategic Design, Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design, Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design, Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design, Separate WaysSeparate Waysประกาศว่า Bounded Context นี้ไม่เชื่อมต่อกับอันอื่นเลย เพื่อให้แต่ละฝ่ายหาทางแก้แบบง่าย ๆ เฉพาะของตน ใช้เมื่อค่าใช้จ่ายการเชื่อมต่อไม่คุ้มStrategic Design, Big Ball of MudBig Ball of Mudระบบที่ปนเปกันยุ่งเหยิงไม่มีขอบเขตชัด ให้ลากเส้นล้อมไว้แล้วยอมรับสภาพ ไม่พยายามทำ model ละเอียดในนั้น และระวังไม่ให้ลามไป context อื่นStrategic Design
- เจอ legacy เละเทะให้กั้นด้วย Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design เสมอ — “อย่าพูดภาษาของระบบนั้น”
- กลไกเชื่อมต่อเรียงจากทนทานน้อยไปมาก: RPC/SOAP → RESTful HTTP → Messaging โดย messaging ที่ดีต้องมี At-Least-Once Delivery + Idempotent Receiver
- บูรณาการที่ปลอดภัยที่สุดคือให้ context หนึ่งเผยแพร่ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design แล้วอีก context ไป subscribe โดยพึ่งพาแค่ schema (Published LanguagePublished Languageภาษากลางที่จัดทำเป็นเอกสารชัดเจน ใช้เป็นสื่อแลกเปลี่ยนข้อมูลระหว่าง context เช่น มาตรฐานข้อมูลของอุตสาหกรรม มักใช้คู่กับ OHSStrategic Design) ไม่ใช่ class ของผู้เผยแพร่
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- Context Mapping — ภาพรวมของแนวคิด Context Map และความสัมพันธ์ทั้งเก้าแบบที่บทนี้อธิบายโดยละเอียด
- Anti-Corruption Layer — สรุปแก่นของ ACL ที่บทนี้ยกเป็น “ทางเลือกป้องกันตัวที่แข็งแรงที่สุด” เมื่อต้องเชื่อมกับระบบภายนอกหรือ legacy
- Shared Kernel — ขยายความรูปแบบ Shared Kernel หนึ่งในเก้าความสัมพันธ์บน Context Map ที่ต้องอาศัยความตกลงร่วมกันอย่างใกล้ชิด
- Domain Events — กลไกเบื้องหลังการเชื่อมต่อแบบ Messaging ที่บทนี้ใช้ในตัวอย่าง
PolicyIssued
เช็กความเข้าใจ — บทที่ 4
ข้อ 1 / 4Anticorruption Layer (ACL) ทำหน้าที่อะไร และฝ่ายใดเป็นผู้สร้าง?