เชื่อม Bounded Context ผ่าน HTTP ด้วย RPC และ REST
ในสองบทก่อนหน้าเราเห็นว่า การเชื่อม Bounded ContextBounded Contextขอบเขตที่ชัดเจน (มักเป็นระบบย่อยหรือทีมหนึ่ง) ที่ model หนึ่งใช้ได้และมีความหมายแน่นอน ภายในขอบเขตนี้ Ubiquitous Language สอดคล้องกัน 100%Strategic Design เข้าด้วยกันไม่ใช่แค่ทำให้ app คุยกันได้ แต่ต้องได้ทั้ง scalability และ fault tolerance ด้วย บทนี้เสนอทางเลือกที่มักถูกมองข้าม นั่นคือการใช้ HTTP — โพรโทคอลที่อุปกรณ์หลายพันล้านเครื่องบนอินเทอร์เน็ตเข้าใจตรงกัน
HTTP เป็นมาตรฐานที่รู้จักกันดี เมื่อแต่ละ Bounded Context มาจากต่างทีมและใช้เทคโนโลยีคนละชุดกัน HTTP จึงน่าสนใจเป็นพิเศษ เพราะมันเป็น platform-agnostic — ไม่ผูกกับภาษาหรือ stack ใดๆ เราจะเทียบสองวิธีหลักของการเชื่อมผ่าน HTTP คือ RPC (ทั้งแบบ SOAP และ JSON) กับ event-driven REST แล้วดูว่าวิธีไหนเหมาะกับสถานการณ์ใด
ทำไมถึงเลือก HTTP
หัวข้อที่มีชื่อว่า “ทำไมถึงเลือก HTTP”ก่อนจะลงรายละเอียดเทคนิค มาดูเหตุผลที่ HTTP คุ้มค่าต่อการเชื่อม Bounded Context กันก่อน
- ไม่ผูกกับ platform (No Platform Coupling) — แต่ละ context สร้างด้วยเทคโนโลยีอะไรก็ได้ ขอเพียงเคารพ public contract ของตัวเอง (คือรูปแบบ HTTP request/response) ทีมก็ refactor หรือเขียนใหม่ด้วยเทคโนโลยีอื่นได้อย่างอิสระ ตราบใดที่ contract ยังเหมือนเดิม การเชื่อมต่อกับ context อื่นก็ไม่พัง สิ่งนี้สร้างทั้ง loosely coupled application และ loosely coupled team
- ทุกคนเข้าใจ HTTP — แทบทุกภาษาและ runtime มี library รองรับ HTTP เต็มไปหมด และเวลาขยายทีม การหา dev ที่เข้าใจ HTTP ง่ายกว่าหาคนที่เชี่ยวชาญ messaging framework เฉพาะตัวมาก
- เครื่องมือและ library เติบโตเต็มที่ — เช่น Visual Studio ที่ generate proxy class ให้อัตโนมัติเมื่อชี้ไปยัง web service ทำให้เขียน code ที่ “ดูเหมือน” object-oriented ธรรมดา ทั้งที่เบื้องหลังคือการสื่อสารข้ามเครือข่าย
- Dogfooding APIs — เมื่อทุกการสื่อสารวิ่งผ่าน HTTP คุณอาจไม่ต้องแยกช่องทาง internal/external แต่ใช้ API ชุดเดียวกัน ทั้งภายในและให้บุคคลที่สาม การใช้ API ที่ share กับลูกค้าด้วยตัวเองเรียกว่า dogfooding — ช่วยให้เจอจุดเจ็บปวดเดียวกับที่ลูกค้าเจอ (ต่างจาก messaging ที่มักใช้ภายในเท่านั้น จึงต้องสร้าง API แยกอีกชุด)
HTTP มอบ อิสระจากการผูกเทคโนโลยี ระหว่าง Bounded Context และใช้ได้สองแบบหลัก: RPC (เหมาะกับงานง่าย เริ่มต้นได้เร็ว) กับ event-driven REST (ให้ fault tolerance และ scalability ที่ดีกว่า) — ไม่ว่าจะเลือกแบบใด จงใช้ทุกโอกาสที่เหมาะสมเพื่อ ทำให้แนวคิดของ domain ปรากฏชัด ใน contract
RPC: ซ่อนเครือข่ายไว้หลังการเรียก method
หัวข้อที่มีชื่อว่า “RPC: ซ่อนเครือข่ายไว้หลังการเรียก method”ถ้าต้องการระบบกระจายที่เชื่อมผ่าน HTTP ทางเลือกแรกคือ RPC (Remote Procedure Call) จุดเด่นของ RPC คือ abstraction แบบ “ซ่อนเครือข่าย” — การเรียกข้ามเครื่องดูเหมือนการเรียก method ในเครื่องเดียวกัน เหมาะเมื่อ ความเร็วในการพัฒนาสำคัญ หรือความต้องการด้าน scale ยังไม่สูงนัก แต่ข้อเสียคือ tight coupling ที่ติดมากับ RPC ทำให้ scale ยากและทำทีมแบบหลวมๆ ได้ลำบาก
สมมติสตาร์ทอัพโซเชียลมีเดียที่กำลังเติบโต ปัจจุบันเป็น app ก้อนเดียวแบบ Big Ball of MudBig Ball of Mudระบบที่ปนเปกันยุ่งเหยิงไม่มีขอบเขตชัด ให้ลากเส้นล้อมไว้แล้วยอมรับสภาพ ไม่พยายามทำ model ละเอียดในนั้น และระวังไม่ให้ลามไป context อื่นStrategic Design ที่ Bounded Context เป็นเพียง library ซึ่งผูกกันด้วย binary dependency เมื่อแก้ context หนึ่งก็ทำให้อีก context พังเป็นลูกโซ่ เราจะแก้ด้วยการแยกแต่ละ context ออกเป็น app เดี่ยวที่สื่อสารกันผ่าน HTTP เท่านั้น โดยใช้ RPC ข้ามเครือข่ายแทนการเรียก method ภายใน process เพื่อตัด binary dependency ทิ้งไป

ในระบบเดิม Discovery เรียก method FindUsersFollowers() ของ class FollowerDirectory ที่อยู่ใน Account Management โดยตรง — นี่คือ binary dependency ที่ผูก2 context ไว้แน่น เราจะแทนมันด้วย RPC ชื่อเดียวกันที่วิ่งข้ามเครือข่าย
public static List<Account> FindRecommendedUsers(string accountId){ var fd = AccountManagement.FollowerDirectory; // เรียกข้าม bounded context แบบ in-process — คือ binary dependency ที่เป็นปัญหา var followers = fd.FindUsersFollowers(accountId); return ApplyCleverRecommendationsAlgorithm(followers);}SOAP — RPC แบบ “เต็มสูบ”
หัวข้อที่มีชื่อว่า “SOAP — RPC แบบ “เต็มสูบ””ทางเลือกดั้งเดิมคือ SOAP (Simple Object Access Protocol) ที่เพิ่ม layer อีกชั้นบน HTTP โดยบรรจุข้อมูลไว้ใน payload อย่างละเอียด เช่น metadata ของ type และ function ทำให้เครื่องมือ (tooling) พัฒนาไปได้ไกล ตัวอย่างเช่นใน .NET ที่ใช้ WCF ประกาศ RPC ได้ด้วยแอตทริบิวต์เพียงไม่กี่ตัว
[ServiceContract] // บอก WCF ว่า class นี้มี RPC อยู่public interface IFollowerDirectory{ [OperationContract] // บอกว่า method นี้คือ RPC List<Follower> FindUsersFollowers(string accountId);}WCF ร่วมกับ Visual Studio จะ generate proxy class บนฝั่ง client ให้อัตโนมัติ — “networking ที่ได้มาฟรีๆ” เช่นนี้คือเสน่ห์ของ SOAP ฝั่ง Discovery เพียงสร้าง proxy แล้วเรียก method ตามปกติ การเรียกนั้นจะกลายเป็น RPC ข้ามเครือข่ายโดยที่ code แทบไม่ต่างจากตอนรันในเครื่องเดียว
แต่ SOAP เสื่อมความนิยมลงมาก เพราะ รูปแบบข้อความซับซ้อนและเยิ่นเย้อ (XML envelope ที่ยาวเหยียด) API สมัยใหม่จึงไม่ค่อยสร้างด้วย SOAP แล้ว ควรระมัดระวังหากจะเปิด public SOAP API แต่หากใช้ภายในและเหมาะกับงาน ก็ไม่ใช่เรื่องน่ากังวลนัก
ปัจจุบันนิยมทำ RPC over HTTP ด้วย payload เบาๆ อย่าง plain XML หรือ JSON แทน SOAP เช่น ASP.NET Web API ทำ JSON-RPC ได้ง่าย — แลกกับการต้องเขียน code ทำ HTTP request และ parse response เองนิดหน่อย (ไม่มี proxy auto-generate เพราะ JSON ไม่มี metadata รวยเท่า SOAP) แต่ข้อมูลที่วิ่งบนสายสะอาดและเบากว่ามาก ทำให้ debug ง่ายขึ้น
ไม่ว่าจะ SOAP หรือ JSON-RPC ก็มี จุดอ่อนเชิงโครงสร้าง เหมือนกัน เพราะ temporal coupling (ทั้งสองฝั่งต้องออนไลน์พร้อมกัน):
- scale ยาก — ถ้าโซ่ RPC พาดผ่าน 3 context อาจต้อง scale ทั้ง 3 context พร้อมกันเพื่อเร่งความเร็ว
- fault tolerance ต่ำ — ถ้า Account Management ล่ม Discovery ก็ล่มตาม เพราะ RPC ข้ามไปดึง followers ไม่ได้
หลายบริษัทที่มีความต้องการด้าน scale มหาศาลก็ยังใช้ RPC over HTTP เป็นกลยุทธ์หลักได้ แต่เมื่อทีมเติบโตขึ้น มักเริ่มเจอจุดเจ็บปวด — เวลาส่วนใหญ่หมดไปกับการ “ดับไฟ” มากกว่าการส่งมอบ feature หลายทีมจึงค่อยๆ ย้ายไปสู่แนวทาง event-driven และ asynchronous
ลองนึกว่า Booking Bounded Context ต้องการสถานะล่าสุดของ Cargo จึงยิง RPC ไปถาม Tracking Bounded Context ทุกครั้งที่ลูกค้าเปิดหน้าจอ ถ้า Tracking ล่ม หน้าจอ booking ก็ค้างตามทันที — และหากวันคริสต์มาสมีคนเช็กสถานะพร้อมกันเป็นล้าน ก็ต้อง scale ทั้ง2 context ไปพร้อมกัน นี่คือ temporal coupling ในสนามจริง
REST: ออกแบบใหม่เพื่อ scale และทนความผิดพลาด
หัวข้อที่มีชื่อว่า “REST: ออกแบบใหม่เพื่อ scale และทนความผิดพลาด”REST เป็นคำที่ถูกเข้าใจผิดและใช้ผิดกันบ่อยมาก ก่อนสร้างระบบ RESTful ใดๆ จึงต้องเข้าใจก่อนว่าแท้จริงแล้ว REST คืออะไร Roy Fielding เป็นผู้แนะนำ REST สู่โลกในฐานะ architectural style ที่อิงหลักการซึ่งทำให้อินเทอร์เน็ตประสบความสำเร็จ
REST APIs must be hypertext-driven. — Roy Fielding (2008)
แก่นของ REST มีสามเรื่อง:
- Resources — request ในระบบ RESTful คือการขอ “ทรัพยากร” (resource) เช่นเอกสาร หน้าเว็บ หรือสื่อ เข้ากันได้ดีกับ DDD เพราะแนวคิดใน domain แสดงเป็น resource ได้ ช่วยเผยแพร่ Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design เช่น
B2bTransactionsหรือPersonalTransactionsปรากฏเป็น URI ตรงๆ — ต่างจาก RPC ที่ตั้งชื่อแบบคำสั่ง (imperative) เลียนแบบการเรียก method เพิ่มเติม resource หนึ่งมีได้หลาย representation (JSON, XML, HTML) โดย client เลือกผ่าน headerAccept - Hypermedia — มนุษย์ท่องเว็บด้วยการคลิก link เครื่องก็ทำได้เช่นกัน โดยฝัง hyperlink ไว้ใน resource แล้วเดินจาก resource หนึ่งไปอีก resource ด้วยการตาม link สิ่งนี้ทำให้ client ไม่ผูกกับ URI — client และ server วิวัฒน์อิสระจากกันได้ (ต่างจาก SOAP ที่มักเปราะเพราะ client/server ผูกกันแน่น) และยังเป็นโอกาสแสดง Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ผ่าน link ที่แทน “ขั้นตอนถัดไป” ของ workflow
- Statelessness — application state (เช่น ของในตะกร้าสินค้า) ไม่ควรเก็บไว้บน server แต่เก็บไว้ฝั่ง client แล้วส่งมาทุกครั้งที่ server ต้องใช้ ทำให้เมื่อ server เครื่องหนึ่งมีปัญหา เครื่องอื่นก็เข้ามารับช่วงต่อได้ทันที — นี่คือรากฐานของ fault tolerance และ scalability
REST ใช้ความสามารถของ HTTP เต็มที่
หัวข้อที่มีชื่อว่า “REST ใช้ความสามารถของ HTTP เต็มที่”REST อิงหลักการที่ทำให้เว็บสำเร็จ จึงโอบรับ convention ของ HTTP อย่างเต็มที่:
- Verbs —
GETอ่าน resource,DELETEลบ,PUTสร้างที่ URI ที่ต้องการ,POSTเพิ่มสมาชิกเข้า collection การมีชุดกริยาเดียวที่ใช้เหมือนกันทั่วอินเทอร์เน็ต ทำให้สร้าง generic client และ infrastructure (เช่น cache) ที่เข้าใจ convention ได้ง่าย - Status codes — จัดกลุ่มตามเลขตัวแรก:
2xxสำเร็จ,3xxredirect,4xxclient ผิด,5xxserver ผิด — agent ใดก็ตามบนเว็บเข้าใจ convention เดียวกัน - Headers — ใช้ caching header (สำคัญต่อ scalability ของ REST) และเพราะ REST เป็น stateless จึงนิยมส่งข้อมูล authentication/authorization ผ่าน header ด้วยโพรโทคอลอย่าง OAuth
ก่อนเรียก API ของคุณว่า RESTful ให้เช็กอย่างน้อยว่ามันมี hypermedia และ resources เป็นศูนย์กลางหรือไม่ Richardson Maturity Model (โดย Leonard Richardson, เผยแพร่โดย Martin Fowler) เป็น “บารอมิเตอร์” วัดว่า API ของคุณใกล้เป็น REST แท้แค่ไหน — หลายระบบที่เรียกตัวเองว่า REST จริงๆ แค่ทำ RPC over HTTP
ออกแบบ event-driven REST: เริ่มที่ domain เสมอ
หัวข้อที่มีชื่อว่า “ออกแบบ event-driven REST: เริ่มที่ domain เสมอ”เมื่อสตาร์ทอัพเติบโตจนจำนวนผู้ใช้พุ่งทวีคูณ และธุรกิจต้องการเพิ่ม premium account ที่โปรโมตให้ผู้ใช้ทั่วไป การเชื่อมแบบ RPC เดิมก็เริ่ม scale ไม่ไหว เราจึงออกแบบใหม่เป็น event-driven architecture ที่ใช้ REST และ HTTP เพื่อเลี่ยงการผูกติดกับเทคโนโลยีหรือผู้ขาย (vendor lock-in) ตลอดอายุระบบ
ขั้นแรกคือ เริ่มที่ domain — ร่างกระบวนการธุรกิจ (business process) ด้วย Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design ก่อนตัดสินใจเรื่องเทคนิค รูปที่ 13-9 เป็น component diagram ที่โฟกัสที่ domain command และ domain event (จึงใช้ได้กับทั้ง messaging และ REST เพราะอิสระจากเทคโนโลยี)

Began Following และ Premium Recommendations Identified คือ Domain EventDomain Eventสิ่งที่ “เกิดขึ้นแล้ว” ใน domain ซึ่งส่วนอื่นสนใจ แทนด้วยอ็อบเจ็กต์ที่เปลี่ยนแปลงไม่ได้ ตั้งชื่อเป็นอดีต เช่น CargoWasRouted ใช้สื่อสารข้าม AggregateTactical Design ที่เป็นส่วนหนึ่งของ Ubiquitous LanguageUbiquitous Languageภาษากลางที่ทุกคนในทีม (ทั้งนักพัฒนาและผู้เชี่ยวชาญธุรกิจ) ใช้ร่วมกัน อิงกับ domain model โดยตรง คำเดียวกันต้องหมายถึงสิ่งเดียวกัน และสะท้อนลงใน codeStrategic Design — ทุกคนในบริษัทเข้าใจตรงกัน และเป็นแนวคิดแกนของธุรกิจ เมื่อ event เหล่านี้วิ่งข้าม Bounded Context มันก็ทำหน้าที่เป็น Integration EventIntegration Eventevent ที่สื่อสาร “ข้าม” Bounded Context หรือข้าม microservice ผ่าน event bus — ต่างจาก Domain Event ที่ทำงานภายใน context เดียว (in-process)Architecture
หลักการอื่นที่นำมาใช้:
- SOA — แยกแต่ละ Bounded Context ให้ทีมเดียวเป็นเจ้าของ ทำให้ loose coupling อยู่ในเอื้อมมือ สิ่งที่เปลี่ยนไปเมื่อใช้ SOA ร่วมกับ HTTP คือ contract ระหว่างทีมไม่ใช่ class ใน code อีกต่อไป แต่เป็นรูปแบบ HTTP request/response
- Event-driven และ reactive — HTTP ไม่รองรับ publish/subscribe มาแต่กำเนิด จึงไม่สามารถ push event ออกไปหา subscriber ได้ทันทีแบบ message bus ทางออกของ REST คือให้ client poll เพื่อตรวจหาความเปลี่ยนแปลง แม้การ poll มักถูกมองว่า scale ได้ไม่ดี แต่การใช้ caching convention ของ HTTP ก็ช่วยลบล้างปัญหานี้ได้

ข้อพิจารณาสำคัญต่อ scalability คือ granularity ของ project คำแนะนำเบื้องต้นในบทนี้คือเริ่มที่ 1 project ต่อ1 resource เพื่อให้ deploy และ scale แต่ละ resource ได้อย่างอิสระตามความต้องการของมัน
ในระบบ e-commerce เมื่อ Ordering Bounded Context บันทึก OrderPlaced มันสามารถ publish event นั้นเป็น Atom feed ส่วน Shipping และ Notification Bounded Context ก็ poll feed นั้นแล้วทำงานต่อตามจังหวะของตัวเอง — ถ้า Notification ล่มไปชั่วครู่ Ordering ก็ยังรับ order ใหม่ได้ตามปกติ ไม่ผูกชะตากันแบบ RPC
Hypermedia-driven API: client ผูกแค่ entry point
หัวข้อที่มีชื่อว่า “Hypermedia-driven API: client ผูกแค่ entry point”Hypermedia คือหัวใจของ REST เพราะมันทำให้ client/server วิวัฒน์อิสระ แต่ client ต้องรู้บางอย่างล่วงหน้า — นั่นคือ entry point resource ที่ client ควรผูกตัวเองไว้ จากนั้นแค่ตาม link ใน hypermedia ที่ตอบกลับมา

client ผูกแค่ URI ของ entry point เท่านั้น จากนั้นตาม link ที่ส่งกลับมาเป็น HTTP response จนถึง Followers resource การออกแบบแบบนี้ทำให้ Account Management เปลี่ยน URI ภายในได้โดยไม่ทำ client พัง
HAL และ Atom — 2 content type สำคัญ
หัวข้อที่มีชื่อว่า “HAL และ Atom — 2 content type สำคัญ”- HAL (Hypertext Application Language) — คือ JSON/XML ธรรมดาที่เพิ่ม convention สำหรับแทน hypermedia link link ทั้งหมดอยู่ใต้
_linksที่ root แต่ละ link ขึ้นต้นด้วย relation (เช่นself,accounts) และมีhrefมีแค่ linkselfที่บังคับ ที่เหลือ optional
{ "_links": { "self": { "href": "http://localhost:4100/accountmanagement" }, "accounts": { "href": "http://localhost:4101/accountmanagement/accounts" }, "beganfollowing": { "href": "http://localhost:4102/accountmanagement/beganfollowing" } }}- Atom (
application/atom+xml) — มาตรฐานสร้าง RSS feed จึงเหมาะเป็น “feed ของ event” การใช้ Atom feed เป็นกระแสของ event คือ building block หลัก ของการสร้างระบบ event-driven แบบกระจายด้วย REST ในบทนี้
อีกเทคนิคที่ช่วยแก้ปัญหา “การนำทางที่ไม่มีประสิทธิภาพ” (ต้องตาม link เป็นร้อยกว่าจะถึง resource ที่ต้องการ) คือ URI template — link ที่มี templated: true พร้อม placeholder เช่น accounts/{accountId} ให้ client แทนค่า ID ที่ต้องการเอง ลดจากร้อย request เหลือเพียงครั้งเดียว
ในตัวอย่าง หนังสือ bind request body เข้ากับ class BeganFollowing ที่เป็นทั้ง domain event และโครงสร้างเก็บข้อมูล เพื่อความกระชับ — แต่ในระบบ production จริง ไม่แนะนำ เพราะทำให้ contract ภายนอกผูกแน่นกับ model ภายใน เปลี่ยน model ทีไรอาจทำ client พัง ควรมีชั้นแปลง (เทียบกับแนวคิด Anticorruption LayerAnticorruption Layer (ACL)ชั้นกั้นที่ทีมปลายน้ำสร้างขึ้นเพื่อแปลง model ของระบบอื่นให้เป็นภาษาของ model ตัวเอง ป้องกันไม่ให้ model ภายนอกมา “ปนเปื้อน” model เราStrategic Design / Open Host ServiceOpen Host Service (OHS)ทีมต้นน้ำเปิดบริการเป็นโพรโทคอล/API ที่นิยามไว้ชัดเจน ให้ทุกคนที่ต้องเชื่อมต่อมาใช้ได้ เหมาะกับระบบที่มีผู้ใช้หลายฝ่ายStrategic Design)
Atom feed: publish, persist, และ poll
หัวข้อที่มีชื่อว่า “Atom feed: publish, persist, และ poll”หัวใจของ event-driven REST คือวงจรสามส่วน: บันทึก event → publish เป็น feed → consumer poll และประมวลผล
1. Persist event — เมื่อมี POST เข้ามาที่ followers resource controller จะสร้าง domain event แล้วบันทึกลง Event Store (เครื่องมือของ Greg Young) โดย append เข้า stream ชื่อ BeganFollowing
[HttpPost]public IHttpActionResult IndexPost(string accountId, Follower follower){ var evnt = new BeganFollowing { AccountId = accountId, FollowerId = follower.AccountId }; EventPersister.PersistEvent(evnt); // append เข้า stream "BeganFollowing" return RedirectToRoute("Account Followers", new { accountId });}2. Publish เป็น Atom feed — อีก component ดึง event ล่าสุดจาก Event Store มาแปลงเป็นรายการใน SyndicationFeed แล้วตอบกลับด้วย content type application/atom+xml component ที่ publish event เป็น Atom feed เหมือน message-publishing component ในระบบ messaging
ในระบบ scale สูงที่มี event เป็นพันต่อวินาที feed เดียวจะใช้งานไม่ได้และเปลือง bandwidth มหาศาล ทางออกคือกำหนดจำนวน event ต่อ feed แล้ว archive เมื่อเต็ม โดยแต่ละ feed มี hypermedia link ไปยัง archive ก่อนหน้า/ถัดไป (ดู IETF RFC 5005 “Feed Paging and Archiving”)
3. Consume ด้วยการ poll — การ consume Atom feed ที่เปิดเผย domain event ก็เหมือนการ subscribe ข้อความในระบบ messaging แต่กลับด้านจาก “ถูก push” เป็น “poll แล้วดึงเอง” logic ทั่วไปมีสามขั้น:
- ดึง event ชุดถัดไป เริ่มจาก ID ของ event ล่าสุดที่ประมวลผลแล้ว (หรือตัวแรกถ้ายังไม่เคยทำ)
- ประมวลผลแต่ละ event ตาม domain policy
- เก็บ ID ของ event ล่าสุดที่ประมวลผลไว้
while (true){ var atomFeed = FetchFeed(); // ตาม link ใน hypermedia จาก entry point → feed var unprocessed = GetUnprocessedEvents(atomFeed.Items.ToList()); if (unprocessed.Any()) ProcessEvents(unprocessed); // ใช้กฎ domain ที่นี่ แล้วจำ ID ล่าสุด Thread.Sleep(1000); // poll สูงสุดวินาทีละครั้ง}สังเกตว่า consumer ผูกแค่ URI ของ entry point แล้วตาม _links["beganfollowing"] ไปหา feed เอง — client/server จึง decouple กันจริง
ดูแลรักษาและข้อจำกัดของ REST
หัวข้อที่มีชื่อว่า “ดูแลรักษาและข้อจำกัดของ REST”Versioning — การปรับปรุงเล็กๆ ทำได้โดยไม่ทำ client พัง ถ้าเปลี่ยนแบบ backward-compatible เช่นเพิ่ม field ใหม่ต่อท้าย resource (client เดิมที่ผูกกับ format เก่าไม่พัง) ส่วนการรื้อใหญ่ (breaking change) นิยมใส่ version ใน URI (/v2/accountmanagement/) หรือใน HTTP header (Version: 2)
Monitoring — ข้อดีของ HTTP คือมีเครื่องมือ monitoring สำเร็จรูปเสียบใช้ได้ทันที (เช่น New Relic) หรือเก็บ custom metric เอง (เช่น StatsD)
แต่ REST ก็มีข้อจำกัดเมื่อเทียบกับ messaging — ส่วนใหญ่คือ ต้องลงแรงพัฒนามากขึ้นตอนเริ่มต้น เพื่อชดเชย feature ที่ messaging มีมาให้พร้อม:
- Fault tolerance น้อยกว่า out-of-the-box — ถ้า Event Store ล่มตอนพยายามเก็บ event จะ ไม่มีการกู้คืนอัตโนมัติ เมื่อมันกลับมา (ต่างจาก messaging ที่ retry ข้อความให้) ต้องเสริม store-and-forward, queue หรือ load balancer/cluster เอง
- Eventual ConsistencyEventual Consistencyความถูกต้องที่ “ตามมาทีหลัง” ไม่ใช่ทันที ใช้กับการอัปเดตข้าม Aggregate ผ่าน Domain Event แทนการบังคับให้ตรงกันในทันทีภายใน transaction เดียวTactical Design — ระบบ share-nothing ที่สื่อสาร async ย่อมเจอ eventual consistency สูง consumer ที่ poll feed จะยังไม่เห็นข้อมูลใหม่จนกว่าจะ poll และประมวลผลรอบถัดไป — รับมือด้วยการใช้ transaction เล็กลง, roll-forward ไปสู่สถานะใหม่ และ retry หลายครั้ง
แต่ข้อเสียเหล่านี้อาจกลายเป็นข้อได้เปรียบตลอดอายุ project — คุณมี framework ที่ต้องดูแลน้อยลง และเข้าใจกลไกการสื่อสารของระบบกระจายของตัวเองอย่างลึกซึ้งขึ้น
- RPC (SOAP/JSON) — เริ่มเร็ว เหมาะกับงานง่าย แต่ tight coupling ทำให้ scale ยากและ fault tolerance ต่ำ (temporal coupling)
- Event-driven REST — อิง resource, hypermedia, statelessness และ Atom feed ของ domain event มอบ scalability และ fault tolerance เทียบเท่า messaging โดยพึ่งเพียง HTTP — แลกกับงานพัฒนาในช่วงเริ่มต้นที่มากกว่า
- ทั้งสองแบบ: HTTP request/response คือ contract ระหว่าง Bounded Context จงหลีกเลี่ยง breaking change และทำให้แนวคิดของ domain ปรากฏชัดในสัญญานั้นเสมอ
เจาะลึกแนวคิดในบทนี้ต่อได้ที่คลังอ้างอิง DevIQ:
- Anti-Corruption Layer — คำเตือนเรื่องอย่าผูก model-binding เข้ากับ domain class โดยตรงในบทนี้ คือการใช้แนวคิด ACL เพื่อกันไม่ให้ contract ภายนอกรั่วเข้าไปผูกกับ model ภายใน
- Context Mapping — การเลือกเชื่อม Bounded Context ด้วย RPC หรือ event-driven REST คือส่วนหนึ่งของการวาด Context Map ที่กำหนดว่าแต่ละ context สัมพันธ์และสื่อสารกันอย่างไร
- Domain Events —
Began FollowingและPremium Recommendations Identifiedที่ publish ผ่าน Atom feed ในบทนี้ คือตัวอย่างของ Domain Event ที่ทำหน้าที่เป็น Integration Event ข้าม Bounded Context
เช็กความเข้าใจ — บทที่ 13
ข้อ 1 / 4เหตุผลหลักที่ HTTP น่าสนใจสำหรับการเชื่อม Bounded Context คืออะไร?