Pattern: Asynchronous Nonblocking (รูปแบบ: Asynchronous Nonblocking)

ใน asynchronous communication การส่ง call ออกไปทางเครือข่ายไม่ block microservice ที่ยิง call นั้น มันสามารถทำงานอื่นต่อไปได้เลยโดยไม่ต้องรอ response asynchronous communication แบบ nonblocking มีหลายรูปแบบ แต่เราจะดูรายละเอียดสามรูปแบบที่พบบ่อยที่สุดใน microservice architecture ได้แก่:

Communication through common data (การสื่อสารผ่านข้อมูลร่วม)

upstream microservice เปลี่ยนแปลง common data บางอย่าง ซึ่ง microservice หนึ่งตัวหรือมากกว่านั้นนำไปใช้ในภายหลัง

Request-response

microservice ส่ง request ไปยังอีก microservice หนึ่งเพื่อขอให้ทำบางอย่าง เมื่อ operation ที่ร้องขอเสร็จสิ้น ไม่ว่าจะสำเร็จหรือไม่ upstream microservice จะได้รับ response โดยเฉพาะอย่างยิ่ง instance ไหนก็ตาม ของ upstream microservice ควรสามารถจัดการ response นั้นได้

Event-driven interaction (ปฏิสัมพันธ์แบบ Event-driven)

microservice broadcast event ซึ่งอาจมองว่าเป็นข้อความบอกข้อเท็จจริงเกี่ยวกับสิ่งที่เกิดขึ้น microservice อื่น ๆ สามารถฟัง event ที่ตัวเองสนใจและตอบสนองตามนั้น

Advantages (ข้อดี)

ด้วย nonblocking asynchronous communication microservice ที่ยิง call เริ่มต้นและ microservice (หรือหลาย microservice) ที่รับ call นั้นถูก decouple กันในเชิงเวลา microservice ที่รับ call ไม่จำเป็นต้อง reachable ในเวลาเดียวกับที่ call ถูกส่งออกไป นั่นหมายความว่าเราหลีกเลี่ยงประเด็นเรื่อง temporal decoupling ที่เราพูดถึงใน Chapter 2 (ดู "A Brief Note on Temporal Coupling" )

รูปแบบ communication นี้ยังมีประโยชน์ถ้า functionality ที่ถูก trigger โดย call นั้นใช้เวลาประมวลผลนาน ลองย้อนกลับไปดูตัวอย่าง MusicCorp โดยเฉพาะขั้นตอนการส่งพัสดุ ใน Figure 4-5 Order Processor รับ payment แล้วและตัดสินใจว่าถึงเวลาต้อง dispatch พัสดุ จึงส่ง call ไปยัง Warehouse microservice กระบวนการหา CD หยิบออกจากชั้น แพ็กของ และให้คนมารับ อาจใช้เวลาหลายชั่วโมงหรืออาจถึงหลายวัน ขึ้นอยู่กับว่ากระบวนการ dispatch จริง ๆ ทำงานอย่างไร ดังนั้นจึงสมเหตุสมผลที่ Order Processor จะยิง nonblocking asynchronous call ไปยัง Warehouse และให้ Warehouse call กลับมาทีหลังเพื่อแจ้ง Order Processor ถึงความคืบหน้า นี่คือรูปแบบหนึ่งของ asynchronous request-response communication

bms2 0405

Figure 4-5. Order Processor เริ่มกระบวนการแพ็กและจัดส่ง order ซึ่งทำแบบ asynchronous

ถ้าเราลองทำสิ่งที่คล้ายกันด้วย synchronous blocking call เราจะต้องปรับโครงสร้างปฏิสัมพันธ์ระหว่าง Order Processor และ Warehouse ใหม่ เพราะมันคงเป็นไปไม่ได้ที่ Order Processor จะเปิด connection ส่ง request block การทำงานอื่น ๆ ใน thread ที่เรียก และรอ response ที่อาจใช้เวลาหลายชั่วโมงหรือหลายวัน

Disadvantages (ข้อเสีย)

ข้อเสียหลักของ nonblocking asynchronous communication เมื่อเทียบกับ blocking synchronous communication คือระดับความซับซ้อนและตัวเลือกที่มากมาย ดังที่เราได้สรุปไปแล้ว มี asynchronous communication หลายรูปแบบให้เลือก แล้วแบบไหนล่ะที่เหมาะกับคุณ? เมื่อเราเริ่มเจาะลึกว่ารูปแบบ communication เหล่านี้ถูก implement อย่างไร ก็มีเทคโนโลยีให้พิจารณามากมายจนอาจงงได้

ถ้า asynchronous communication ไม่ตรงกับ mental model การคิดเชิงคอมพิวเตอร์ของคุณ การปรับมาใช้ style แบบ asynchronous จะเป็นเรื่องท้าทายในตอนแรก และอย่างที่เราจะสำรวจต่อไปเมื่อดู style ต่าง ๆ ของ asynchronous communication อย่างละเอียด มีวิธีต่าง ๆ มากมายที่น่าสนใจที่คุณอาจพา ตัวเอง เข้าไปเจอปัญหาใหญ่ได้

Async/Await and When Asynchronous Is Still Blocking (Async/Await และเมื่อ Asynchronous ยังคง Blocking อยู่ดี)

เช่นเดียวกับ หลาย ๆ ด้านของงานคอมพิวเตอร์ เราอาจใช้คำเดียวกันในบริบทต่างกันแล้วมีความหมายต่างกันมาก style การเขียนโปรแกรมแบบหนึ่งที่ดูเหมือนจะได้รับความนิยมเป็นพิเศษคือการใช้ construct อย่าง async/await เพื่อทำงานกับแหล่งข้อมูลที่อาจเป็น asynchronous แต่ในสไตล์ blocking, synchronous

ใน Example 4-1 เราเห็นตัวอย่าง JavaScript ง่าย ๆ ของสิ่งนี้ในการทำงานจริง อัตราแลกเปลี่ยนเงินตราผันผวนบ่อยตลอดวัน และเรารับข้อมูลนี้ผ่าน message broker เรานิยาม Promise โดยทั่วไปแล้ว promise คือสิ่งที่จะ resolve เป็น state บางอย่างในอนาคต ในกรณีของเรา eurToGbp จะ resolve เป็นอัตราแลกเปลี่ยน euro-to-GBP ล่าสุดในที่สุด

Example 4-1. ตัวอย่างการทำงานกับ call ที่อาจเป็น asynchronous ในสไตล์ blocking, synchronous
async function f() {

  let eurToGbp = new Promise((resolve, reject) => {
    //code to fetch latest exchange rate between EUR and GBP
    ...
  });

  var latestRate = await eurToGbp; 
  process(latestRate); 
}

1

รอจนกว่าอัตราแลกเปลี่ยน EUR-to-GBP ล่าสุดจะถูกดึงมา

2

จะไม่ทำงานจนกว่า promise จะถูก fulfill

เมื่อเรา reference eurToGbp โดยใช้ await เราจะ block จนกว่า state ของ latestRate จะถูก fulfill — process จะยังไม่ทำงานจนกว่าเราจะ resolve state ของ eurToGbp ได้ 3

แม้ว่าอัตราแลกเปลี่ยนของเราจะถูกรับมาแบบ asynchronous แต่การใช้ await ในบริบทนี้หมายความว่าเรากำลัง block จนกว่า state ของ latestRate จะถูก resolve ดังนั้นแม้ว่าเทคโนโลยีเบื้องหลังที่เราใช้เพื่อดึงอัตรานั้นจะถือว่าเป็น asynchronous โดยธรรมชาติ (เช่น การรอ rate) แต่จากมุมมองของโค้ดเรา นี่คือปฏิสัมพันธ์แบบ synchronous, blocking โดยพื้นฐาน

Where to Use It (ควรใช้เมื่อไหร่)

ท้ายที่สุดแล้ว เมื่อพิจารณาว่า asynchronous communication เหมาะกับคุณหรือไม่ คุณยังต้องพิจารณาด้วยว่าต้องการ ประเภท ไหนของ asynchronous communication เพราะแต่ละประเภทมี trade-off ของตัวเอง แต่โดยทั่วไปแล้วมี use case เฉพาะบางอย่างที่ทำให้ผมเลือกใช้ asynchronous communication รูปแบบใดรูปแบบหนึ่ง กระบวนการที่ทำงานยาวนานเป็นตัวอย่างที่ชัดเจน ดังที่เราสำรวจใน Figure 4-5 รวมถึงสถานการณ์ที่คุณมี call chain ยาว ๆ ที่ปรับโครงสร้างใหม่ได้ยากก็อาจเป็นตัวเลือกที่ดีเช่นกัน เราจะเจาะลึกเรื่องนี้มากขึ้นเมื่อดูสาม รูปแบบที่พบบ่อยที่สุดของ asynchronous communication ได้แก่ request-response call, event-driven communication และ communication through common data