Style Specifics (รายละเอียดเฉพาะของสไตล์นี้)

หัวข้อต่อไปนี้จะอธิบาย EDA อย่างละเอียดมากขึ้น รวมถึงข้อควรพิจารณา pattern และ hybrid ต่างๆ ของสไตล์สถาปัตยกรรมที่ซับซ้อนนี้

Events Versus Messages (Event เทียบกับ Message)

Event-driven architecture ใช้ event ในการส่งผ่านและประมวลผลข้อมูล แต่ event ต่างจาก message จริงหรือ คำตอบคือใช่ มันต่างกันจริงๆ

event คือการ broadcast ให้ event processor อื่นทราบว่ามีบางสิ่งเกิดขึ้นแล้ว: “ฉันเพิ่งสั่งซื้อสินค้า” ในขณะที่ message เป็นเหมือนคำสั่งหรือคำถามมากกว่า เช่น “ช่วยชำระเงินสำหรับคำสั่งซื้อนี้” หรือ “ให้ตัวเลือกการจัดส่งสำหรับคำสั่งซื้อนี้หน่อย” นี่ไม่ใช่ความแตกต่างเล็กน้อยเลย เมื่อเราพูดถึง event processing เราหมายถึงการ ตอบสนอง ต่อสิ่งที่เกิดขึ้นแล้ว ในขณะที่ message อธิบายถึงสิ่งที่ ต้องทำ ในตัวอย่างของเรา จะเห็นได้ชัดว่า “ฉันเพิ่งสั่งซื้อสินค้า” เป็น event เพราะมันไม่ได้บอกว่าต้องประมวลผลอะไรต่อ แบบที่ message จะบอก สิ่งนี้แสดงให้เห็นถึงลักษณะ decoupled ของ EDA

ความแตกต่างหลักประการที่สองระหว่าง event และ message คือ event มักไม่ต้องการการตอบกลับจากผู้รับ ในขณะที่ message มักต้องการ สิ่งนี้ช่วยลดการสื่อสารไปมาระหว่าง event processor ทำให้พวกมัน decoupled จากกันมากขึ้นไปอีก

ความแตกต่างสำคัญอีกอย่างระหว่าง event กับ message คือ event มักถูก broadcast ไปยัง event processor หลายตัว ในขณะที่ message มักถูกส่งตรงไปยัง event processor เพียงตัวเดียว ในตัวอย่างการประมวลผลคำสั่งซื้อง่ายๆ ของเรา event processor หลายตัวสนใจและตอบสนองต่อ event order created ในขณะที่มี event processor เพียงตัวเดียวที่ตอบสนองต่อ message apply payment โดยทั่วไป event ใช้การสื่อสารแบบ publish and subscribe (หนึ่งต่อหลาย) ในขณะที่ message มักใช้การสื่อสารแบบ point-to-point (หนึ่งต่อหนึ่ง)

ความแตกต่างสุดท้ายระหว่าง event กับ message คือลักษณะทางกายภาพของช่องทางการสื่อสาร event ใช้ topic , stream หรือ notification service เพื่อให้ event processor หลายตัว subscribe เข้ามาฟัง event ได้ ในขณะที่ messaging มักใช้ queue หรือ messaging service เพื่อรับประกันว่ามี event processor ประเภทเดียวเท่านั้นที่จะได้รับ message นั้น

Event-driven architecture ใช้ event เป็นหลัก (ตามชื่อของมันนั่นเอง) แต่ก็สามารถใช้ message ได้เป็นบางครั้ง เช่น การขอข้อมูลจาก event processor อื่น (ดู “Data Topologies” และ “Request-Reply Processing” ) ต่อมาในบทนี้ เราจะแสดงให้เห็น mediated event-driven architecture (ดู “Mediated Event-Driven Architecture” ) ซึ่งใช้ message ในการควบคุมลำดับการประมวลผลของ event

คุณลองแยกดูสิว่าข้อความต่อไปนี้ข้อไหนเป็น event และข้อไหนเป็น message?

  • “Adventurous Air เที่ยวบิน 6557 เลี้ยวซ้าย มุ่งหน้า 230 องศา”

  • “ข่าวด่วน มีแนวอากาศเย็นเคลื่อนเข้าสู่พื้นที่แล้ว”

  • “โอเค นักเรียน เปิดไปหน้า 145 ในสมุดแบบฝึกหัด”

  • “สวัสดีทุกคน ขอโทษที่มาสายสำหรับการประชุมนะ”

มาดูกันทีละข้อ:

“Adventurous Air เที่ยวบิน 6557 เลี้ยวซ้าย มุ่งหน้า 230 องศา”

นี่คือ message เพราะมันเป็นคำสั่ง (สิ่งที่ต้องทำ) และเพราะมันมุ่งไปยังเป้าหมายเดียว คือนักบิน แม้ว่านักบินคนอื่นๆ อาจได้ยิน message นี้ด้วยก็ตาม

“ข่าวด่วน มีแนวอากาศเย็นเคลื่อนเข้าสู่พื้นที่แล้ว”

นี่คือ event มันถูก broadcast ไปยังหลายคน อธิบายสิ่งที่เกิดขึ้นแล้ว และผู้ประกาศข่าวไม่ได้คาดหวังคำตอบกลับ (แม้ว่า message บางอย่างก็ไม่ต้องการคำตอบกลับเช่นกัน)

“โอเค นักเรียน เปิดไปหน้า 145 ในสมุดแบบฝึกหัด”

ข้อนี้ค่อนข้างหลอกล่อนิดหน่อย ที่จริงแล้วนี่คือ message แม้จะถูก broadcast ไปยังนักเรียนหลายคนก็ตาม มันเป็น คำสั่ง ให้ทำอะไรบางอย่าง ไม่ใช่สิ่งที่เกิดขึ้นแล้ว (ซึ่งจะทำให้มันเป็น event) สิ่งนี้แสดงให้เห็นประเด็นสำคัญเกี่ยวกับความแตกต่างระหว่าง event กับ message: การ broadcast คำสั่ง (เช่น การให้เปิดไปหน้า 145 ในสมุดแบบฝึกหัด) ผ่านช่องทาง publish-and-subscribe ไม่ได้ทำให้มันกลายเป็น event ไปได้

“สวัสดีทุกคน ขอโทษที่มาสายสำหรับการประชุมนะ”

นี่คือ event เพราะการที่คนคนนี้มาสายสำหรับการประชุมได้เกิดขึ้นแล้ว มันยังถูก broadcast ไปยังหลายคนด้วย และไม่มีการคาดหวังคำตอบกลับ

Derived Events (Derived Event)

Derived event เป็นส่วนสำคัญและจำเป็นของ EDA มันถูกสร้างและ trigger โดย event processor หลังจาก ที่ได้รับ initiating event event processor หนึ่งตัวสามารถ trigger derived event ได้มากกว่าหนึ่งตัว ขึ้นอยู่กับการประมวลผลของมัน

ลองพิจารณา derived event ที่ถูก trigger เมื่อ event processor Payment เรียกเก็บเงินจากบัตรเครดิตของลูกค้าสำหรับการซื้อสินค้าใน Figure 15-3 ดังภาพประกอบใน Figure 15-4 การเรียกเก็บเงินจากบัตรเครดิตเกี่ยวข้องกับการตรวจสอบการฉ้อโกงที่อาจเกิดขึ้น (ประมวลผลโดย event processor Fraud Detection ) และการตรวจสอบวงเงินบัตรเครดิต (ประมวลผลโดย event processor Credit Limit ) EDA สามารถใช้ event เดียว ( creditcard charged ) เพื่อทำทั้งสองกิจกรรมพร้อมกันได้

Derived Events

Figure 15-4. Derived events are generated in response to the initiating event (Derived Event ถูกสร้างขึ้นเพื่อตอบสนองต่อ Initiating Event)

สังเกตว่าการกระทำเดียวนี้สร้าง derived event ขึ้นมามากมายแค่ไหน ใน event processor Fraud Detection มันจะ trigger derived event ที่เป็นไปได้สองแบบ: แบบหนึ่งถ้า processor ตรวจพบการฉ้อโกง อีกแบบถ้าไม่พบ derived event ทั้งสองแบบนี้จำเป็นทั้งคู่ เพราะ event processor ต่างๆ อาจดำเนินการต่อแตกต่างกันไปตามผลลัพธ์ของการตรวจสอบการฉ้อโกง

ลองดู derived event จาก event processor Credit Limit บ้าง อย่างแรก derived event limit okay บอกให้ระบบส่วนที่เหลือทราบว่าไม่มีความเสี่ยงสำหรับการซื้อครั้งนี้ และลูกค้ามีวงเงินเหลืออยู่มาก ในความเป็นจริง event นี้ยังสามารถเก็บจำนวนวงเงินที่ลูกค้าเหลืออยู่ไว้ใน payload ได้ด้วย ซึ่งอาจเป็นประโยชน์ต่อ event processor ปลายทางอื่นๆ อย่างที่สอง derived event limit warning ที่เตือนว่ายอดคงเหลือของบัตรใกล้ถึงวงเงินแล้ว อาจเป็นประโยชน์ต่อ event processor ปลายทางอื่นๆ เช่น event processor Notification ที่สามารถแจ้งลูกค้าว่าใกล้ถึงวงเงินเครดิตแล้ว สุดท้าย derived event limit exceeded ซึ่งร้ายแรง เป็นที่สนใจของ event processor หลายตัว รวมถึง Notification , Decline Purchase และอาจรวมถึง event processor ด้าน marketing อย่าง Extend Credit Limit ที่ขยายวงเงินเครดิตของลูกค้าโดยอัตโนมัติเพื่อให้การซื้อผ่านได้

สิ่งนี้แสดงให้เห็นว่า derived event มากกว่าหนึ่งตัวสามารถถูก trigger จาก event processor เดียวได้ อย่างไรก็ตาม ต้องระวังอย่าให้ติดกับดัก antipattern Swarm of Gnats ซึ่งเป็นกรณีที่หน่วยประมวลผลหนึ่งส่ง event ที่ละเอียดเกินไปออกมามากเกินไป (ดู “The Swarm of Gnats Antipattern” )

Triggering Extensible Events (การ Trigger Event ที่ขยายได้)

ใน EDA โดยทั่วไปแล้วเป็นแนวปฏิบัติที่ดีที่ event processor แต่ละตัวจะประกาศสิ่งที่มันทำให้ระบบส่วนที่เหลือทราบ ไม่ว่าจะมี event processor ตัวอื่นสนใจการกระทำนั้นหรือไม่ก็ตาม

เมื่อไม่มี event processor ใดสนใจหรือตอบสนองต่อ event นั้น เราเรียกมันว่า extensible derived event เพราะถึงอย่างไรมันก็รองรับ architectural extensibility ด้วยการมอบ “hook” ที่มีอยู่แล้วในตัว เผื่อไว้กรณีที่การประมวลผล event นั้นต้องการฟังก์ชันเพิ่มเติมในภายหลัง ตัวอย่างเช่น สมมติว่าในขั้นตอนหนึ่งของ event process ที่ซับซ้อน (ดังภาพประกอบใน Figure 15-5 ) event processor Notification สร้างอีเมลที่ส่งให้ลูกค้าเพื่อแจ้งการกระทำหนึ่งๆ จากนั้นก็ประกาศว่าได้ส่งอีเมลแล้วให้ระบบส่วนที่เหลือทราบผ่าน derived event ใหม่ ( email sent ) เนื่องจากไม่มี event processor ตัวใดฟังหรือตอบสนองต่อ event นี้อยู่ในตอนนี้ message นั้นก็แค่หายไปเฉยๆ (หรือถูกเพิกเฉยในกรณีของ event streaming) นั่นอาจดูเหมือนเป็นการเสียทรัพยากรไปเปล่าๆ แต่จริงๆ แล้วไม่ใช่ สมมติว่าธุรกิจตัดสินใจจะวิเคราะห์อีเมลทั้งหมดที่ส่งให้ลูกค้า ทีมงานสามารถเพิ่ม event processor ใหม่อย่าง Email Analyzer เข้าไปในระบบโดยรวมได้ด้วยความพยายามเพียงเล็กน้อย และไม่ต้องแก้ไข event processor ตัวอื่นเลย เพราะข้อมูลอีเมลมีอยู่แล้วผ่าน derived event email sent

Notification event ignored

Figure 15-5. Notification event is sent, but ignored and not used (Event ของ Notification ถูกส่งออกไปแต่ถูกเพิกเฉยและไม่ถูกใช้)

Asynchronous Capabilities (ความสามารถด้าน Asynchronous)

สไตล์สถาปัตยกรรมแบบ event-driven มีลักษณะเฉพาะตัวคือพึ่งพาการสื่อสารแบบ อะซิงโครนัสเป็นหลัก ทั้งการประมวลผลแบบ fire-and-forget (ไม่ต้องการการตอบกลับ) และแบบ request/reply (เมื่อต้องการการตอบกลับจาก event consumer ดู “Request-Reply Processing” ) การสื่อสารแบบอะซิงโครนัสเป็นเทคนิคที่ทรงพลังในการเพิ่ม responsiveness โดยรวมของระบบ

ใน Figure 15-6 ผู้ใช้กำลังโพสต์รีวิวสินค้าบนเว็บไซต์ ในตัวอย่างนี้ comment service ใช้เวลา 3,000 มิลลิวินาทีในการตรวจสอบและโพสต์คอมเมนต์นั้น คอมเมนต์ต้องผ่าน parsing engine หลายตัว: การตรวจคำที่ไม่เหมาะสม การตรวจข้อความที่แสดงถึงการดูหมิ่น (เช่น “คิดช้า” หรือ “คิดอะไรไม่ออก”) และสุดท้ายการตรวจบริบทเพื่อให้แน่ใจว่าคอมเมนต์นั้นเกี่ยวกับสินค้า (ไม่ใช่แค่การระบายเรื่องการเมือง)

เส้นทางด้านบนใน Figure 15-6 โพสต์คอมเมนต์โดยใช้การเรียก RESTful แบบซิงโครนัส นั่นหมายถึง network latency 50 มิลลิวินาทีสำหรับ service ในการรับโพสต์ 3,000 มิลลิวินาทีในการตรวจสอบและโพสต์คอมเมนต์ และอีก 50 มิลลิวินาทีของ latency เพื่อบอกผู้ใช้ว่าคอมเมนต์ถูกโพสต์แล้ว ดังนั้นเวลารวมในการโพสต์คอมเมนต์จากมุมมองผู้ใช้คือ 3,100 มิลลิวินาที ทีนี้ลองดูเส้นทางด้านล่างซึ่งใช้ messaging แบบอะซิงโครนัส ที่นี่เวลารวมในการโพสต์ของผู้ใช้อยู่ที่เพียง 25 มิลลิวินาที เทียบกับ 3,100 มิลลิวินาที ระบบยังคงใช้เวลา 25 มิลลิวินาทีในการรับคอมเมนต์และ 3,000 มิลลิวินาทีในการโพสต์ รวมเป็น 3,025 มิลลิวินาที แต่จากมุมมองของผู้ใช้ปลายทาง มีเพียง 25 มิลลิวินาทีเท่านั้นก่อนที่ระบบจะตอบกลับว่าได้รับคอมเมนต์แล้ว (แม้ว่าจริงๆ แล้วยังไม่ได้ถูกโพสต์)

Synchronous versus asynchronous

Figure 15-6. Synchronous versus asynchronous communication (การสื่อสารแบบ Synchronous เทียบกับ Asynchronous)

ความแตกต่างของเวลาตอบสนองระหว่าง 3,100 มิลลิวินาที กับ 25 มิลลิวินาที นั้นน่าตกใจมาก แต่ก็มีข้อควรระวังอยู่หนึ่งอย่าง คือในเส้นทางซิงโครนัสด้านบน ผู้ใช้ปลายทางจะได้รับ การรับประกัน ว่าคอมเมนต์ของตัวเองถูกโพสต์แล้ว แต่ในเส้นทางอะซิงโครนัสด้านล่าง การโพสต์เป็นเพียงการรับทราบ (acknowledged) พร้อมคำสัญญาว่า ในที่สุด มันจะถูกโพสต์ จะเกิดอะไรขึ้นถ้าคอมเมนต์ของผู้ใช้มีคำหยาบคายและระบบปฏิเสธมัน ไม่มีทางที่จะแจ้งให้ผู้ใช้ปลายทางทราบได้เลยหรือ? ถ้าผู้ใช้ต้องลงทะเบียนกับเว็บไซต์เพื่อโพสต์คอมเมนต์ ระบบก็สามารถส่ง message แจ้งปัญหาเกี่ยวกับคอมเมนต์นั้นและแนะนำวิธีแก้ไขให้ได้

ตัวอย่างนี้แสดงให้เห็นความแตกต่างระหว่าง responsiveness (เวลาที่ใช้ในการส่งข้อมูลกลับไปให้ผู้ใช้) กับ performance (เวลาที่ใช้ในการเพิ่มคอมเมนต์ลงในฐานข้อมูล) เมื่อผู้ใช้ไม่ต้องการข้อมูลตอบกลับอะไรเลย (นอกจากการรับทราบหรือข้อความขอบคุณ) แล้วจะให้พวกเขารอไปทำไม responsiveness คือเรื่องของการแจ้งให้ผู้ใช้ทราบว่าการกระทำนั้นถูกรับแล้วและจะถูกประมวลผลในไม่ช้า ในขณะที่ performance คือเรื่องของการทำให้กระบวนการทั้งหมดเร็วขึ้น ในเส้นทางอะซิงโครนัสด้านล่าง สถาปนิกไม่ได้ทำอะไรเพื่อปรับปรุงวิธีที่ comment service ประมวลผลคอมเมนต์เลย (นั่นคือการแก้ปัญหาด้าน responsiveness ) ถ้าสถาปนิกใช้เวลาปรับปรุง comment service ให้ดีขึ้น เช่น รัน parsing engine ด้านข้อความและไวยากรณ์ทั้งหมดแบบขนาน ใช้ caching และเทคนิคที่คล้ายกันอื่นๆ แต่ยังคงใช้การสื่อสารแบบซิงโครนัสอยู่ นั่นก็จะเป็นการแก้ปัญหาด้าน performance โดยรวมแทน

นั่นเป็นตัวอย่างง่ายๆ แล้วถ้าเป็นตัวอย่างที่ซับซ้อนกว่านั้นล่ะ คราวนี้ลองมาดูการ ซื้อขายหุ้น ออนไลน์ ที่ผู้ใช้กำลังซื้อหุ้นแบบอะซิงโครนัส จะเกิดอะไรขึ้นถ้าไม่มีทางแจ้งข้อผิดพลาดให้ผู้ใช้ทราบ?

แม้การสื่อสารแบบอะซิงโครนัสจะช่วยเพิ่ม responsiveness ได้อย่างมาก แต่การจัดการข้อผิดพลาด (error handling) ก็เป็นปัญหาใหญ่ ความยากในการจัดการเงื่อนไขข้อผิดพลาดนี้เพิ่มความซับซ้อนให้กับสไตล์สถาปัตยกรรมนี้ “Error Handling” แสดงให้เห็น pattern ของ reactive architecture สำหรับจัดการความท้าทายด้าน error-handling นั่นคือ pattern Workflow Event

นอกจากด้าน responsiveness แล้ว การสื่อสารแบบอะซิงโครนัสยังช่วยให้เกิด dynamic decoupling ในระดับดีและหลีกเลี่ยง antipattern Dynamic Quantum Entanglement ซึ่งเกิดขึ้นเมื่อ architectural quantum สองตัวสื่อสารกันผ่านการสื่อสารแบบซิงโครนัส (คุณคงจำได้จาก Chapter 7 ว่า architectural quantum คือส่วนหนึ่งของระบบที่สามารถ deploy แยกจากส่วนอื่นของระบบได้ และถูกผูกไว้ด้วย synchronous dynamic coupling และ architectural characteristic นั้นอยู่ในระดับ quantum) เนื่องจาก architectural quanta สองตัวนี้ตอนนี้ต้องพึ่งพากันแล้ว มันจึงกลายเป็น entangled (พันกัน) โดยพื้นฐาน การพึ่งพากันนี้ทำให้พวกมันกลายเป็น architectural quantum เดียว การสื่อสารแบบอะซิงโครนัสสามารถช่วยแก้พันธนาการของ architectural quanta ได้ เพราะมันขจัด dynamic dependency นั้นออกไป

เพื่อแสดงให้เห็นประเด็นสำคัญนี้ ลองพิจารณาระบบทั้งสองใน Figure 15-7 ในตัวอย่างนี้ ระบบ Portfolio Management สร้างคำสั่งซื้อขายเพื่อซื้อหุ้น มันส่งคำสั่งซื้อขายนี้ไปยังระบบ Trade Order แบบซิงโครนัส ซึ่งจะทำการตรวจสอบด้าน compliance และสร้างคำสั่งซื้อขาย เนื่องจากการสื่อสารระหว่างสองระบบนี้เป็นแบบซิงโครนัส ระบบ Portfolio Management จึงจำเป็นต้อง block และรอหมายเลขยืนยันคำสั่งซื้อขายจากระบบ Trade Order ทั้งสองระบบนี้จึงพันกันและกลายเป็น architectural quantum เดียว

Single architectural quantum

Figure 15-7. These systems form a single architectural quantum due to synchronous dynamic coupling (ระบบเหล่านี้รวมกันเป็น Architectural Quantum เดียวเนื่องจาก Synchronous Dynamic Coupling)

ความสำคัญของการพันกันนี้คือ architectural characteristic ตอนนี้อยู่ ระหว่าง ระบบทั้งสองนี้ ถ้าระบบ Trade Order ใช้งานไม่ได้หรือไม่ตอบสนอง ระบบ Portfolio Management ก็ไม่สามารถส่งคำสั่งซื้อขายได้ สิ่งนี้ยังทำให้ responsiveness แย่ลงด้วย ถ้าระบบ Trade Order ทำงานช้า ระบบ Portfolio Management ก็จะช้าตามไปด้วย scalability ก็ได้รับผลกระทบเช่นกัน เพราะถ้าระบบ Portfolio Management จำเป็นต้อง scale ระบบ Trade Order ก็ต้อง scale ตามไปด้วย ถ้ามันไม่ scale หรือ scale ไม่ได้ ระบบ Portfolio Management ก็จะ scale ตามความต้องการไม่ได้เช่นกัน

สถาปนิกสามารถแก้พันธนาการของ architectural quanta เหล่านี้ได้ด้วยการแทนที่การเรียกแบบซิงโครนัสด้วยการเรียกแบบอะซิงโครนัสระหว่างสองระบบนี้ ดังภาพประกอบใน Figure 15-8 .

Multiple architectural quanta

Figure 15-8. These systems form separate architectural quanta due to their asynchronous dynamic coupling (ระบบเหล่านี้แยกเป็น Architectural Quanta คนละตัวเนื่องจาก Asynchronous Dynamic Coupling)

ด้วยการใช้การสื่อสารแบบอะซิงโครนัส ระบบ Portfolio Management สามารถส่งคำสั่งซื้อขายผ่าน queue หรือวิธีการอะซิงโครนัสอื่นๆ ได้ ทำให้ไม่ต้องรอให้ระบบ Trade Order สร้างคำสั่งซื้อขายเสร็จก่อน เมื่อระบบ Trade Order ทำการตรวจสอบ compliance และสร้างคำสั่งซื้อขายเสร็จแล้ว ก็สามารถส่งหมายเลขยืนยันไปยังระบบ Portfolio Management ผ่านช่องทางอะซิงโครนัสแยกต่างหากได้ การขจัด dependency นี้ออกจาก dynamic coupling ของทั้งสองระบบทำให้พวกมันกลายเป็น architectural quanta แยกกันสองตัว ถ้าระบบ Trade Order ใช้งานไม่ได้หรือไม่ตอบสนอง ระบบ Portfolio Management ก็ยังคงส่งคำสั่งซื้อขายต่อไปได้ โดยรู้ว่าในที่สุดคำสั่งเหล่านั้นจะถูกสร้างและหมายเลขยืนยันจะถูกส่งกลับมา

Broadcast Capabilities (ความสามารถด้าน Broadcast)

อีกหนึ่งลักษณะเฉพาะของ EDA คือความสามารถในการ broadcast event โดยไม่ต้องรู้ว่ามีหน่วยประมวลผลอื่นใด (ถ้ามี) กำลังรับ event เหล่านั้นอยู่ หรือจะประมวลผลอะไรเพื่อตอบสนอง ดังที่ Figure 15-9 แสดงให้เห็น สิ่งนี้ทำให้ event processor แยกออกจากกันแบบ dynamic

Broadcast messaging

Figure 15-9. Broadcasting events to other event processors (การ Broadcast Event ไปยัง Event Processor อื่นๆ)

ความสามารถด้าน broadcast เป็นส่วนสำคัญของหลาย pattern รวมถึง eventual consistency และ complex event processing (CEP) ตัวอย่างเช่น ราคาของตราสารที่ซื้อขายในตลาดหุ้นเปลี่ยนแปลงบ่อยมาก ทุกครั้งที่มีการเผยแพร่ราคา ticker ใหม่ (ราคาปัจจุบันของหุ้นตัวหนึ่งๆ) event processor หลายตัวอาจตอบสนองต่อราคาใหม่นี้ (เช่น trade analytics หรือการซื้อขายหุ้น) อย่างไรก็ตาม event processor ที่เผยแพร่ราคาล่าสุดเพียงแค่ broadcast มันออกไปเท่านั้น โดยไม่รู้เลยว่าข้อมูลนั้นจะถูกนำไปใช้อย่างไร สิ่งนี้เรียกว่า semantic decoupling คือ event processor ตัวหนึ่งไม่รู้ (หรือไม่พึ่งพา) การกระทำของ event processor ตัวอื่นเลย

Event Payload (Event Payload)

ข้อมูลที่บรรจุอยู่ใน event เรียกว่า payload payload มีความหลากหลายมาก อาจเป็นแค่คู่ key-value ง่ายๆ หรือเป็นข้อมูลทั้งหมดที่จำเป็นสำหรับการประมวลผลปลายทาง สอง type พื้นฐานคือ data-based และ key-based event payload สถาปนิกต้องวิเคราะห์ trade-off อย่างระมัดระวังเพื่อตัดสินใจว่าตัวเลือกไหนเหมาะกับ event แต่ละประเภทที่ถูก trigger ในระบบ ในหัวข้อนี้เราจะอธิบาย payload ทั้งสอง type นี้พร้อม trade-off ที่เกี่ยวข้อง

Data-based event payloads (Data-Based Event Payload)

data-based event payload คือ event payload ที่ส่งข้อมูลทั้งหมดที่จำเป็นสำหรับการประมวลผล ในตัวอย่างที่แสดงใน Figure 15-10 ลูกค้าสั่งซื้อสินค้า อันดับแรก event processor Order Placement จะเพิ่มคำสั่งซื้อทั้งหมดลงในฐานข้อมูล (system of record) จากนั้นจะ broadcast event ที่ชื่อว่า order_placed ซึ่งมีรายละเอียดคำสั่งซื้อทั้งหมด (ในกรณีนี้คือ 45 attribute รวมทั้งหมด 500 KB ของหน่วยความจำ) event processor Payment ตอบสนองต่อ event นี้ด้วยการดึงข้อมูลจาก payload โดยเฉพาะ order ID ข้อมูลลูกค้า และยอดรวมของคำสั่งซื้อ แล้วนำไปใช้ในการชำระเงิน ในขณะเดียวกัน event processor Inventory Management ก็ใช้ item ID และจำนวนสินค้าจาก payload เพื่อปรับปรุงสต็อกปัจจุบันของสินค้าที่ถูกซื้อ

Data-based event payload

Figure 15-10. Data-based event payloads contain all the data necessary for processing (Data-Based Event Payload มีข้อมูลทั้งหมดที่จำเป็นสำหรับการประมวลผล)

event processor Payment และ Inventory Management ไม่จำเป็นต้อง query ฐานข้อมูลเพื่อดึงข้อมูลคำสั่งซื้อเลย เพราะข้อมูลมีอยู่แล้วใน payload นี่เป็นข้อได้เปรียบใหญ่ที่สุดอย่างหนึ่งของการใช้ data-based event payload ยิ่ง event processor query ฐานข้อมูลน้อยเท่าไหร่ performance, responsiveness และ scalability ก็จะยิ่งดีขึ้นเท่านั้น นอกจากนี้ ด้วยลักษณะ decoupled ที่ dynamic สูงของ EDA event processor Order Placement อาจไม่รู้ด้วยซ้ำว่ามี event processor ตัวไหนบ้างที่ตอบสนองต่อ event นี้ หรือต้องการข้อมูลอะไรบ้างสำหรับการประมวลผล การส่งข้อมูลทั้งหมดไปใน payload จึงรับประกันได้ว่า event processor ที่ตอบสนองแต่ละตัวจะมีข้อมูลที่ต้องการในการประมวลผลของตัวเองครบถ้วน event processor บางตัวอาจไม่มีสิทธิ์เข้าถึงฐานข้อมูลที่มีข้อมูลคำสั่งซื้อด้วยซ้ำ โดยเฉพาะใน data topology ที่มี bounded context เข้มงวด แบบแบ่งตาม domain หรือ database-per-service (ดู “Data Topologies” )

แม้ข้อดีเหล่านี้จะแสดงให้เห็นวิธีสร้างระบบที่ responsive, scalable และยืดหยุ่นมากขึ้น แต่ data-based payload ก็มีข้อเสียหลายประการ ข้อแรกคือมันยากกว่าที่จะรักษา data consistency และ data integrity เมื่อมี multiple systems of record เนื่องจากข้อมูลคำสั่งซื้อทั้งหมดถูกเก็บทั้งในฐานข้อมูล และ ใน event ที่ trigger ในระบบ ข้อมูลคำสั่งซื้อจึงหลุด sync กันได้ง่าย โดยเฉพาะถ้าคำสั่งซื้อถูกอัปเดตระหว่างการประมวลผล

ตัวอย่างเช่น สมมติว่าลูกค้าสั่งซื้อสินค้าหนึ่งร้อยชิ้น แต่ตั้งใจจะสั่งแค่ชิ้นเดียว แล้วรู้ตัวว่าผิดพลาดทันทีหลังจากส่งคำสั่งซื้อ หรือบางทีลูกค้าอาจรู้ตัวหลังสั่งซื้อว่าใช้ที่อยู่จัดส่งผิด (เรื่องนี้เคยเกิดกับผู้เขียนทั้งสองคนหลายครั้งแล้ว) ไม่ว่ากรณีไหน ลูกค้าจะอัปเดตคำสั่งซื้อด้วยข้อมูลที่ถูกต้องทันที ฐานข้อมูลซึ่งเป็น system of record เดียวจะมีค่าที่ถูกแก้ไขแล้ว แต่ event บางส่วนที่มีค่าเก่าอาจยังไม่ถูกประมวลผลในทันที นั่นหมายความว่าค่าเก่าที่ไม่ถูกต้องซึ่งยังอยู่ระหว่างประมวลผลจะถูกใช้แทนค่าใหม่ที่ถูกต้อง ยิ่งไปกว่านั้น ใน EDA มันยากมากที่จะควบคุมจังหวะเวลาของ event ดังนั้นจึงเป็นไปได้ที่ค่าใหม่จะถูกประมวลผล ก่อน ค่าเก่า นั่นหมายความว่าถ้า event processor ตัวอื่นใช้ค่าเก่าที่ไม่ถูกต้อง ค่านั้นอาจไปทับค่าใหม่ที่ถูกต้องได้

ข้อเสียใหญ่ประการที่สองของ data-based event payload เกี่ยวข้องกับการจัดการ contract และ versioning เรารู้ว่าคำสั่งซื้อในระบบนี้มี 45 attribute เนื่องจากข้อมูลทั้งหมดถูกบรรจุอยู่ใน payload event จึงต้องการ contract บางอย่าง ซึ่งเป็นวิธีจัดโครงสร้างข้อมูลที่ถูกส่ง สถาปนิกต้องเผชิญกับการตัดสินใจมากมาย: ควรให้ payload type เป็น JSON object หรือไม่? หรือ XML object? contract ควรเป็นแบบ strict หรือ loose? (contract แบบ strict คือแบบที่ใช้ schema หรือ object definition บางอย่าง เช่น JSON schema, GraphQL spec หรือ class definition ในขณะที่ contract แบบ loose อาจใช้แค่คู่ name-value ของ JSON ธรรมดา) แต่ละการตัดสินใจเหล่านี้มาพร้อม trade-off มากมาย และแต่ละอย่างก็สร้าง tight static coupling ระหว่าง event processor

แล้วก็ยังมีเรื่อง versioning อีก สำหรับ strict contract สถาปนิกหรือนักพัฒนาอาจใช้ vendor MIME type ใน event header เพื่อระบุหมายเลข version วิธีนี้ช่วยให้ระบบมีความคล่องตัวมากขึ้นและรองรับ backward compatibility (เพื่อไม่ให้ event processor ตัวอื่นพัง) อย่างไรก็ตาม event processor ทุกตัวต้องใช้ตรรกะ versioning เดียวกัน ซึ่งต้องการ governance ที่เข้มงวด ถ้า event processor ตัวใดเพิกเฉยต่อ contract version เมื่อตอบสนองต่อ payload ของ strict contract การเปลี่ยน schema นั้นก็มีแนวโน้มที่จะทำให้ event processor นั้นล้มเหลว นอกจากนี้ยังยากมากที่จะ implement กลยุทธ์การสื่อสาร version และการเลิกใช้งาน (deprecation) ในสถาปัตยกรรมที่ decoupled และ asynchronous สูงอย่าง EDA ทั้งหมดนี้ทำให้ data-based event payload ค่อนข้างเปราะบาง

data-based event payload ยังอาจประสบปัญหา stamp coupling ซึ่งเป็น static coupling รูปแบบหนึ่งที่ module หลายตัว (ในที่นี้คือ event processor) ใช้ data structure ร่วมกัน แต่ใช้แค่บางส่วนของมัน (และในหลายกรณีก็ใช้คนละส่วนกัน) เมื่อสถานการณ์นี้เกิดขึ้น การเปลี่ยน data structure ที่ใช้ร่วมกันอาจต้องเปลี่ยน event processor ตัวอื่นด้วย แม้แต่ตัวที่ไม่ได้สนใจข้อมูลนั้นเลย

Figure 15-11 แสดงให้เห็นว่า stamp coupling ทำงานอย่างไรและผลกระทบเชิงลบต่อสถาปัตยกรรม ในตัวอย่างนี้ event processor Order Placement ส่ง event order_placed ที่ประกอบด้วย 45 attribute ซึ่งมีข้อมูลทั้งหมดเกี่ยวกับคำสั่งซื้อ ขนาด 500 KB event processor Inventory ตอบสนองต่อ event order_created แต่ต้องการเพียงสอง attribute คือ item_id และ quantity รวมกันเพียง 30 ไบต์เท่านั้น ในตัวอย่างนี้ การเปลี่ยนแปลง payload เช่น การลบ attribute ที่อยู่บรรทัดหนึ่งออก จะส่งผลกระทบต่อ event processor Inventory ทั้งที่มันไม่สนใจฟิลด์นั้นเลย

Stamp coupling

Figure 15-11. An example of stamp coupling, where another service only needs part of the data sent (ตัวอย่างของ Stamp Coupling ที่ Service อื่นต้องการแค่บางส่วนของข้อมูลที่ส่งมา)

ในตัวอย่างนี้ การใช้ contract versioning กับ strict contract ช่วยลดความเสี่ยงที่ event processor Inventory จะพังได้ แต่ในที่สุด เมื่อ contract version ถูกเลิกใช้หรือมี breaking change กับ contract เกิดขึ้น นักพัฒนาก็จะต้องทดสอบและ deploy มันใหม่อยู่ดี

ปัญหาหนึ่งของ stamp coupling ที่มักถูกมองข้ามคือ bandwidth utilization fallacy ข้อที่สามของ distributed computing คือ “bandwidth มีไม่จำกัด” แน่นอนว่ามันไม่จริง อันที่จริง ในสภาพแวดล้อมบน cloud ส่วนใหญ่ bandwidth คือสิ่งที่มีค่าใช้จ่ายสูงมาก ย้อนกลับไปที่ตัวอย่างใน Figure 15-11 ด้วย data-based payload ถ้าลูกค้าสั่งซื้อ 500 ออร์เดอร์ต่อวินาที การส่ง event ขนาด 500 KB เพียง event เดียวไปยัง event processor Inventory จะใช้ bandwidth ถึง 250,000 KB ต่อวินาที แต่ถ้าระบบส่งเฉพาะ 30 ไบต์ของข้อมูลที่ จำเป็น จริงๆ event นั้นจะใช้ bandwidth เพียง 15 KB ต่อวินาทีเท่านั้น นี่เป็นความแตกต่างที่น่าตกใจ และคุ้มค่าที่จะพิจารณาเมื่อใช้ data-based event payload

เหตุผลหนึ่งที่สถาปนิกบางครั้งจำกัด stamp coupling คือการใช้ consumer-driven contracts ซึ่งผู้บริโภค (consumer) แต่ละรายของ message จะมี contract ของตัวเองที่มีเฉพาะข้อมูลที่ตัวเองต้องการสำหรับการประมวลผล อย่างไรก็ตาม เนื่องจากความสามารถด้าน broadcast ของ EDA และเพราะระบบไม่สามารถรู้ได้เสมอไปว่า event processor ตัวไหนจะตอบสนองต่อ event มันจึงยากที่จะใช้ consumer-driven contract กับ event ใน event-driven architecture ด้วยเหตุนี้ (และเพื่อแก้ข้อเสียอื่นๆ ของ data-based event payload) สถาปนิกหลายคนจึงหันไปใช้ key-based event payload แทน .

Key-based event payload (Key-Based Event Payload)

key-based event payload คือ event payload ที่มีเพียง key ที่ระบุบริบทของ event เท่านั้น (เช่น order ID หรือ customer ID) เมื่อใช้ key-based event payload event processor ที่ตอบสนองต่อ event ต้อง query ฐานข้อมูลเพื่อดึงข้อมูลที่ต้องการมาใช้ประมวลผล event นั้น

เมื่อลูกค้าสั่งซื้อสินค้า event processor Order Placement จะเพิ่มคำสั่งซื้อลงในฐานข้อมูลและ trigger key-based event ที่ชื่อว่า order_placed event นี้มีค่า key เพียงค่าเดียวคือ order ID ในรูปแบบ JSON ง่ายๆ:

{
 "order_id": "123"
}

ข้อเสียหลักอย่างหนึ่งของ key-based event payload คือ event processor ที่ตอบสนองต่อ event แต่ละตัวต้อง query ฐานข้อมูลเพื่อดึงข้อมูลที่ต้องการมาประมวลผลคำสั่งซื้อ ตัวอย่างเช่น เมื่อ event processor Payment ตอบสนองต่อ event มันต้อง query ฐานข้อมูลเพื่อดึงข้อมูลคำสั่งซื้อที่จำเป็นสำหรับการประมวลผลการชำระเงิน event processor Inventory ก็ตอบสนองต่อ event นี้พร้อมกัน จึงต้อง query ฐานข้อมูลเพื่อดึง item ID และจำนวนสินค้าเช่นกัน สิ่งนี้อาจส่งผลเสียต่อ responsiveness, performance และ scalability และอาจทำให้ฐานข้อมูลรับภาระหนักเกินไป โดยเฉพาะในสถาปัตยกรรมที่ทำงานแบบขนานสูงและอะซิงโครนัสอย่าง event-driven architecture (ดู “Data Topologies” สำหรับวิธีลดความเสี่ยงนี้) key-based event payload ยังเป็นความท้าทายถ้าข้อมูลที่ต้องการเข้าถึงได้ยาก (เช่น อยู่ใน bounded context ของ event processor ตัวอื่น) Figure 15-12 แสดงเทคนิคนี้ให้เห็น

Key-based event payload

Figure 15-12. With key-based event payloads, only the context key is contained in the event (ใน Key-Based Event Payload มีเพียง Context Key เท่านั้นที่บรรจุอยู่ใน Event)

อย่างไรก็ตาม การใช้ key-based event payload ก็มีข้อดีหลายอย่าง ซึ่งบางอย่างอาจคุ้มค่ากว่าปัญหาด้าน performance และ scalability ข้อดีหลักประการแรกคือ data consistency และ data integrity โดยรวมที่ดีขึ้น เนื่องจากมี single system of record เพราะข้อมูลเกี่ยวกับ event อยู่ที่เดียวเท่านั้น (ฐานข้อมูล) key-based event payload จึงรองรับการเปลี่ยนแปลงข้อมูลระหว่างการประมวลผล event ได้ง่ายกว่า data-based event payload มาก

ข้อดีหลักประการที่สองคือ เนื่องจาก contract ใน key-based event payload นั้นเรียบง่ายและแทบไม่เปลี่ยนแปลง สถาปนิกจึงมักจะ implement มันด้วย JSON หรือ XML แบบ loose ที่ไม่มี schema ดังนั้น key-based event payload จึงไม่มีปัญหาเรื่องการจัดการการเปลี่ยนแปลง contract, versioning และกลยุทธ์การสื่อสารและการเลิกใช้งานเหมือนที่ data-based event payload มักเจอ

ข้อดีอีกอย่างของ key-based event payload คือมันไม่มีปัญหาด้าน stamp coupling และ bandwidth เหมือน data-based event payload เพราะไม่มีข้อมูล opaque ที่ผูกติดกับ event contract จึงเรียบง่าย เล็ก และใช้ bandwidth น้อยที่สุด ดังนั้นมันจึงมักทำงานได้เร็วกว่า data-based event payload ทั้งในแง่ network และ message broker

Trade-off summary (สรุป Trade-Off)

การเลือกระหว่าง data-based event payload กับ key-based data payload ต้องอาศัยการวิเคราะห์ trade-off อย่างรอบคอบ อย่าลืมว่ามันไม่ใช่ทางเลือกแบบทั้งหมดหรือไม่มีเลย: event แต่ละประเภทสามารถใช้ payload type ต่างกันได้ Table 15-1 สรุป trade-off ที่เกี่ยวข้องกับ data-based และ key-based event payload

Table 15-1. Data-based versus key-based event payloads (Data-Based เทียบกับ Key-Based Event Payload) | Criteria | Data-based payloads | Key-based payloads | | --- | --- | --- | | Performance and scalability | Good | Bad | | Contract management | Bad | Good | | Stamp coupling | Bad | Good | | Bandwidth utilization | Bad | Good | | Restricted database access | Good | Bad | | Overall system fragility | Bad | Good |

สังเกตว่า trade-off โดยรวมระหว่างสองตัวเลือกนี้สรุปได้ว่าเป็นเรื่องของ scalability และ performance เทียบกับการจัดการ contract และ bandwidth utilization ให้ถามตัวเองว่าอย่างไหนสำคัญกว่ากันสำหรับ event แต่ละตัว การประมวลผล event บางอย่างต้องการ scale และ performance ระดับสูงมาก ซึ่งกรณีนี้ data-based event payload จะเป็นตัวเลือกที่ดีกว่า ในขณะที่การประมวลผล event บางอย่างข้อมูลจะเปลี่ยนแปลงบ่อย ซึ่งกรณีนี้ key-based event payload อาจเหมาะสมกว่า

เช่นเดียวกับสิ่งส่วนใหญ่ใน software architecture การเลือกของสถาปนิกอยู่บน spectrum ไม่ใช่แค่แบบไบนารีง่ายๆ นี่คือเหตุผลที่ต้องระวังไม่ให้เกิดสิ่งที่เรียกว่า anemic event

Anemic events (Anemic Event)

anemic event คือ derived event ที่มี payload ซึ่งไม่มีข้อมูลเพียงพอที่จะช่วยให้ event processor ตัดสินใจได้ และขาดบริบทที่จำเป็นสำหรับการประมวลผลปลายทางต่อไป

Figure 15-13 แสดงตัวอย่าง anemic derived event ในตัวอย่างนี้ ลูกค้าได้อัปเดตข้อมูลบางอย่างใน user profile ของตัวเอง เมื่อข้อมูลนั้นถูกอัปเดตในฐานข้อมูลแล้ว event processor Customer Profile จะ trigger event profile_updated โดยใช้ key-based event payload ที่ส่งเพียง customer ID เป็นข้อมูล key

service ทั้งสามที่ตอบสนองต่อ event นี้จะได้รับเพียง customer ID และบริบทว่าโปรไฟล์ของลูกค้าถูกเปลี่ยนแปลง service แรก ( Service 1 ) ไม่รู้เลยว่าข้อมูลอะไรในโปรไฟล์ที่ถูกเปลี่ยน: ชื่อ ที่อยู่ หรือข้อมูลสำคัญอื่นๆ? น่าเสียดายที่การ query ฐานข้อมูลก็ตอบคำถามนี้ไม่ได้ ดังนั้น Service 1 จึงไม่รู้ว่าจะตอบสนองอย่างไรหรือควรทำอะไรต่อ Service 2 ตอบสนองต่อ event profile_updated แต่เมื่อดูจาก key เพียงอย่างเดียว มันไม่รู้ว่าต้องประมวลผลเพิ่มเติมหรือไม่ สุดท้าย Service 3 ตอบสนองต่อ event เดียวกันนี้ด้วย แต่ไม่รู้ว่าค่าก่อนหน้าคืออะไร จึงไม่สามารถประมวลผลของตัวเองได้ event processor ทั้งสามตัวนี้จำเป็นต้องตอบสนองต่อการอัปเดตโปรไฟล์ของลูกค้าในทางใดทางหนึ่ง แต่ทำไม่ได้เพราะขาดข้อมูล นี่คือ anemic event: event ที่ไม่มีข้อมูลเพิ่มเติมสำหรับประมวลผล event ต่อไป

Anemic events

Figure 15-13. An anemic event lacks enough context to process the event (Anemic Event ขาดบริบทเพียงพอสำหรับการประมวลผล Event)

เพื่อหลีกเลี่ยง anemic event แบบนี้ ให้รวมข้อมูลลูกค้าที่อัปเดตแล้ว พร้อมทั้งค่าก่อนหน้า ไปด้วย เนื่องจากฐานข้อมูลส่วนใหญ่มักไม่สะท้อน ข้อมูล นั้นไว้

นี่เป็นตัวอย่างของ spectrum ของความละเอียด (granularity) ของ event payload ปลายด้านซ้ายสุดของ spectrum คือ key-based event payload ที่มีเพียง key อยู่ใน event แม้วิธีนี้จะมีประโยชน์เมื่อสร้างหรือลบคำสั่งซื้อ แต่มันใช้ไม่ได้ดีเมื่อลูกค้าอัปเดตคำสั่งซื้อ ปลายด้านขวาสุดของ spectrum คือ data-based event payload ที่รวมข้อมูล ทั้งหมด เข้าไป ไม่ว่าจะจำเป็นหรือไม่ก็ตาม ตรงนี้เองที่ stamp coupling จะแสดงตัวออกมาให้เห็น สถานการณ์การอัปเดตโปรไฟล์ลูกค้าอยู่ตรงกลางระหว่างสองขั้วนี้ เพราะมันให้ระดับข้อมูลที่พอเหมาะ จึงหลีกเลี่ยงปัญหา anemic derived event ได้

The Swarm of Gnats Antipattern (Antipattern Swarm of Gnats)

เกี่ยวข้องกับ anemic event คือ antipattern ที่เรียกว่า Swarm of Gnats คุณคงรู้จัก gnats ในฐานะแมลงบินตัวเล็กๆ ที่น่ารำคาญ บินวนอยู่รอบหัวคุณ จนต้องหนีกลับเข้าบ้านในวันที่แดดสวยๆ ในขณะที่ anemic event เกี่ยวข้องกับความละเอียดของ payload ของ event antipattern Swarm of Gnats เกี่ยวข้องกับความละเอียดของตัว event ที่ถูก trigger เอง และจำนวน derived event ที่ถูก trigger จาก event processor หนึ่งตัว ถ้าสถาปนิก trigger derived event มากเกินไปจาก event processor ตัวเดียว ก็เสี่ยงที่จะติดกับดัก antipattern Swarm of Gnats

ลองพิจารณาตัวอย่างการชำระเงินด้วยบัตรเครดิตที่แสดงใน Figure 15-14 ที่ลูกค้าสั่งซื้อสินค้าและบัตรเครดิตของพวกเขาถูกเรียกเก็บเงินเพื่อจ่ายค่าสินค้า เมื่อบัตรเครดิตถูกเรียกเก็บเงิน event processor Payment จะ trigger event payment applied และ (โชคดีที่) event processor Fraud Detection ฟัง event นี้อยู่ event processor นี้จะวิเคราะห์ทุกการเรียกเก็บเงินเพื่อพิจารณาว่าถูกต้องหรือเป็นการฉ้อโกง ไม่ว่าผลจะออกมาเป็นอย่างไร event processor Fraud Detection จะ trigger derived event fraud_checked พร้อมผลลัพธ์ของการตรวจสอบการฉ้อโกงอยู่ใน payload

Coarse-grained event

Figure 15-14. An example of an event that is too coarse-grained (ตัวอย่างของ Event ที่หยาบเกินไป)

event processor สามตัวสนใจผลลัพธ์ของการตรวจสอบการฉ้อโกงบัตรเครดิต:

  • ถ้าพบการฉ้อโกง event processor Credit Card Locking จะล็อกบัตรเครดิตของลูกค้าเพื่อป้องกันการเรียกเก็บเงินเพิ่มเติม

  • event processor Customer Notify จะแจ้งลูกค้าถึงการฉ้อโกงที่อาจเกิดขึ้น

  • ถ้า ไม่ พบการฉ้อโกง event processor Purchase Profile จะอัปเดต อัลกอริทึม ของตัวเอง

น่าเสียดายที่เมื่อ derived event fraud_checked เพียงตัวเดียวถูก trigger event processor ทั้งหมด นี้ต้องตอบสนองต่อ event ตรวจสอบ payload หาผลลัพธ์ และตัดสินใจว่าจะดำเนินการหรือไม่ เนื่องจาก derived event นี้หยาบเกินไป event processor ทั้งหมดจึงต้องทำงานเพิ่มเติม: การวิเคราะห์ payload ของ derived event เดียวนี้เพื่อตัดสินใจว่าจะดำเนินการหรือไม่ ถ้าไม่พบการฉ้อโกง นี่ก็เป็นการเสีย bandwidth และพลังประมวลผลไปเปล่าๆ เพราะมีเพียง event processor Purchase Profile เท่านั้นที่ต้องดำเนินการ

วิธีที่มีประสิทธิภาพกว่ามากคือการ trigger derived event แยก สอง ตัว ( fraud_detected และ no_fraud_detected ) ดังที่แสดงใน Figure 15-15 ในที่นี้ derived event ที่ trigger โดย event processor Fraud Detection ให้บริบทอยู่ นอก payload ของ event ทำให้ event processor แต่ละตัวสามารถตัดสินใจได้ว่าจะตอบสนองหรือไม่ โดยไม่ต้องวิเคราะห์ payload ภายในของ event เลย

Appropriate event granularity

Figure 15-15. Triggering multiple events allows for more efficient processing and decision making (การ Trigger Event หลายตัวช่วยให้การประมวลผลและการตัดสินใจมีประสิทธิภาพมากขึ้น)

ในตัวอย่างนี้ การ trigger derived event หลายตัวสำหรับแต่ละผลลัพธ์ช่วยให้ event flow ดีขึ้น ลดความวุ่นวาย (churn) และประมวลผลได้มีประสิทธิภาพมากขึ้น อย่างไรก็ตาม การ trigger derived event มากเกินไป ก็จะนำไปสู่ antipattern Swarm of Gnats

สถานการณ์ที่แสดงใน Figure 15-16 แสดงให้เห็นว่า antipattern นี้เกิดขึ้นได้อย่างไร ลูกค้าเพิ่งย้ายที่อยู่และต้องเปลี่ยน user profile บนเว็บไซต์เพื่ออัปเดตที่อยู่เรียกเก็บเงินของบัตรเครดิต ที่อยู่จัดส่ง (ที่คำสั่งซื้อจะถูกส่งไป) และหมายเลขโทรศัพท์จากเบอร์บ้านเก่าเป็นเบอร์มือถือ เมื่อลูกค้ากดปุ่ม Submit สำหรับการเปลี่ยนแปลงโปรไฟล์เหล่านี้ event processor Customer Profile จะรับคำขออัปเดต อัปเดตฐานข้อมูล และ trigger event แยกสำหรับการอัปเดตแต่ละอย่างที่มีข้อมูลที่จำเป็นสำหรับการประมวลผลต่อ

Swarm of gnats antipattern

Figure 15-16. Triggering too many fine-grained derived events is known as the Swarm of Gnats antipattern (การ Trigger Derived Event ที่ละเอียดเกินไปมากเกินไป เรียกว่า Antipattern Swarm of Gnats)

ปัญหาของการ trigger event ที่ละเอียดเกินไปมากเกินไปคือมันสามารถทำให้ระบบอิ่มตัวและล้นไปด้วย derived event ที่ล้วนเกี่ยวข้องกับเรื่องเดียวกัน: ลูกค้าอัปเดตโปรไฟล์ของตัวเอง antipattern นี้ยังมักทำให้เกิด derived event เล็กๆ จำนวนมากจาก event processor อื่นๆ ด้วย จนสุดท้ายทำให้ยากที่จะเข้าใจ event flow โดยรวมของระบบ

เพื่อหลีกเลี่ยง antipattern นี้ สถาปนิกสามารถรวมการอัปเดตโปรไฟล์แต่ละอย่างเข้าเป็น derived event เดียว profile_updated สำหรับการกระทำทั้งหมด ที่มีข้อมูลก่อนและหลังของฟิลด์ที่ถูกอัปเดตทั้งหมด แนวทางที่มีประสิทธิภาพมากกว่านี้แสดงอยู่ใน Figure 15-17

Domain Event

Figure 15-17. Combining individual state changes into a single derived event avoids the Swarm of Gnats antipattern (การรวมการเปลี่ยนแปลง State แต่ละอย่างเข้าเป็น Derived Event เดียวช่วยหลีกเลี่ยง Antipattern Swarm of Gnats)

การกำหนดระดับความละเอียดที่เหมาะสมสำหรับ derived event นั้นค่อนข้างท้าทาย เราแนะนำให้เน้นที่ ผลลัพธ์ ของการประมวลผลหรือการเปลี่ยนแปลง state เพื่อหลีกเลี่ยง antipattern Swarm of Gnats และช่วยทำให้ event flow เรียบง่ายขึ้น

Error Handling (การจัดการข้อผิดพลาด)

pattern Workflow Event ของ reactive architecture เป็นวิธีหนึ่งในการจัดการ error handling ใน asynchronous workflow pattern นี้แก้ปัญหาทั้งด้าน resiliency และ responsiveness เพราะช่วยให้ระบบจัดการ error แบบอะซิงโครนัสได้โดยไม่กระทบต่อ responsiveness ของมัน

pattern Workflow Event ใช้การ delegate, containment และ repair ผ่าน workflow delegate ดังภาพประกอบใน Figure 15-18 ใน pattern นี้ event processor จะส่งข้อมูลผ่าน message channel ไปยัง event consumer แบบอะซิงโครนัส ถ้า event consumer พบ error ระหว่างประมวลผลข้อมูล มันจะ delegate error นั้นไปยัง service Workflow Processor ทันที แล้วไปทำงานกับ message ถัดไปใน event queue ต่อ วิธีนี้ทำให้ message ถัดไปถูกประมวลผลทันที ดังนั้น responsiveness โดยรวมจึงยังคงเดิม ถ้า event consumer ต้องเสียเวลามานั่งหาสาเหตุของ error มันก็จะไม่ได้ประมวลผล message ถัดไปใน queue ซึ่งจะทำให้ทั้ง message ถัดไปและ message อื่นๆ ที่รออยู่ใน queue ล่าช้าไปด้วย

เมื่อ service Workflow Processor ได้รับ error มันจะพยายามหาว่า message นั้นมีปัญหาอะไร บางทีอาจเป็น error แบบ static ที่กำหนดได้แน่นอน? มันสามารถวิเคราะห์ message ด้วยอัลกอริทึม machine-learning หรือ AI เพื่อหาความผิดปกติในข้อมูลได้ ไม่ว่าจะด้วยวิธีไหน workflow processor จะแก้ไขข้อมูลต้นฉบับ โดยอัตโนมัติผ่านโปรแกรม (คือไม่มีคนเข้ามาแทรกแซง) เพื่อพยายามซ่อมมัน แล้วส่งกลับไปยัง queue ต้นทาง event consumer จะเห็น message ที่ถูกอัปเดตนี้เป็น message ใหม่และพยายามประมวลผลมันอีกครั้ง หวังว่าครั้งนี้จะสำเร็จมากขึ้น

แน่นอนว่า workflow processor ไม่สามารถหาสาเหตุของปัญหาใน message ได้เสมอไป ในกรณีเหล่านี้ มันจะส่ง message นั้นไปยัง queue อีกตัวหนึ่ง ซึ่งจะถูกรับโดย dashboard บนเดสก์ท็อปของผู้ที่มีความรู้เกี่ยวกับเรื่องนั้น คนคนนี้จะดู message แก้ไขด้วยตนเอง แล้วส่งกลับไปยัง queue เดิม (โดยปกติผ่าน message header variable ที่ชื่อ reply-to)

Workflow event pattern

Figure 15-18. The Workflow Event pattern of reactive architecture (Pattern Workflow Event ของ Reactive Architecture)

สมมติว่าที่ปรึกษาการเทรดในส่วนหนึ่งของประเทศรับ trade order (คำสั่งว่าจะซื้อหุ้นตัวไหนและกี่หุ้น) ในนามของบริษัทเทรดขนาดใหญ่ในอีกส่วนหนึ่งของประเทศ ที่ปรึกษาจะรวบรวม trade order เป็นสิ่งที่เรียกกันทั่วไปว่า basket แล้วส่งไปยังโบรกเกอร์ที่อีกฝั่งของประเทศแบบอะซิงโครนัส ซึ่งจะเป็นผู้ซื้อหุ้นนั้น เพื่อให้ตัวอย่างง่ายขึ้น สมมติว่า contract สำหรับคำสั่งเทรดต้องเป็นไปตามรูปแบบต่อไปนี้:

ACCOUNT(String),SIDE(String),SYMBOL(String),SHARES(Long)

สมมติว่าบริษัทเทรดขนาดใหญ่ได้รับ basket ของคำสั่งเทรดหุ้น Apple (AAPL) ต่อไปนี้จากที่ปรึกษาการเทรด:

12654A87FR4,BUY,AAPL,1254
87R54E3068U,BUY,AAPL,3122
6R4NB7609JJ,BUY,AAPL,5433
2WE35HF6DHF,BUY,AAPL,8756 SHARES
764980974R2,BUY,AAPL,1211
1533G658HD8,BUY,AAPL,2654

คำสั่งเทรดที่สี่ ( 2WE35HF6DHF,BUY,AAPL,8756 SHARES ) มีคำว่า SHARES ต่อท้ายจำนวนหุ้นที่จะเทรด เมื่อบริษัทหลักประมวลผล trade order แบบอะซิงโครนัสเหล่านี้โดยไม่มีความสามารถในการจัดการ error เลย error ต่อไปนี้จะเกิดขึ้นใน service TradePlacement :

Exception in thread "main" java.lang.NumberFormatException:
    For input string: "8756 SHARES"
    at java.lang.NumberFormatException.forInputString
    (NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:589)
    at java.lang.Long.<init>(Long.java:965)
    at trading.TradePlacement.execute(TradePlacement.java:23)
    at trading.TradePlacement.main(TradePlacement.java:29)

เมื่อ exception นี้เกิดขึ้น เนื่องจากเป็นคำขอแบบอะซิงโครนัส จึงไม่มีผู้ใช้คนไหนที่จะตอบสนองและแก้ไข error นี้แบบซิงโครนัสได้เลย service TradePlacement ทำอะไรไม่ได้เลยนอกจาก log สภาวะ error นั้นไว้เท่านั้น

การใช้ pattern Workflow Event สามารถแก้ error นี้ได้โดยอัตโนมัติผ่านโปรแกรม เนื่องจากบริษัทหลักไม่สามารถควบคุมที่ปรึกษาการเทรดหรือข้อมูล trade-order ที่ส่งมาได้ มันจึงต้องตอบสนองเพื่อแก้ error ด้วยตัวเอง (ดู Figure 15-19 ) เมื่อ error เดียวกันเกิดขึ้น ( 2WE35HF6DHF,BUY,AAPL,8756 SHARES ) service TradePlacement จะ delegate error ผ่าน asynchronous messaging ไปยัง service Trade Placement Error เพื่อจัดการ error ทันที พร้อมส่งข้อมูล error เกี่ยวกับ exception นั้นไปด้วย:

Trade Placed: 12654A87FR4,BUY,AAPL,1254
Trade Placed: 87R54E3068U,BUY,AAPL,3122
Trade Placed: 6R4NB7609JJ,BUY,AAPL,5433
Error Placing Trade: "2WE35HF6DHF,BUY,AAPL,8756 SHARES"
Sending to trade error processor <-- delegate the error fixing and move on
Trade Placed: 764980974R2,BUY,AAPL,1211
...

service Trade Placement Error ซึ่งทำหน้าที่เป็น workflow delegate จะรับ error นั้นและตรวจสอบ exception เมื่อเห็นว่าปัญหาคือคำว่า SHARES ในฟิลด์ Number of Shares service Trade Placement Error จะตัดคำว่า SHARES ออก แล้วส่งคำสั่งเทรดกลับไปประมวลผลใหม่:

Received Trade Order Error: 2WE35HF6DHF,BUY,AAPL,8756 SHARES
Trade fixed: 2WE35HF6DHF,BUY,AAPL,8756
Resubmitting Trade For Re-Processing

ตอนนี้ service TradePlacement สามารถประมวลผลคำสั่งเทรดที่ถูกแก้ไขแล้วได้สำเร็จ:

...
trade placed: 1533G658HD8,BUY,AAPL,2654
trade placed: 2WE35HF6DHF,BUY,AAPL,8756 <-- this was the original trade in error

Workflow Event pattern example

Figure 15-19. Error handling with the Workflow Event pattern (การจัดการ Error ด้วย Pattern Workflow Event)

ผลข้างเคียงอย่างหนึ่งของการใช้ pattern Workflow Event คือ message ที่ถูกส่งไปยัง workflow processor แล้วถูกส่งกลับมาใหม่จะถูกประมวลผลนอกลำดับ (out of sequence) ในตัวอย่างการเทรดของเรา ลำดับของ message มีความสำคัญมาก เพราะการเทรดทั้งหมดภายในบัญชีหนึ่งๆ ต้องถูกประมวลผลตามลำดับ (เช่น การ SELL หุ้น IBM ต้องเกิดก่อนการ BUY หุ้น AAPL ภายในบัญชีเทรดเดียวกัน) การรักษาลำดับ message ภายในบริบทหนึ่งๆ (ในที่นี้คือหมายเลขบัญชีเทรด) จะซับซ้อน แม้จะไม่ใช่เรื่องที่เป็นไปไม่ได้ วิธีหนึ่งในการแก้ปัญหานี้คือให้ service TradePlacement เก็บหมายเลขบัญชีเทรดของคำสั่งที่ผิดพลาดไว้ใน queue การเทรดใดๆ ที่มีหมายเลขบัญชีเทรดเดียวกันนี้จะถูกเก็บไว้ใน queue ชั่วคราวเพื่อประมวลผลภายหลัง (ตามลำดับ first-in, first-out หรือ FIFO) เมื่อคำสั่งเทรดที่ผิดพลาดถูกแก้ไขและประมวลผลแล้ว service TradePlacement จะดึงคำสั่งเทรดที่เหลือของบัญชีเดียวกันออกจาก queue และประมวลผลตามลำดับ

Preventing Data Loss (การป้องกัน Data Loss)

สถาปนิกที่ทำงานกับการสื่อสารแบบอะซิงโครนัสมักกังวลเรื่อง data loss เสมอ นั่นคือเมื่อ event หรือ message ถูกทิ้งไปหรือไม่เคยไปถึงปลายทางเลย โชคดีที่มีเทคนิคพื้นฐานที่ใช้งานได้ทันทีสำหรับป้องกัน data loss

สถาปนิกสามารถ implement event channel ได้หลายวิธี event-driven architecture ส่วนใหญ่ใช้ Advanced Message Queuing Protocol (AMQP) สำหรับ trigger และตอบสนองต่อ event ตัวอย่างของ AMQP broker ได้แก่ Amazon SNS (Simple Notification Service) , RabbitMQ , Solace และ Azure Event Hubs ด้วย AMQP event จะถูก publish ไปยัง exchange exchange จะใช้ binding rule ที่ตั้งค่าโดย event processor ที่บริโภคเพื่อส่งต่อ event ไปยัง queue สำหรับ event processor แต่ละตัวที่ subscribe event นั้น AMQP broker ยังสามารถใช้สิ่งที่เรียกว่า pattern Event Forwarding เพื่อป้องกัน data loss ได้ด้วย ซึ่งเป็นเทคนิคที่เราจะอธิบายในหัวข้อนี้

อีกวิธีในการ implement event channel คือ Jakarta Messaging API (เดิมชื่อ Java Message Service หรือ JMS) ซึ่งใช้ topic แทนกระบวนการ forwarding แบบสองขั้นตอนที่ queue ใช้ อย่างไรก็ตาม Jakarta Messaging ก็ยังสามารถใช้ pattern Event Forwarding เพื่อป้องกัน data loss ได้ ตราบใดที่ event processor ที่ตอบสนองต่อ event ถูกกำหนดค่าให้เป็น durable subscriber durable subscriber คือตัวที่รับประกันว่าจะได้รับ event เสมอ ถ้า event processor ล่มหรือไม่พร้อมใช้งาน JMS topic จะเก็บ event นั้นไว้จนกว่า event processor ที่ subscribe จะพร้อมใช้งานอีกครั้ง

อีกวิธีหนึ่งในการ implement event channel คือ event streaming ด้วย Kafka เป็น event broker (ผลิตภัณฑ์ซอฟต์แวร์ที่มี queue และ topic) เทคนิคในการป้องกัน data loss ภายใน event streaming แตกต่างจากที่ใช้กับ pattern Event Forwarding ที่อธิบายในหัวข้อนี้มาก ดูข้อมูลเพิ่มเติมเกี่ยวกับการป้องกัน data loss เมื่อใช้ streaming event broker ประเภทนี้ได้ที่ เว็บไซต์ของ Kafka

ลองพิจารณาสถานการณ์ทั่วไปที่ event processor A publish event แบบอะซิงโครนัสไปยัง message broker ซึ่งในที่สุดจะไปที่ AMQP queue หรือ JMS topic event processor B ตอบสนองต่อ event และเพิ่มข้อมูลจาก payload ลงในฐานข้อมูล ดังภาพประกอบใน Figure 15-20 มีสามวิธีที่ data loss อาจเกิดขึ้นในสถานการณ์นี้:

  1. ในขณะที่ event processor A กำลัง publish event มันล่มก่อนที่จะได้รับ acknowledgment จาก event broker หรืออีกกรณีหนึ่ง event broker ส่ง acknowledgment ไปยัง event processor A แล้ว แต่ล่มก่อนที่ event จะถูกรับโดย event processor อีกตัว

  2. event processor B รับ event จาก queue แต่ล่มก่อนที่จะประมวลผล event นั้น

  3. event processor B ไม่สามารถบันทึก message ลงฐานข้อมูลได้เนื่องจากมี data error

พื้นที่เหล่านี้ที่เกิด data loss สามารถลดผลกระทบได้ผ่าน pattern Event Forwarding

Data loss issue

Figure 15-20. Places where data loss can happen within an event-driven architecture (จุดที่ Data Loss อาจเกิดขึ้นภายใน Event-Driven Architecture)

สำหรับปัญหาแรก event ไม่เคยไปถึง queue เลยหรือ broker ล่มก่อนที่ event จะถูกอ่าน วิธีแก้คือใช้ persistent message queue ร่วมกับ synchronous send persisted message queue รองรับ guaranteed delivery เมื่อ event broker ได้รับ event มันไม่เพียงเก็บ event ไว้ในหน่วยความจำเพื่อดึงข้อมูลได้เร็วเท่านั้น แต่ยังบันทึก event ลงใน physical data store บางอย่างด้วย (เช่น filesystem หรือฐานข้อมูล) ถ้า event broker ล่ม event จะยังคงถูกเก็บไว้บน disk จริง ดังนั้นมันจะยังพร้อมสำหรับการประมวลผลเมื่อ event broker กลับมาทำงานอีกครั้ง synchronous send จะทำ blocking wait ใน event processor หยุดไม่ให้มัน trigger event จนกว่า broker จะยืนยันว่าได้บันทึก event ลงฐานข้อมูลแล้ว เทคนิคพื้นฐานสองอย่างนี้ป้องกัน data loss ระหว่าง event producer กับ queue เพราะ event จะยังคงอยู่กับ event producer หรือถูกบันทึกอยู่ภายใน queue เสมอ

เทคนิค messaging พื้นฐานที่เรียกว่า client acknowledge mode สามารถแก้ปัญหาที่สอง ซึ่งเป็นกรณีที่ event processor B ดึง event ถัดไปจาก queue แล้วล่มก่อนที่จะประมวลผลมันได้ โดยค่าเริ่มต้น เมื่อ event ถูกอ่านจาก queue มันจะถูกลบออกจาก queue นั้นทันที (เรียกว่าโหมด auto acknowledge ) client acknowledge mode จะเก็บ event ไว้ใน queue และแนบ client ID เข้าไปด้วย เพื่อไม่ให้ consumer ตัวอื่นอ่านหรือประมวลผล event นั้นได้ ด้วยโหมดนี้ ถ้า event processor B ล่ม event ก็ยังคงถูกเก็บรักษาไว้ใน queue ป้องกันการสูญหายของ message

ปัญหาที่สาม ซึ่งเป็นกรณีที่ event processor B ไม่สามารถบันทึก event ลงฐานข้อมูลได้เนื่องจาก data error บางอย่าง สามารถแก้ได้ด้วย ACID transaction ผ่าน database commit เมื่อ event processor ทำ database commit แล้ว ข้อมูลจะถูกรับประกันว่าถูกบันทึกลงฐานข้อมูล Last participant support (LPS) จะลบ event ออกจาก persisted queue ด้วยการยืนยันว่าการประมวลผลทั้งหมดเสร็จสิ้นแล้วและ event ถูกบันทึกแล้ว สิ่งนี้รับประกันว่า event ไม่ได้สูญหายระหว่างทางจาก event processor A ไปยังฐานข้อมูล เทคนิคเหล่านี้แสดงไว้ใน Figure 15-21 .

Preventing data loss

Figure 15-21. Preventing data loss within an event-driven architecture (การป้องกัน Data Loss ภายใน Event-Driven Architecture)

Request-Reply Processing (การประมวลผลแบบ Request-Reply)

จนถึงตอนนี้ในบทนี้ เราได้พูดถึงคำขอแบบอะซิงโครนัสที่ไม่ต้องการการตอบกลับทันทีจาก event consumer แล้วถ้าเป็น event processor ที่ต้องการข้อมูลกลับมาทันทีจาก event processor ตัวอื่นล่ะ เช่น การรอหมายเลขยืนยันหรือการรับทราบบางอย่างก่อนที่จะ trigger event สถานการณ์นี้ต้องการการสื่อสารแบบซิงโครนัสเพื่อให้คำขอเสร็จสมบูรณ์

ใน EDA การสื่อสารแบบซิงโครนัสมักทำผ่าน messaging แบบ request-reply (บางครั้งเรียกว่า pseudosynchronous communications ) event channel แต่ละตัวใน request-reply messaging ประกอบด้วย queue สองตัว: queue request และ queue reply message producer ที่ส่งคำขอข้อมูลเริ่มต้นจะส่งข้อมูลไปยัง request queue แบบอะซิงโครนัส แล้วคืนการควบคุมกลับไปยัง message producer จากนั้น message producer จะทำงานอื่นเพิ่มเติม และในที่สุดก็จะรอรับที่ reply queue เพื่อรอการตอบกลับ message consumer จะรับและประมวลผล message แล้วส่งการตอบกลับไปยัง reply queue event producer จะรับ message ที่มีข้อมูลตอบกลับนั้น flow พื้นฐานนี้แสดงไว้ใน Figure 15-22

Request-reply

Figure 15-22. Request-reply message processing (การประมวลผล Message แบบ Request-Reply)

มีสองวิธีหลักในการ implement request-reply messaging เทคนิคแรก (และพบบ่อยที่สุด) คือการใส่ฟิลด์ correlation ID (CID) ไว้ใน message header ของ reply message โดยปกติจะตั้งเป็น message ID ของ request message ต้นฉบับ (เรียกสั้นๆ ว่า ID ใน Figure 15-23 ) มันทำงานแบบนี้:

  1. event producer ส่ง message ไปยัง request queue และบันทึก message ID ที่ไม่ซ้ำกัน (ID 124) สังเกตว่า CID ในกรณีนี้คือ null

  2. event producer จะทำ blocking wait ที่ reply queue พร้อม message filter (เรียกอีกอย่างว่า message selector ) โดยที่ CID ใน message header ต้องเท่ากับ message ID ต้นฉบับ (124) มี message สองตัวใน reply queue: ID 855 พร้อม CID 120 และ ID 856 พร้อม CID 122 ทั้งสอง message นี้จะไม่ถูกรับ เพราะ correlation ID ทั้งสองไม่ตรงกับสิ่งที่ event consumer กำลังหา (CID 124)

  3. event consumer รับ message (ID 124) และประมวลผลคำขอ

  4. event consumer สร้าง reply message ที่มีการตอบกลับ และตั้งค่า CID ใน message header ให้เป็น message ID ต้นฉบับ (124)

  5. event consumer ส่ง message ID ใหม่ (857) ไปยัง reply queue

  6. event producer รับ message เพราะ CID (124) ตรงกับ message selector จากขั้นตอนที่ 2

Correlation ID

Figure 15-23. Request-reply message processing using a correlation ID (การประมวลผล Message แบบ Request-Reply โดยใช้ Correlation ID)

อีกวิธีในการ implement request-reply messaging คือการใช้ temporary queue เป็น reply queue temporary queue คือ queue เฉพาะสำหรับคำขอหนึ่งๆ ที่ถูกสร้างขึ้นเมื่อมีคำขอ และถูกลบเมื่อคำขอสิ้นสุดลง เทคนิคนี้ ดังภาพประกอบใน Figure 15-24 ไม่จำเป็นต้องใช้ correlation ID เพราะ temporary queue เป็น queue เฉพาะที่รู้จักเฉพาะ event producer สำหรับคำขอนั้นๆ เท่านั้น เทคนิค temporary queue ทำงานดังนี้:

  1. event producer สร้าง temporary queue (หรือถูกสร้างขึ้นโดยอัตโนมัติ ขึ้นอยู่กับ message broker) และส่ง message ไปยัง request queue พร้อมส่งชื่อของ temporary queue ไปใน reply-to header (หรือ custom attribute อื่นที่ตกลงกันไว้ใน message header)

  2. event producer จะทำ blocking wait ที่ temporary reply queue ไม่จำเป็นต้องใช้ message selector เพราะ message ใดๆ ที่ส่งไปยัง queue นี้เป็นของ event producer ที่ส่ง message ต้นฉบับเพียงผู้เดียวเท่านั้น

  3. event consumer รับ message ประมวลผลคำขอ และส่ง response message ไปยัง reply queue ที่ระบุไว้ใน reply-to header

  4. event processor รับ message และลบ temporary queue

Temporary queues

Figure 15-24. Request-reply message processing using a temporary queue (การประมวลผล Message แบบ Request-Reply โดยใช้ Temporary Queue)

แม้เทคนิค temporary queue จะง่ายกว่ามาก แต่ message broker ต้องสร้าง temporary queue สำหรับทุกคำขอแล้วลบมันทิ้งทันที สิ่งนี้อาจทำให้ broker ทำงานช้าลงอย่างมาก และส่งผลกระทบต่อ performance และ responsiveness โดยรวม โดยเฉพาะเมื่อมีปริมาณ message มากและมี concurrency สูง ด้วยเหตุนี้ เราจึงมักแนะนำให้ใช้เทคนิค correlation ID .

Mediated Event-Driven Architecture (Event-Driven Architecture แบบ Mediated)

จนถึงตอนนี้ในบทนี้ เราเน้นไปที่ EDA แบบ choreographed ที่ event processor จะ trigger event ผ่านความสามารถด้าน broadcast และ event processor หลายตัวตอบสนองต่อ event นั้น อย่างไรก็ตาม บางครั้งสถาปนิกอาจต้องการควบคุมการประมวลผล event ให้มากขึ้น ในกรณีนี้ สถาปนิกสามารถใช้ EDA รูปแบบ orchestrated ที่เรียกว่า mediator topology

mediator topology แก้ปัญหาข้อบกพร่องบางอย่างของ standard choreographed EDA topology ที่เราได้อธิบายมาตลอดในบทนี้ มันมีศูนย์กลางอยู่ที่ event mediator ซึ่งจัดการและควบคุม workflow สำหรับ initiating event ที่ต้องการการประสานงานระหว่าง event processor หลายตัว ส่วนประกอบสถาปัตยกรรมที่ประกอบเป็น mediator topology ได้แก่: initiating event, event queue, event mediator, event channel และ event processor

ที่สำคัญคือ mediated topology มักใช้ message มากกว่า event (ดู “Events Versus Messages” ) โดยทั่วไปแล้วมันเป็นคำสั่ง (เช่น ship_order ) มากกว่า event ที่เกิดขึ้นแล้ว (เช่น order_shipped )

เช่นเดียวกับใน choreographed topology initiating event คือสิ่งที่เริ่มต้นกระบวนการทั้งหมด อย่างไรก็ตาม ใน mediator topology ( Figure 15-25 ) event mediator จะรับ initiating event มันรู้เพียงขั้นตอนที่เกี่ยวข้องในการประมวลผล event นั้น จึงสร้าง derived message ที่สอดคล้องกันและส่งไปยัง message channel เฉพาะ (มักเป็น queue) แบบ point-to-point event processor จะฟัง event channel เฉพาะของตัวเอง ประมวลผล message และ (มักจะ) ตอบสนองกลับไปยัง mediator เมื่อทำงานเสร็จ event processor ภายใน mediator topology จะไม่ประกาศสิ่งที่ตัวเองทำให้ระบบส่วนที่เหลือทราบผ่าน derived message เพิ่มเติม

ใน implementation ส่วนใหญ่ของ mediator topology จะมี mediator หลายตัว โดยแต่ละตัวมักจะสัมพันธ์กับ domain หรือกลุ่มของ event เฉพาะ วิธีนี้ช่วยหลีกเลี่ยงการมี single point of failure ซึ่งอาจเป็นปัญหาของ topology นี้ และช่วยเพิ่ม throughput และ performance โดยรวม ตัวอย่างเช่น customer mediator อาจจัดการ event ที่เกี่ยวข้องกับลูกค้าทั้งหมด (เช่น การลงทะเบียนลูกค้าใหม่และการอัปเดตโปรไฟล์) ในขณะที่ order mediator จัดการกิจกรรมที่เกี่ยวข้องกับคำสั่งซื้อ (เช่น การเพิ่มสินค้าลงตะกร้าและการชำระเงิน)

วิธีที่สถาปนิกเลือก implement event mediator มักขึ้นอยู่กับลักษณะและความซับซ้อนของ message ที่ event mediator กำลังประมวลผล ตัวอย่างเช่น สำหรับ event ที่ต้องการ error handling และ orchestration แบบง่ายๆ mediator อย่าง Apache Camel , Mule ESB หรือ Spring Integration ก็มักจะเพียงพอ message flow และ message route ภายใน mediator ประเภทนี้มักเขียนด้วย code แบบ custom (เช่น Java หรือ C#) เพื่อควบคุม event-processing workflow

อย่างไรก็ตาม ถ้า event workflow ต้องการ conditional processing จำนวนมากและมีเส้นทาง dynamic หลายเส้นทางพร้อม error handling directive ที่ซับซ้อน mediator อย่าง Apache ODE หรือ Oracle BPEL Process Manager จะเป็นตัวเลือกที่เหมาะสมกว่า mediator เหล่านี้อิงตาม Business Process Execution Language (BPEL) ซึ่งเป็นโครงสร้างแบบ XML ที่อธิบายขั้นตอนที่เกี่ยวข้องในการประมวลผล event artifact ของ BPEL ยังมี structured element สำหรับ error handling, การ redirect , multicasting และอื่นๆ BPEL เป็นภาษาที่ทรงพลังแต่ค่อนข้างซับซ้อนที่จะเรียนรู้ สถาปนิกจึงมักสร้าง mediator ด้วย GUI tool ใน BPEL engine suite แทน

Mediator topology

Figure 15-25. Mediator topology (Mediator Topology)

BPEL เหมาะกับ workflow ที่ซับซ้อนและ dynamic แต่ทำงานได้ไม่ดีกับ event workflow ที่มี long-running transaction ที่ต้องการการแทรกแซงของมนุษย์ตลอดกระบวนการ event ตัวอย่างเช่น สมมติว่ามีการวาง trade ผ่าน initiating event place_trade event mediator จะรับ event นี้ แต่ระหว่างการประมวลผล พบว่าต้องมีการอนุมัติด้วยตนเอง เพราะการเทรดนั้นเกินจำนวนหุ้นที่กำหนด event mediator จึงต้องหยุดการประมวลผล event แจ้งเทรดเดอร์อาวุโสให้ขออนุมัติด้วยตนเอง และรอการอนุมัตินั้น ในกรณีเช่นนี้ เครื่องยนต์ Business Process Management (BPM) อย่าง jBPM จะเหมาะสมกว่าการใช้ event mediator

ก่อนเลือกว่าจะ implement event mediator ประเภทไหน สิ่งสำคัญคือต้องรู้ประเภทของ event ที่มันจะประมวลผล สำหรับ event ที่ซับซ้อนและ long-running ที่เกี่ยวข้องกับการมีส่วนร่วมของมนุษย์ Apache Camel จะยากมากที่จะใช้และดูแลรักษา ในทางกลับกัน การใช้ BPM engine สำหรับ event flow แบบง่ายๆ จะเสียเวลาหลายเดือนไปกับสิ่งที่ Apache Camel ทำได้ภายในไม่กี่วัน

แน่นอนว่า event ทั้งหมดไม่ได้จัดอยู่ในระดับความซับซ้อนเดียวกันเสมอไป เราแนะนำให้จัดประเภท event เป็นแบบง่าย (simple) แบบยาก (hard) หรือแบบซับซ้อน (complex) แล้วส่ง event ทุกตัวผ่าน mediator แบบง่าย เช่น Apache Camel หรือ Mule mediator แบบง่ายสามารถจัดการ event นั้นเองได้ หรือส่งต่อไปยัง event mediator ที่ซับซ้อนกว่าตามการจัดประเภทความซับซ้อนนั้น โมเดล mediator delegation นี้ ดังภาพประกอบใน Figure 15-26 รับประกันว่า event ทุกประเภทจะถูกจัดการโดย mediator ประเภทที่ประมวลผลได้มีประสิทธิภาพที่สุด

Mediator delegation

Figure 15-26. Delegating the event to the appropriate type of event mediator (การ Delegate Event ไปยัง Event Mediator ประเภทที่เหมาะสม)

สังเกตใน Figure 15-26 ว่า Simple Event Mediator จะสร้างและส่ง derived message เมื่อ event workflow ง่ายพอที่จะจัดการได้ทั้งหมดโดย mediator แบบง่าย อย่างไรก็ตาม เมื่อ initiating event ถูกจัดประเภทว่ายากหรือซับซ้อน Simple Event Mediator จะส่ง initiating event ต้นฉบับต่อไปยัง mediator ที่สอดคล้องกัน (BPEL หรือ BPM) Simple Event Mediator ซึ่งได้ดักจับ event ต้นฉบับไว้ อาจยังคงรับผิดชอบในการรู้ว่า event นั้นเสร็จสิ้นเมื่อไหร่ หรืออาจแค่ delegate ทั้ง workflow (รวมถึงการแจ้งลูกค้า) ให้ mediator ตัวอื่นทำแทนก็ได้

เพื่อดูว่า mediator topology ทำงานอย่างไร ลองพิจารณาระบบรับคำสั่งซื้อสินค้าปลีกเดียวกันกับที่เราอธิบายในหัวข้อ choreographed topology แต่คราวนี้ใช้ mediator topology แทน mediator รู้ขั้นตอนที่จำเป็นในการประมวลผล event นี้ event flow ภายในของ mediator component แสดงไว้ใน Figure 15-27

Mediator flow

Figure 15-27. Mediator steps for placing an order (ขั้นตอนของ Mediator สำหรับการวางคำสั่งซื้อ)

ตามตัวอย่างก่อนหน้า initiating event เดียวกัน ( place order ) จะถูกส่งไปยัง event mediator ผ่าน queue เฉพาะเพื่อประมวลผล mediator Customer จะรับ initiating event นี้และเริ่มสร้าง derived message ตาม flow ใน Figure 15-27 event ที่แสดงในขั้นตอนที่ 2, 3 และ 4 ล้วนเกิดขึ้นทั้งพร้อมกันและตามลำดับระหว่างขั้นตอน กล่าวคือ ขั้นตอนที่ 3 (fulfill order) ต้องเสร็จสมบูรณ์และได้รับการยืนยันก่อนที่ลูกค้าจะได้รับแจ้งว่าคำสั่งซื้อพร้อมจัดส่งในขั้นตอนที่ 4 (ship order)

เมื่อรับ initiating event แล้ว mediator Customer จะสร้าง derived message create order และส่งไปยัง queue order placement (ดู Figure 15-28 ) event processor Order Placement จะรับ message ตรวจสอบและสร้างคำสั่งซื้อ แล้วส่ง acknowledgment พร้อม order ID กลับไปให้ mediator ณ จุดนี้ mediator อาจส่ง order ID นั้นไปยังลูกค้าเพื่อบอกว่าคำสั่งซื้อถูกวางแล้ว หรืออาจต้องดำเนินการต่อจนกว่าทุกขั้นตอนจะเสร็จสมบูรณ์ (ขึ้นอยู่กับกฎทางธุรกิจเฉพาะเกี่ยวกับการวางคำสั่งซื้อ)

Mediator example 1

Figure 15-28. Step 1 of the mediator example (ขั้นตอนที่ 1 ของตัวอย่าง Mediator)

เมื่อขั้นตอนที่ 1 เสร็จสมบูรณ์แล้ว mediator จะดำเนินการต่อไปยังขั้นตอนที่ 2 (ดู Figure 15-29 ) และสร้าง derived message สามตัวพร้อมกัน: email customer , apply payment และ adjust inventory โดยส่งทั้งสามตัวไปยัง queue ของแต่ละตัว event processor ทั้งสามจะรับ message เหล่านี้ ทำงานของตัวเอง แล้วแจ้ง mediator ว่าประมวลผลเสร็จแล้ว mediator ต้องรอจนกว่าจะได้รับ acknowledgment จากกระบวนการทั้งสามที่ทำงานแบบขนานก่อนจึงจะดำเนินการต่อไปยังขั้นตอนที่ 3 ถ้ามี error เกิดขึ้นใน event processor ตัวใดตัวหนึ่งที่ทำงานแบบขนาน mediator สามารถดำเนินการแก้ไขได้ (จะกล่าวถึงเพิ่มเติมในภายหลังในหัวข้อนี้)

Mediator example 2

Figure 15-29. Step 2 of the mediator example (ขั้นตอนที่ 2 ของตัวอย่าง Mediator)

เมื่อ mediator ได้รับ acknowledgment ที่สำเร็จจาก event processor ทั้งหมดในขั้นตอนที่ 2 แล้ว มันจะดำเนินการต่อไปยังขั้นตอนที่ 3 เพื่อ fulfill คำสั่งซื้อ (ดู Figure 15-30 ) อีกครั้งที่ทั้งสอง message ( fulfill order และ order stock ) สามารถเกิดขึ้นพร้อมกันได้ event processor Order Fulfillment และ Warehouse จะรับ message ทำงานของตัวเอง และส่ง acknowledgment กลับไปยัง mediator

Mediator example 3

Figure 15-30. Step 3 of the mediator example (ขั้นตอนที่ 3 ของตัวอย่าง Mediator)

mediator ดำเนินการต่อไปยังขั้นตอนที่ 4 (ดู Figure 15-31 ) เพื่อจัดส่งคำสั่งซื้อ ขั้นตอนนี้จะสร้าง derived message สองตัว: message ship order และ message email customer อีกตัวที่มีข้อมูลเฉพาะเกี่ยวกับสิ่งที่ต้องทำ (แจ้งลูกค้าว่าคำสั่งซื้อพร้อมจัดส่งแล้ว)

Mediator example 4

Figure 15-31. Step 4 of the mediator example (ขั้นตอนที่ 4 ของตัวอย่าง Mediator)

สุดท้าย mediator จะดำเนินการต่อไปยังขั้นตอนที่ 5 (ดู Figure 15-32 ) และสร้าง message email customer ตามบริบทอีกตัวหนึ่งเพื่อแจ้งลูกค้าว่าคำสั่งซื้อถูกจัดส่งแล้ว นี่คือจุดจบของ workflow mediator จะทำเครื่องหมายว่า initiating event flow นี้เสร็จสมบูรณ์แล้ว และลบ state ทั้งหมดที่เกี่ยวข้องกับ initiating event นั้น

Mediator example 5

Figure 15-32. Step 5 of the mediator example (ขั้นตอนที่ 5 ของตัวอย่าง Mediator)

ใน topology นี้ ต่างจาก choreographed topology mediator component จะมีความรู้และการควบคุมเหนือ workflow มันสามารถรักษา event state และจัดการ error handling, recoverability และความสามารถในการ restart ได้ ตัวอย่างเช่น สมมติว่าในตัวอย่างของเรา การชำระเงินไม่สำเร็จเพราะบัตรเครดิตหมดอายุ เมื่อ mediator ได้รับเงื่อนไข error นี้ มันจะรู้ว่าคำสั่งซื้อไม่สามารถ fulfill ได้ (ขั้นตอนที่ 3) จนกว่าการชำระเงินจะสำเร็จ มันจึงหยุด workflow และบันทึก state ของคำขอไว้ใน datastore แบบ persistent ของตัวเอง เมื่อการชำระเงินสำเร็จในที่สุด workflow ก็สามารถเริ่มใหม่ได้จากจุดที่ค้างไว้ (ในกรณีนี้คือจุดเริ่มต้นของขั้นตอนที่ 3)

แม้ mediator topology จะแก้ปัญหาที่เกี่ยวข้องกับ choreographed topology ได้ แต่ก็มีข้อเสียของตัวเองเช่นกัน อย่างแรก มันยากมากที่จะสร้างแบบจำลอง (model) การประมวลผลแบบ dynamic ที่เกิดขึ้นใน event flow ที่ซับซ้อนด้วยวิธี declarative ผลก็คือ workflow หลายตัวภายใน mediator topology จะจัดการเฉพาะการประมวลผลทั่วไปเท่านั้น แต่ใช้ hybrid model ที่รวม mediator และ choreographed topology เข้าด้วยกัน เพื่อจัดการกับลักษณะ dynamic ของการประมวลผล event ที่ซับซ้อน เช่น เงื่อนไขสินค้าหมดสต็อกหรือ error ที่ไม่ปกติอื่นๆ นอกจากนี้ แม้ event processor จะ scale ได้ง่ายในลักษณะเดียวกับ choreographed topology แต่ mediator ก็ต้อง scale ด้วยเช่นกัน ซึ่งบางครั้งก็ทำให้เกิด bottleneck ใน event-processing flow โดยรวม event processor ใน mediator topology ก็ไม่ decoupled สูงเท่ากับใน choreographed topology สุดท้าย performance ก็ไม่ดีเท่าใน topology นี้ เพราะ mediator เป็นผู้ควบคุมการประมวลผล event

trade-off ระหว่าง choreographed topology กับ mediator topology โดยพื้นฐานแล้วคือการชั่งน้ำหนักระหว่างการควบคุม workflow และความสามารถด้าน error handling กับ performance และ scalability ที่สูง แม้ performance และ scalability จะยังดีอยู่ใน mediator topology แต่ก็ไม่สูงเท่ากับ choreographed topology .