Sagas (Saga)
ต่างจาก two-phase commit saga คือ algorithm ที่ถูกออกแบบมาเพื่อประสานการเปลี่ยนแปลง state หลายตัว แต่หลีกเลี่ยงความจำเป็นในการ lock resource เป็นเวลานาน saga ทำสิ่งนี้ได้โดยการ model ขั้นตอนต่าง ๆ ที่เกี่ยวข้องให้เป็น activity แยกกันที่สามารถ execute ได้อย่างเป็นอิสระ การใช้ saga มาพร้อมข้อดีเพิ่มเติมคือบังคับให้เรา model business process ของเราอย่างชัดเจน ซึ่งสามารถให้ประโยชน์ที่สำคัญได้
แนวคิด หลัก ซึ่งถูกสรุปครั้งแรกใน "Sagas" โดย Hector Garcia-Molina และ Kenneth Salem 4 พูดถึงวิธีการที่ดีที่สุดในการจัดการ operation ที่เรียกว่า long lived transaction (LLT) transaction เหล่านี้อาจใช้เวลานาน (เป็นนาที ชั่วโมง หรืออาจถึงเป็นวัน) และในฐานะส่วนหนึ่งของกระบวนการนั้นต้องการการเปลี่ยนแปลงกับ database
ถ้าคุณ map LLT ไปยัง database transaction ปกติโดยตรง database transaction เดียวจะครอบคลุมทั้ง life cycle ของ LLT นี่อาจทำให้หลาย row หรือแม้แต่ทั้ง table ถูก lock เป็นเวลานานในขณะที่ LLT กำลังเกิดขึ้น ก่อให้เกิดปัญหาที่มีนัยสำคัญถ้ามีโปรเซสอื่นพยายามอ่านหรือแก้ไข resource ที่ถูก lock เหล่านี้
แทนที่จะเป็นแบบนั้น ผู้เขียน paper แนะนำว่าเราควรแยก LLT เหล่านี้ออกเป็นลำดับของ transaction ที่แต่ละตัวจัดการได้อย่างเป็นอิสระ แนวคิดคือระยะเวลาของ "sub" transaction แต่ละตัวจะสั้นลง และจะแก้ไขแค่ส่วนหนึ่งของข้อมูลที่ได้รับผลกระทบจาก LLT ทั้งหมดเท่านั้น ผลก็คือจะมี contention ใน underlying database น้อยลงมาก เพราะขอบเขตและระยะเวลาของ lock ถูกลดลงอย่างมาก
ใน ตอนแรก saga ถูกคิดค้นขึ้นมาเป็นกลไกเพื่อช่วยเรื่อง LLT ที่ทำงานกับ database เดียว แต่ model นี้ก็ใช้ได้ดีพอ ๆ กันสำหรับการประสานการเปลี่ยนแปลงข้าม service หลายตัว เราสามารถแยก business process เดียวออกเป็นชุดของ call ที่จะถูกทำไปยัง collaborating service ต่าง ๆ ได้ นี่คือสิ่งที่ประกอบขึ้นเป็น saga
Note
ก่อน ที่เราจะไปต่อ คุณต้องเข้าใจว่า saga ไม่ ให้ atomicity ในความหมายของ ACID แบบที่เราคุ้นเคยกับ database transaction ปกติ เมื่อเราแยก LLT ออกเป็น transaction แยกกัน เราจะไม่มี atomicity ในระดับของ saga เอง เรามี atomicity สำหรับแต่ละ transaction ภายใน saga ทั้งหมด เพราะแต่ละตัวสามารถสัมพันธ์กับการเปลี่ยนแปลงแบบ ACID transaction ได้ถ้าจำเป็น สิ่งที่ saga ให้เราคือข้อมูลเพียงพอที่จะคิดได้ว่ามันอยู่ใน state ไหน ส่วนเรื่องผลกระทบจากสิ่งนี้เป็นหน้าที่ของเราที่จะจัดการเอง
มาดูตัวอย่าง order fulfillment flow ง่าย ๆ สำหรับ MusicCorp กัน ซึ่งสรุปไว้ใน Figure 6-5 ซึ่งเราสามารถใช้เพื่อสำรวจ saga เพิ่มเติมในบริบทของ microservice architecture
ที่นี่ กระบวนการ order fulfillment ถูกแทนด้วย saga เดียว โดยแต่ละขั้นตอนใน flow นี้แทน operation ที่สามารถทำได้โดย service ที่ต่างกัน ภายในแต่ละ service การเปลี่ยนแปลง state ใด ๆ สามารถจัดการได้ภายใน local ACID transaction ยกตัวอย่างเช่น เมื่อเราตรวจสอบและจอง stock โดยใช้ Warehouse service ภายใน Warehouse service อาจสร้าง row ใน table Reservation ของมันเองเพื่อบันทึกการจอง การเปลี่ยนแปลงนี้จะถูกจัดการภายใน database transaction ปกติ
Figure 6-5. ตัวอย่าง order fulfillment flow พร้อมกับ service ที่รับผิดชอบในการทำ operation
Saga Failure Modes (Failure Mode ของ Saga)
ด้วย saga ที่ถูกแยกออกเป็น transaction แยกกัน เราต้องพิจารณาว่าจะจัดการ failure อย่างไร หรือเจาะจงกว่านั้นคือจะกู้คืนอย่างไรเมื่อเกิด failure paper saga ต้นฉบับอธิบาย recovery สองประเภทคือ backward recovery และ forward recovery
Backward recovery เกี่ยวข้องกับการย้อนกลับ failure และทำความสะอาดในภายหลัง นั่นคือ rollback เพื่อให้สิ่งนี้ทำงานได้ เราต้องนิยาม compensating action ที่ช่วยให้เราย้อนกลับ transaction ที่ commit ไปแล้วได้ Forward recovery ช่วยให้เราเริ่มต่อจากจุดที่เกิด failure และประมวลผลต่อไป เพื่อให้สิ่งนี้ทำงานได้ เราต้องสามารถ retry transaction ได้ ซึ่งหมายความว่าระบบของเราต้องเก็บข้อมูลเพียงพอที่จะให้ retry นี้เกิดขึ้นได้
ขึ้นอยู่กับธรรมชาติของ business process ที่ถูก model คุณอาจคาดหวังว่า failure mode ใด ๆ จะ trigger backward recovery, forward recovery หรืออาจผสมทั้งสองแบบ
เป็น เรื่องสำคัญมากที่ต้องสังเกตว่า saga ช่วยให้เรากู้คืนจาก business failure ไม่ใช่ technical failure ยกตัวอย่างเช่น ถ้าเราพยายามรับ payment จากลูกค้าแต่ลูกค้ามีเงินไม่พอ นี่คือ business failure ที่ saga ควรถูกคาดหวังให้จัดการได้ ในทางกลับกัน ถ้า Payment Gateway timeout หรือ throw 500 Internal Service Error นี่คือ technical failure ที่เราต้องจัดการแยกต่างหาก saga สมมติว่า component เบื้องหลังทำงานได้ถูกต้อง คือระบบเบื้องหลัง reliable และเรากำลังประสานงานของ component ที่ reliable เหล่านั้น เราจะสำรวจวิธีทำให้ technical component ของเรา reliable มากขึ้นใน Chapter 12 แต่สำหรับข้อจำกัดของ saga เรื่องนี้เพิ่มเติม ผมแนะนำ "The Limits of the Saga Pattern" โดย Uwe Friedrichsen
Saga rollbacks (การ Rollback ของ Saga)
ด้วย ACID transaction ถ้าเราเจอปัญหา เรา trigger rollback ก่อนที่ commit จะเกิดขึ้น หลังจาก rollback แล้วมันเหมือนไม่มีอะไรเกิดขึ้นเลย การเปลี่ยนแปลงที่เราพยายามทำไม่เกิดขึ้น แต่กับ saga ของเรา เรามี transaction หลายตัวที่เกี่ยวข้อง และบางตัวอาจ commit ไปแล้วก่อนที่เราจะตัดสินใจ rollback operation ทั้งหมด แล้วเราจะ rollback transaction ที่ commit ไปแล้วได้อย่างไร?
มากลับไปที่ตัวอย่างการประมวลผล order กัน ตามที่สรุปไว้ใน Figure 6-5 ลองพิจารณา failure mode ที่อาจเกิดขึ้น เรามาถึงขั้นพยายามแพ็กสินค้า แล้วพบว่าหาสินค้าไม่เจอใน warehouse ดังที่แสดงใน Figure 6-6 ระบบของเราคิดว่าสินค้ามีอยู่ แต่มันแค่ไม่อยู่บนชั้น!
ทีนี้ สมมติว่าเราตัดสินใจว่าอยากจะ rollback order ทั้งหมด แทนที่จะให้ลูกค้าเลือกให้สินค้าเป็น back order ปัญหาคือเราได้รับ payment และให้ loyalty point สำหรับ order นี้ไปแล้ว
ถ้าขั้นตอนเหล่านี้ทั้งหมดถูกทำใน database transaction เดียว การ rollback ง่าย ๆ ก็จะทำความสะอาดได้ทั้งหมด แต่แต่ละขั้นตอนใน order fulfillment process ถูกจัดการโดย service call ที่ต่างกัน แต่ละตัวทำงานใน transactional scope ที่ต่างกัน ไม่มี "rollback" ง่าย ๆ สำหรับ operation ทั้งหมด
Figure 6-6. เราพยายามแพ็กสินค้าแล้ว แต่หาไม่เจอใน warehouse
แทนที่จะเป็นแบบนั้น ถ้าคุณต้องการ implement rollback คุณต้อง implement compensating transaction compensating transaction คือ operation ที่ย้อนกลับ transaction ที่ commit ไปแล้ว เพื่อ rollback order fulfillment process ของเรา เราจะ trigger compensating transaction สำหรับแต่ละขั้นตอนใน saga ของเราที่ commit ไปแล้ว ดังที่แสดงใน Figure 6-7
ควรกล่าวถึงว่า compensating transaction เหล่านี้อาจไม่ได้ทำงานเหมือน database rollback ปกติเป๊ะ ๆ database rollback เกิดขึ้นก่อน commit และหลังจาก rollback แล้ว มันเหมือนกับว่า transaction ไม่เคยเกิดขึ้นเลย ในสถานการณ์นี้ แน่นอนว่า transaction เหล่านี้ เกิดขึ้นจริง เรากำลังสร้าง transaction ใหม่ที่ย้อนกลับการเปลี่ยนแปลงที่ทำโดย transaction เดิม แต่เราไม่สามารถย้อนเวลาและทำให้เสมือนว่า transaction เดิมไม่เคยเกิดขึ้นได้
Figure 6-7. การ trigger rollback ของ saga ทั้งหมด
เพราะ เราไม่สามารถย้อนกลับ transaction ได้อย่างสะอาดเสมอไป เราจึงเรียก compensating transaction เหล่านี้ว่า semantic rollback เราไม่สามารถทำความสะอาดทุกอย่างได้เสมอไป แต่เราก็ทำเพียงพอสำหรับบริบทของ saga ของเรา ยกตัวอย่างเช่น หนึ่งในขั้นตอนของเราอาจเกี่ยวข้องกับการส่งอีเมลไปหาลูกค้าเพื่อบอกว่า order ของพวกเขากำลังจัดส่ง ถ้าเราตัดสินใจ rollback สิ่งนั้น เราไม่สามารถเรียกอีเมลกลับคืนได้! 5 แทนที่จะเป็นแบบนั้น compensating transaction ของเราอาจทำให้มีการส่งอีเมลฉบับที่สองไปหาลูกค้า เพื่อแจ้งว่ามีปัญหากับ order และมันถูกยกเลิกแล้ว
เป็นเรื่องที่เหมาะสมอย่างยิ่งที่ข้อมูลที่เกี่ยวข้องกับ rollback จะถูกเก็บไว้ในระบบ จริง ๆ แล้วนี่อาจเป็นข้อมูลที่สำคัญมาก คุณอาจต้องการเก็บ record ไว้ใน Order service สำหรับ order ที่ถูก abort นี้ พร้อมข้อมูลว่าเกิดอะไรขึ้น ด้วยเหตุผลหลายอย่าง
Reordering workflow steps to reduce rollbacks (การจัดลำดับขั้นตอน Workflow ใหม่เพื่อลด Rollback)
ใน Figure 6-7 เรา สามารถทำให้ rollback scenario ที่น่าจะเกิดขึ้นง่ายขึ้นได้ โดยการจัดลำดับขั้นตอนใน workflow เดิมของเราใหม่ การเปลี่ยนแปลงง่าย ๆ คือให้ point ก็ต่อเมื่อ order ถูก dispatch จริง ๆ แล้วเท่านั้น ดังที่เห็นใน Figure 6-8
Figure 6-8. การย้ายขั้นตอนให้ไปอยู่หลังใน saga สามารถลดสิ่งที่ต้อง rollback ในกรณีที่เกิด failure ได้
ด้วยวิธีนี้ เราจะหลีกเลี่ยงความกังวลเรื่อง stage นั้นถูก rollback ถ้าเราเจอปัญหาระหว่างพยายามแพ็กและส่ง order บางครั้งคุณสามารถทำให้ rollback operation ของคุณง่ายขึ้นได้แค่ปรับวิธีที่ workflow ของคุณถูกทำ โดยการดึงขั้นตอนที่มีโอกาสล้มเหลวมากที่สุดมาไว้ข้างหน้าและทำให้กระบวนการล้มเหลวเร็วขึ้น คุณหลีกเลี่ยงการต้อง trigger compensating transaction ในภายหลัง เพราะขั้นตอนเหล่านั้นไม่เคยถูก trigger ตั้งแต่แรก
การเปลี่ยนแปลงเหล่านี้ ถ้าสามารถทำได้ จะทำให้ชีวิตคุณง่ายขึ้นมาก หลีกเลี่ยงความจำเป็นในการสร้าง compensating transaction สำหรับบางขั้นตอนด้วยซ้ำ นี่สำคัญเป็นพิเศษถ้าการ implement compensating transaction ยาก คุณอาจสามารถย้ายขั้นตอนให้ไปอยู่หลังในกระบวนการ ไปยัง stage ที่ไม่ต้อง rollback เลย
Mixing fail-backward and fail-forward situations (การผสม Fail-Backward และ Fail-Forward)
เป็นเรื่องที่เหมาะสมอย่างยิ่งที่จะมี failure recovery mode ผสมกัน failure บางแบบอาจต้อง rollback (fail backward) ในขณะที่บางแบบอาจ fail forward สำหรับการประมวลผล order ยกตัวอย่างเช่น เมื่อเรารับเงินจากลูกค้าและสินค้าถูกแพ็กแล้ว ขั้นตอนเดียวที่เหลือคือ dispatch พัสดุ ถ้าไม่ว่าด้วยเหตุผลใดก็ตามเรา dispatch พัสดุไม่ได้ (อาจเพราะบริษัทขนส่งที่เราใช้ไม่มีที่ว่างในรถของวันนี้) การ rollback order ทั้งหมดดูจะแปลกมาก แทนที่จะเป็นแบบนั้น เราน่าจะแค่ retry การ dispatch (อาจ queue ไว้สำหรับวันถัดไป) และถ้ามันล้มเหลวอีก เราจะต้องการให้มนุษย์เข้ามาแทรกแซงเพื่อแก้ไขสถานการณ์
Implementing Sagas (การ Implement Saga)
ตอนนี้ เราได้ดู logical model ว่า saga ทำงานอย่างไรแล้ว แต่เราต้องเจาะลึกเพิ่มอีกหน่อยเพื่อสำรวจวิธี implement saga เอง เราสามารถดู saga implementation ได้สอง style Orchestrated saga ใกล้เคียงกับ solution space เดิมมากกว่า และพึ่งพา centralized coordination และการติดตามเป็นหลัก สิ่งนี้สามารถเปรียบเทียบได้กับ choreographed saga ซึ่งหลีกเลี่ยงความจำเป็นในการมี centralized coordination โดยเลือกใช้ model ที่ loosely coupled กว่า แต่อาจทำให้การติดตามความคืบหน้าของ saga ซับซ้อนขึ้น
Orchestrated sagas (Orchestrated Saga)
Orchestrated saga ใช้ central coordinator (ซึ่งต่อไปนี้เราจะเรียกว่า orchestrator ) เพื่อกำหนดลำดับการทำงานและ trigger compensating action ที่จำเป็นใด ๆ คุณสามารถคิดว่า orchestrated saga เป็นแนวทางแบบ command-and-control ได้ orchestrator ควบคุมว่าอะไรเกิดขึ้นและเมื่อไหร่ และด้วยเหตุนี้มันจึงให้ visibility ที่ดีในสิ่งที่กำลังเกิดขึ้นกับ saga ใด ๆ
ลองดู order fulfillment process ใน Figure 6-5 มาดูกันว่า central coordination process นี้จะทำงานเป็นชุดของ collaborating service ได้อย่างไร ดังที่แสดงใน Figure 6-9
Figure 6-9. ตัวอย่างวิธีที่ orchestrated saga อาจถูกใช้เพื่อ implement order fulfillment process ของเรา
ที่นี่ Order Processor ตรงกลางของเรา ซึ่งเล่นบทบาทของ orchestrator ประสาน fulfillment process ของเรา มันรู้ว่า service ไหนจำเป็นสำหรับทำ operation นี้ และมันตัดสินใจว่าเมื่อไหร่จะเรียก service เหล่านั้น ถ้า call ล้มเหลว มันสามารถตัดสินใจได้ว่าจะทำอะไรต่อ โดยทั่วไปแล้ว orchestrated saga มักใช้ request-response interaction ระหว่าง service เยอะมาก Order Processor ส่ง request ไปยัง service (เช่น Payment Gateway ) และคาดหวัง response เพื่อให้รู้ว่า request สำเร็จหรือไม่และให้ผลลัพธ์ของ request
การมี business process ของเรา model ไว้อย่างชัดเจนภายใน Order Processor มีประโยชน์อย่างมาก มันช่วยให้เราดูที่จุดเดียวในระบบและเข้าใจว่ากระบวนการนี้ควรทำงานอย่างไร นี่ช่วยให้การ onboard คนใหม่ง่ายขึ้นและช่วยให้เข้าใจส่วนสำคัญของระบบได้ดีขึ้น
แต่ก็มีข้อเสียบางอย่างที่ต้องพิจารณา อย่างแรก โดยธรรมชาติแล้ว นี่เป็นแนวทางที่ coupled ในระดับหนึ่ง Order Processor ของเราต้องรู้จัก service ที่เกี่ยวข้องทั้งหมด ส่งผลให้เกิด domain coupling ในระดับที่สูงขึ้น แม้ domain coupling จะไม่ได้แย่โดยธรรมชาติ แต่เราก็ยังอยากเก็บมันไว้ให้น้อยที่สุดถ้าทำได้ ที่นี่ Order Processor ของเราต้องรู้จักและควบคุมสิ่งต่าง ๆ มากมาย จน coupling รูปแบบนี้หลีกเลี่ยงได้ยาก
ปัญหาอีกอย่างหนึ่ง ซึ่งละเอียดอ่อนกว่า คือ logic ที่ควรจะถูกผลักเข้าไปใน service กลับเริ่มถูกดูดซับเข้าไปใน orchestrator แทน ถ้าสิ่งนี้เริ่มเกิดขึ้น คุณอาจพบว่า service ของคุณกลายเป็น anemic มีพฤติกรรมของตัวเองน้อยมาก แค่รับคำสั่งจาก orchestrator อย่าง Order Processor สิ่งสำคัญคือคุณยังต้องพิจารณา service ที่ประกอบขึ้นเป็น orchestrated flow เหล่านี้ให้เป็น entity ที่มี local state และพฤติกรรมของตัวเอง พวกมันควบคุม local state machine ของตัวเอง
Warning
ถ้า logic มีที่ที่มันสามารถถูกรวมศูนย์ได้ มันก็จะถูก รวมศูนย์!
วิธีหนึ่งที่จะหลีกเลี่ยงการรวมศูนย์มากเกินไปกับ orchestrated flow คือให้แน่ใจว่าคุณมี service ต่างกันมาเล่นบทบาท orchestrator สำหรับ flow ต่างกัน คุณอาจมี Order Processor microservice ที่จัดการการวาง order, Returns microservice ที่จัดการกระบวนการคืนและคืนเงิน, Goods Receiving microservice ที่จัดการ stock ใหม่ที่มาถึงและถูกวางบนชั้น และอื่น ๆ อะไรอย่าง Warehouse microservice ของเราอาจถูกใช้โดย orchestrator เหล่านั้นทั้งหมด model แบบนี้ทำให้คุณเก็บ functionality ไว้ใน Warehouse microservice เองได้ง่ายขึ้น ทำให้คุณใช้ functionality ซ้ำได้ข้าม flow ทั้งหมดเหล่านั้น
BPM Tools (เครื่องมือ BPM)
เครื่องมือ business process modeling (BPM) มีมานานหลายปีแล้ว โดยทั่วไปแล้วมันถูกออกแบบมาให้ nondeveloper สามารถนิยาม business process flow ได้ มักใช้เครื่องมือ visual แบบ drag-and-drop แนวคิดคือ developer จะสร้าง building block ของกระบวนการเหล่านี้ แล้ว nondeveloper จะเอา building block เหล่านี้มาต่อกันเป็น process flow ที่ใหญ่ขึ้น การใช้เครื่องมือแบบนี้ดูเข้ากันได้ดีในฐานะวิธี implement orchestrated saga และจริง ๆ แล้ว process orchestration ก็เกือบจะเป็น use case หลักของเครื่องมือ BPM (หรือในทางกลับกัน การใช้เครื่องมือ BPM ทำให้คุณต้อง adopt orchestration)
จากประสบการณ์ของผม ผมมาไม่ชอบเครื่องมือ BPM อย่างมาก เหตุผลหลักคือแนวคิดหลักที่ว่า nondeveloper จะนิยาม business process นั้น จากประสบการณ์ของผมแทบไม่เคยเป็นจริงเลย เครื่องมือที่มุ่งเป้าไปที่ nondeveloper สุดท้ายก็ถูกใช้โดย developer และน่าเสียดายที่เครื่องมือเหล่านี้มักทำงานในแบบที่แปลกแยกจากวิธีที่ developer ชอบทำงาน มันมักต้องใช้ GUI เพื่อเปลี่ยน flow, flow ที่มันสร้างอาจยาก (หรือเป็นไปไม่ได้) ที่จะ version control, flow เองอาจไม่ได้ถูกออกแบบมาโดยคำนึงถึงการทดสอบ และอื่น ๆ อีกมาก
ถ้า developer ของคุณจะ implement business process ของคุณ ให้พวกเขาใช้ tooling ที่พวกเขารู้จักและเข้าใจ และเหมาะกับ workflow ของพวกเขา โดยทั่วไปแล้วนี่หมายถึงแค่ปล่อยให้พวกเขาใช้โค้ด implement สิ่งเหล่านี้! ถ้าคุณต้องการ visibility ว่า business process ถูก implement อย่างไรหรือมันทำงานอย่างไร มันง่ายกว่ามากที่จะฉาย visual representation ของ workflow จากโค้ด มากกว่าใช้ visual representation ของ workflow เพื่ออธิบายว่าโค้ดของคุณควรทำงานอย่างไร
มีความพยายามสร้างเครื่องมือ BPM ที่เป็นมิตรกับ developer มากขึ้น feedback เกี่ยวกับเครื่องมือเหล่านี้จาก developer ดูจะผสมกัน แต่เครื่องมือเหล่านี้ทำงานได้ดีสำหรับบางคน และเป็นเรื่องดีที่เห็นคนพยายามปรับปรุง framework เหล่านี้ ถ้าคุณรู้สึกว่าต้องสำรวจเครื่องมือเหล่านี้เพิ่มเติม ลองดู Camunda และ Zeebe ทั้งคู่เป็น open source orchestration framework ที่มุ่งเป้าไปที่ microservice developer และจะเป็นตัวเลือกอันดับต้น ๆ ของผม ถ้าผมตัดสินใจว่าเครื่องมือ BPM เหมาะกับผม
Choreographed sagas (Choreographed Saga)
Choreographed saga มุ่งกระจายความรับผิดชอบสำหรับการทำงานของ saga ไปยัง collaborating service หลายตัว ถ้า orchestration เป็นแนวทางแบบ command-and-control choreographed saga แทน architecture แบบ trust-but-verify อย่างที่เราจะเห็นในตัวอย่างของเราใน Figure 6-10 choreographed saga มักใช้ event เยอะมากในการทำงานร่วมกันระหว่าง service
Figure 6-10. ตัวอย่างของ choreographed saga สำหรับ implement order fulfillment
มีอะไรเกิดขึ้นเยอะพอสมควรตรงนี้ จึงควรสำรวจในรายละเอียดมากขึ้น อย่างแรก microservice เหล่านี้ตอบสนองต่อ event ที่ได้รับ ในเชิงแนวคิด event ถูก broadcast ในระบบ และฝ่ายที่สนใจสามารถรับมันได้ จำไว้ว่าอย่างที่เราพูดถึงใน Chapter 4 คุณไม่ได้ส่ง event ไปที่ microservice ตัวใดตัวหนึ่ง คุณแค่ยิงมันออกไป และ microservice ที่สนใจ event เหล่านี้สามารถรับและตอบสนองตามนั้นได้ ในตัวอย่างของเรา เมื่อ Warehouse service รับ event Order Placed ตัวแรก มันรู้ว่างานของมันคือจอง stock ที่เหมาะสมและยิง event เมื่อทำเสร็จ ถ้า stock ไม่สามารถรับได้ Warehouse ต้อง raise event ที่เหมาะสม (อาจเป็น event Insufficient Stock ) ซึ่งอาจนำไปสู่การ abort order
เรายังเห็นในตัวอย่างนี้ว่า event สามารถช่วยให้เกิดการประมวลผลแบบ parallel ได้อย่างไร เมื่อ event Payment Taken ถูกยิงโดย Payment Gateway มันทำให้เกิดปฏิกิริยาทั้งใน Loyalty และ Warehouse microservice Warehouse ตอบสนองด้วยการ dispatch พัสดุ ในขณะที่ Loyalty microservice ตอบสนองด้วยการให้ point
โดยทั่วไปแล้ว คุณจะใช้ message broker บางแบบเพื่อจัดการการ broadcast และส่ง event อย่าง reliable เป็นไปได้ว่า microservice หลายตัวอาจตอบสนองต่อ event เดียวกัน และนั่นคือจุดที่คุณจะใช้ topic ฝ่ายที่สนใจ event ประเภทหนึ่งจะ subscribe ไปยัง topic เฉพาะโดยไม่ต้องกังวลว่า event เหล่านี้มาจากไหน และ broker รับประกัน durability ของ topic และ event บนนั้นจะถูกส่งไปยัง subscriber สำเร็จ ยกตัวอย่างเช่น เราอาจมี Recommendation service ที่ฟัง event Order Placed ด้วยเช่นกัน และใช้มันสร้าง database ของตัวเลือกเพลงที่คุณอาจชอบ
ใน architecture ก่อนหน้านี้ ไม่มี service ไหนรู้จัก microservice อื่นเลย พวกมันแค่ต้องรู้ว่าต้องทำอะไรเมื่อได้รับ event หนึ่ง ๆ เราลด domain coupling ลงไปอย่างมาก โดยธรรมชาติแล้วสิ่งนี้ทำให้ได้ architecture ที่ coupled น้อยลงมาก เนื่องจาก implementation ของ process ถูกแยกและกระจายไปยัง microservice ทั้งสามตัวที่นี่ เรายังหลีกเลี่ยงความกังวลเรื่องการรวมศูนย์ของ logic ด้วย (ถ้าคุณไม่มีที่ที่ logic จะถูกรวมศูนย์ได้ มันก็จะไม่ถูกรวมศูนย์!)
ข้อเสียของสิ่งนี้คือมันอาจยากกว่าที่จะเข้าใจว่าเกิดอะไรขึ้น ด้วย orchestration process ของเราถูก model ไว้อย่างชัดเจนใน orchestrator ของเรา ทีนี้ ด้วย architecture แบบนี้ที่นำเสนอ คุณจะสร้าง mental model ของกระบวนการที่ควรจะเป็นได้อย่างไร? คุณต้องดูพฤติกรรมของแต่ละ service แยกกันและประกอบภาพนี้ขึ้นมาในหัวของคุณเอง ซึ่งไม่ตรงไปตรงมาเลย แม้แต่กับ business process ง่าย ๆ แบบนี้
การขาด representation ที่ชัดเจนของ business process ของเราก็แย่พออยู่แล้ว แต่เรายังขาดวิธีรู้ว่า saga อยู่ใน state ไหน ซึ่งอาจทำให้เราไม่มีโอกาสผูก compensating action เมื่อจำเป็น เราสามารถผลักความรับผิดชอบบางส่วนไปยัง service แต่ละตัวเพื่อทำ compensating action ได้ แต่โดยพื้นฐานแล้วเราต้องการวิธีรู้ว่า saga อยู่ใน state ไหนสำหรับการกู้คืนบางประเภท การไม่มีที่ centralized ให้ตรวจสอบ status ของ saga เป็นปัญหาใหญ่ เราได้สิ่งนี้มาด้วย orchestration แล้วเราจะแก้ปัญหานี้ที่นี่ได้อย่างไร?
วิธีง่ายที่สุดวิธีหนึ่งในการทำสิ่งนี้คือฉาย view เกี่ยวกับ state ของ saga โดยการ consume event ที่ถูก emit ถ้าเราสร้าง unique ID สำหรับ saga ซึ่งเรียกว่า correlation ID เราสามารถใส่มันลงใน event ทั้งหมดที่ถูก emit ในฐานะส่วนหนึ่งของ saga นี้ได้ เมื่อ service ตัวหนึ่งของเราตอบสนองต่อ event correlation ID จะถูกดึงออกมาและใช้สำหรับ local logging process ใด ๆ และมันยังถูกส่งต่อไปยัง downstream พร้อมกับ call หรือ event เพิ่มเติมที่ถูกยิงออกไปด้วย เราอาจมี service ที่มีหน้าที่แค่เก็บรวบรวม event ทั้งหมดเหล่านี้และนำเสนอ view ว่าแต่ละ order อยู่ใน state ไหน และอาจทำ action แบบ programmatic เพื่อแก้ปัญหาในฐานะส่วนหนึ่งของ fulfillment process ถ้า service อื่นทำเองไม่ได้ ผมถือว่า correlation ID บางรูปแบบจำเป็นสำหรับ choreographed saga แบบนี้ แต่ correlation ID ก็มีคุณค่ามากในภาพกว้างกว่าด้วย ซึ่งเราจะสำรวจในรายละเอียดมากขึ้นใน Chapter 10
Mixing styles (การผสม Style)
แม้ ดูเหมือนว่า orchestrated saga และ choreographed saga จะเป็นมุมมองที่ตรงข้ามกันอย่างสิ้นเชิงว่า saga ควรถูก implement อย่างไร แต่คุณสามารถพิจารณาผสมและจับคู่ model ได้อย่างง่ายดาย คุณอาจมี business process บางอย่างในระบบของคุณที่เข้ากับ model หนึ่งมากกว่าอีกแบบโดยธรรมชาติ คุณอาจมี saga ตัวเดียวที่ผสมผสาน style ด้วยเช่นกัน ใน use case ของ order fulfillment ยกตัวอย่างเช่น ภายในขอบเขตของ Warehouse service เมื่อจัดการการแพ็กและ dispatch order เราอาจใช้ orchestrated flow แม้ว่า request เดิมจะถูกทำในฐานะส่วนหนึ่งของ choreographed saga ที่ใหญ่กว่า 6
ถ้าคุณตัดสินใจผสม style คุณต้องมีวิธีที่ชัดเจนในการเข้าใจว่า saga อยู่ใน state ไหน และมี activity อะไรบ้างที่เกิดขึ้นแล้วในฐานะส่วนหนึ่งของ saga โดยไม่มีสิ่งนี้ การเข้าใจ failure mode จะซับซ้อนขึ้น และการกู้คืนจาก failure ก็ยาก
Tracing Calls (การ Trace Call)
ไม่ว่าคุณจะเลือก choreography หรือ orchestration เมื่อ implement business process โดยใช้ microservice หลายตัว เป็นเรื่องปกติที่จะต้องการ trace call ทั้งหมดที่เกี่ยวข้องกับ process บางครั้งนี่ก็แค่ช่วยให้คุณเข้าใจว่า business process กำลังทำงานถูกต้องหรือไม่ หรืออาจช่วยวินิจฉัยปัญหา ใน Chapter 10 เราจะดูแนวคิดอย่าง correlation ID และ log aggregation และวิธีที่พวกมันช่วยในเรื่องนี้ได้
Should I use choreography or orchestration (or a mix)? (ควรใช้ Choreography หรือ Orchestration (หรือผสม) ดี?)
การ implement choreographed saga อาจนำแนวคิดที่ไม่คุ้นเคยมาสู่คุณและทีมของคุณ พวกมันมักสมมติว่ามีการใช้ event-driven collaboration หนักมาก ซึ่งไม่ได้เป็นที่เข้าใจกันอย่างแพร่หลาย อย่างไรก็ตาม จากประสบการณ์ของผม ความซับซ้อนเพิ่มเติมที่มาพร้อมการติดตามความคืบหน้าของ saga มักถูกชดเชยด้วยประโยชน์ที่มาพร้อม architecture ที่ loosely coupled มากกว่าเสมอ
แต่ถ้าไม่นับรสนิยมส่วนตัวของผม คำแนะนำทั่วไปที่ผมให้เกี่ยวกับ orchestration กับ choreography คือผมค่อนข้างสบายใจกับการใช้ orchestrated saga เมื่อทีมเดียวเป็นเจ้าของ implementation ของ saga ทั้งหมด ในสถานการณ์แบบนี้ architecture ที่ coupled มากกว่าโดยธรรมชาตินั้นจัดการได้ง่ายกว่ามากภายในขอบเขตของทีม ถ้าคุณมีหลายทีมเกี่ยวข้อง ผมชอบ choreographed saga ที่แยกส่วนมากกว่าเยอะ เพราะมันง่ายกว่าที่จะกระจายความรับผิดชอบในการ implement saga ไปยังทีมต่าง ๆ โดย architecture ที่ loosely coupled มากกว่าช่วยให้ทีมเหล่านี้ทำงานได้อย่างอิสระมากขึ้น
ควรสังเกตว่าโดยทั่วไปแล้ว คุณมักโน้มเอียงไปทาง request-response call ด้วย orchestration ในขณะที่ choreography มักใช้ event เยอะกว่า นี่ไม่ใช่กฎตายตัว แค่เป็นข้อสังเกตทั่วไป ความโน้มเอียงของผมเองไปทาง choreography น่าจะเป็นผลจากการที่ผมมักโน้มเอียงไปทาง event-driven interaction model ถ้าคุณพบว่าการใช้ event-driven collaboration เข้าใจยาก choreography อาจไม่เหมาะกับคุณ
Sagas Versus Distributed Transactions (Saga เทียบกับ Distributed Transaction)
อย่างที่ ผมหวังว่าได้อธิบายไปแล้วจนถึงตอนนี้ distributed transaction มาพร้อมความท้าทายที่มีนัยสำคัญ และนอกเหนือจากบางสถานการณ์ที่เฉพาะเจาะจงมาก ๆ ผมมักหลีกเลี่ยงมัน Pat Helland ผู้บุกเบิกด้าน distributed system สรุปความท้าทายพื้นฐานที่มากับการ implement distributed transaction สำหรับ application ประเภทที่เราสร้างกันในปัจจุบัน: 7
In most distributed transaction systems, the failure of a single node causes transaction commit to stall. This in turn causes the application to get wedged. In such systems, the larger it gets, the more likely the system is going to be down. When flying an airplane that needs all of its engines to work, adding an engine reduces the availability of the airplane.
จากประสบการณ์ของผม การ model business process อย่างชัดเจนในรูปแบบ saga หลีกเลี่ยงความท้าทายมากมายของ distributed transaction ได้ พร้อมข้อดีเพิ่มเติมคือทำให้ process ที่อาจถูก model แบบไม่ชัดเจนอยู่แล้วชัดเจนและเห็นได้ชัดมากขึ้นสำหรับ developer ของคุณ การทำให้ business process หลักของระบบของคุณเป็น first-class concept จะมีข้อดีมากมาย