Enforced Heterogeneity (ความหลากหลายทางเทคโนโลยีที่บังคับใช้)

สถาปนิกผู้บุกเบิกสไตล์ microservices ท่านหนึ่งเคยเป็นหัวหน้าสถาปนิกที่สตาร์ทอัพแห่งหนึ่ง ซึ่งสร้างซอฟต์แวร์จัดการ ข้อมูลส่วนบุคคลสำหรับอุปกรณ์มือถือ เนื่องจากมือถือเป็นโดเมนปัญหาที่เปลี่ยนแปลงเร็วมาก สถาปนิกท่านนี้ต้องการมั่นใจว่า ไม่มีทีมพัฒนาทีมไหนสร้างจุด coupling ขึ้นมาโดยไม่ตั้งใจ จนขัดขวางความสามารถในการทำงานอย่างอิสระของแต่ละทีม ปรากฏว่าแต่ละทีมมีทักษะทางเทคนิคที่หลากหลายมาก สถาปนิกจึงออกกฎใหม่ว่าแต่ละทีมพัฒนาต้องใช้ technology stack ที่ แตกต่างกัน หากทีมหนึ่งใช้ Java และอีกทีมใช้ .NET ก็จะเป็นไปไม่ได้เลยที่จะแชร์คลาสกันโดยไม่ตั้งใจ!

แนวทางนี้ตรงข้ามกับนโยบายการกำกับดูแลระดับองค์กรส่วนใหญ่ ซึ่งมักยืนกรานให้ใช้ technology stack เดียวเป็นมาตรฐาน ในโลกของ microservices เป้าหมายไม่ใช่การสร้างระบบนิเวศที่ซับซ้อนที่สุดเท่าที่จะทำได้ แต่คือการเลือกขนาดเทคโนโลยี ที่เหมาะสมกับขอบเขตปัญหาที่แคบของแต่ละ service ไม่ใช่ทุก service ที่ต้องการ relational database ระดับอุตสาหกรรม และการบังคับใช้กับทีมเล็ก ๆ มีแนวโน้มจะทำให้พวกเขาทำงานช้าลงมากกว่าจะได้ประโยชน์ แนวคิดนี้ใช้ประโยชน์จากธรรมชาติ ที่ decouple กันสูงของ microservices

สำหรับการสื่อสารแบบ asynchronous สถาปนิกมักใช้ event และ message คล้ายกับที่เราอธิบายไว้ใน event-driven architecture ใน Chapter 15

Choreography and Orchestration (Choreography และ Orchestration)

Choreography ใช้รูปแบบการสื่อสารเดียวกับ EDA สถาปัตยกรรมแบบ choreography ไม่มีผู้ประสานงานส่วนกลาง ยึดตามปรัชญา bounded context และทำให้การ implement event แบบ decoupled ระหว่าง service เป็นเรื่องธรรมชาติ

ใน choreography แต่ละ service จะเรียก service อื่นตามที่จำเป็น โดยไม่มี mediator ส่วนกลาง ตัวอย่างเช่น ลองพิจารณาสถานการณ์ใน Figure 18-7 ผู้ใช้ขอดูรายละเอียด wish list ของผู้ใช้อีกคน เนื่องจาก service CustomerWishList ไม่มีข้อมูลทั้งหมดที่ต้องการ มันจึงเรียกไปยัง CustomerDemographics เพื่อดึงข้อมูลที่ขาดหายไป แล้วจึงส่งผลลัพธ์กลับไปให้ผู้ใช้

Simple choreography

Figure 18-7. การใช้ choreography ใน microservices เพื่อจัดการการประสานงาน

เนื่องจาก microservices ไม่มี global mediator เหมือน service-oriented architecture แบบอื่น หากสถาปนิกต้องประสานงาน ข้าม service หลาย ๆ ตัว พวกเขาสามารถสร้าง mediator เฉพาะจุดขึ้นมาเอง (มักเรียกว่า orchestration service )

ใน Figure 18-8 นักพัฒนาสร้าง service ที่มีหน้าที่เดียวคือประสานการเรียก ตัวอย่างเช่น ผู้ใช้เรียก mediator ReportCustomerInformation ซึ่งจะเรียก service อื่น ๆ ที่จำเป็นทั้งหมดเพื่อดึงข้อมูลที่ต้องการ

Simple orchestration

Figure 18-8. การใช้ orchestration ใน microservices

กฎข้อแรกของสถาปัตยกรรมซอฟต์แวร์บอกว่าไม่มีทางออกไหนสมบูรณ์แบบ—แต่ละแบบล้วนมี trade-off Choreography รักษาปรัชญา decoupled สุดขั้วของ microservices ไว้เพื่อเก็บเกี่ยวประโยชน์สูงสุด แต่ก็ทำให้ปัญหาทั่วไปอย่าง การจัดการข้อผิดพลาดและการประสานงานซับซ้อนขึ้นเช่นกัน

ลองพิจารณาตัวอย่างที่มี workflow ซับซ้อนกว่านี้ ใน Figure 18-9 service แรกที่ถูกเรียกต้องประสานงานกับ service อื่น ๆ ที่หลากหลาย ซึ่งก็คือทำหน้าที่เป็น mediator นอกเหนือจาก ความรับผิดชอบด้านโดเมนของตัวเอง สิ่งนี้เรียกว่ารูปแบบ Front Controller ซึ่ง service ที่ควรจะเป็น choreographed กลายเป็น mediator ที่ซับซ้อนขึ้นสำหรับปัญหาบางอย่าง ข้อเสียของรูปแบบนี้คือ service ที่รับหลายบทบาทจะมีความซับซ้อนเพิ่มขึ้น

Complex choreography

Figure 18-9. การใช้ choreography สำหรับกระบวนการทางธุรกิจที่ซับซ้อน

อีกทางหนึ่ง สถาปนิกอาจเลือกใช้ orchestration สำหรับกระบวนการทางธุรกิจที่ซับซ้อน ดังแสดงใน Figure 18-10 สถาปนิกสร้าง mediator service เพื่อประสาน workflow ทางธุรกิจ ซึ่งสร้าง coupling ระหว่าง service เหล่านั้น แต่ก็ทำให้สถาปนิกโฟกัสการประสานงานไว้ที่ service เดียว ปล่อยให้ service อื่นได้รับผลกระทบน้อยลง workflow ของโดเมนมักผูกติดกันโดยธรรมชาติอยู่แล้ว ดังนั้นหน้าที่ของสถาปนิกคือหาวิธีแสดง coupling นั้นออกมาให้ตอบโจทย์ เป้าหมายทั้งของโดเมนและของสถาปัตยกรรมได้ดีที่สุด

Complex orchestration

Figure 18-10. การใช้ orchestration สำหรับกระบวนการทางธุรกิจที่ซับซ้อน

Transactions and Sagas (ธุรกรรมและ Saga)

สถาปนิกใฝ่ฝันถึงการ decouple แบบสุดขั้วใน microservices แต่ก็มักเจอปัญหาว่าจะประสานงานธุรกรรมข้าม service อย่างไร เนื่องจาก microservices สนับสนุนให้ decouple ฐานข้อมูลในระดับเดียวกับสถาปัตยกรรม ความเป็นเอกภาพ (atomicity) ที่เคยเป็นเรื่องเล็กน้อยในแอปพลิเคชันแบบ monolithic จึงกลายเป็นปัญหาในระบบแบบ distributed

การสร้างธุรกรรมข้ามขอบเขต service ถือเป็นการละเมิดหลักการ decoupling หลักของ microservices และยังก่อให้เกิด dynamic connascence ประเภทเลวร้ายที่สุดอย่าง Connascence of Values (ดู "Connascence" ) คำแนะนำที่ดีที่สุดที่เราให้กับสถาปนิกที่ต้องการทำธุรกรรมข้าม service คือ: อย่าทำ! ให้แก้ที่ granularity ของ service แทน หากคุณพบว่าต้องเชื่อมสถาปัตยกรรม microservices ของคุณเข้าด้วยกันด้วยธุรกรรม นั่นเป็นสัญญาณว่าการออกแบบของคุณมี granularity ละเอียดเกินไป

Tip (เคล็ดลับ)

พยายามหลีกเลี่ยงธุรกรรมที่พาดผ่านหลาย microservice—ให้แก้ที่ granularity ของ service แทน!

ตามกฎ "it depends" ย่อมมีข้อยกเว้นเสมอ ตัวอย่างเช่น อาจเกิดสถานการณ์ที่ service สองตัวต้องการคุณลักษณะ สถาปัตยกรรมที่แตกต่างกันมาก จนต้องมีขอบเขต service แยกกัน แต่ก็ยังต้องการการประสานงานแบบธุรกรรมอยู่ดี ในกรณีนั้น สถาปนิกอาจพิจารณา trade-off อย่างรอบคอบ แล้วใช้ transactional pattern บางแบบเพื่อ orchestrate ธุรกรรม

ธุรกรรมแบบ distributed ใน microservices มักถูกจัดการด้วยสิ่งที่เรียกว่ารูปแบบ Saga ในวรรณกรรม saga คือเรื่องราวมหากาพย์ที่บรรยายเหตุการณ์ยาวนานซึ่งนำไปสู่บทสรุปแบบวีรบุรุษ นี่จึงเป็นที่มาของชื่อ transactional pattern นี้

ใน Figure 18-11 service หนึ่งทำหน้าที่เป็น mediator ประสานการเรียก service หลายตัวเพื่อจัดการธุรกรรม mediator เรียกแต่ละส่วน ของธุรกรรม บันทึกผลสำเร็จหรือล้มเหลว และประสานผลลัพธ์เข้าด้วยกัน หากทุกอย่างเป็นไปตามแผน ค่าทั้งหมดใน service และฐานข้อมูลของมันจะอัปเดตพร้อมกัน แต่หากเกิดข้อผิดพลาดและส่วนหนึ่งของธุรกรรมล้มเหลว mediator ต้องมั่นใจว่า ไม่มีส่วนไหนของธุรกรรมสำเร็จเลย ลองพิจารณาสถานการณ์ใน Figure 18-12

Saga pattern happy path

Figure 18-11. รูปแบบ Saga ในสถาปัตยกรรม microservices

หากส่วนแรกของธุรกรรมสำเร็จแต่ส่วนที่สองล้มเหลว mediator ต้องส่ง request ไปยัง service อื่น ๆ ที่เข้าร่วมธุรกรรม ซึ่งสำเร็จไปแล้ว เพื่อบอกให้ย้อนกลับ (undo) request ก่อนหน้า รูปแบบการประสานธุรกรรมแบบนี้เรียกว่า compensating transaction framework นักพัฒนามักจะ implement รูปแบบนี้โดยให้แต่ละ request จาก mediator เข้าสู่สถานะ pending จนกว่า mediator จะบอกว่าสำเร็จโดยรวม อย่างไรก็ตาม การจัดการ request แบบ asynchronous หลาย ๆ ตัวพร้อมกันอาจซับซ้อนขึ้นมาก โดยเฉพาะเมื่อมี request ใหม่ที่ขึ้นอยู่กับสถานะธุรกรรมที่ยัง pending อยู่ ไม่ว่าจะใช้โปรโตคอลใด compensating transaction ก็สร้าง traffic การประสานงานจำนวนมากในระดับเครือข่าย

Saga pattern error

Figure 18-12. compensating transaction ของรูปแบบ Saga สำหรับเงื่อนไขข้อผิดพลาด
Tip (เคล็ดลับ)

บางครั้งก็จำเป็นต้องมีธุรกรรมบางส่วนพาดผ่าน service หลายตัว แต่ถ้ามันกลายเป็นคุณลักษณะหลักของสถาปัตยกรรม microservices ก็มักไม่ใช่ทางเลือกที่ถูกต้องแล้ว!

การจัดการธุรกรรมใน microservices เป็นเรื่องซับซ้อน และยังมีรายละเอียดให้เจาะลึกได้อีกมาก เราได้ระบุ Saga pattern แบบธุรกรรมไว้ถึงแปดแบบเพื่อแก้สถานการณ์ต่าง ๆ ซึ่งคุณสามารถอ่านได้ใน Chapter 12 ของหนังสือของเราเรื่อง Software Architecture: The Hard Parts (O'Reilly, 2021) ซึ่งเขียนร่วมกับ Pramod Sadalage และ Zhamak Dehghani