คู่มือการเขียนโปรแกรมเชิงกระบวนการใน Elixir และ OTP
เผยแพร่แล้ว: 2022-03-11คนชอบจัดหมวดหมู่ภาษาโปรแกรมเป็นกระบวนทัศน์ มีภาษาเชิงวัตถุ (OO) ภาษาที่จำเป็น ภาษาที่ใช้งานได้ ฯลฯ ซึ่งจะเป็นประโยชน์ในการหาว่าภาษาใดแก้ปัญหาที่คล้ายคลึงกัน และปัญหาประเภทใดที่ภาษาตั้งใจจะแก้ไข
ในแต่ละกรณี กระบวนทัศน์โดยทั่วไปจะมีจุดสนใจและเทคนิค "หลัก" เดียวซึ่งเป็นแรงผลักดันให้ตระกูลภาษานั้น:
ในภาษา OO เป็น คลาสหรืออ็อบเจ็กต์ ที่เป็นวิธีแค็ปซูลสถานะ (ข้อมูล) ด้วยการจัดการสถานะนั้น (เมธอด)
ในภาษาที่ใช้งานได้ อาจเป็นการ จัดการฟังก์ชัน เองหรือ ข้อมูลที่ไม่เปลี่ยนรูปแบบที่ ส่งผ่านจากฟังก์ชันหนึ่งไปยังอีกฟังก์ชันหนึ่ง
แม้ว่า Elixir (และ Erlang ก่อนหน้านั้น) มักถูกจัดประเภทเป็นภาษาที่ใช้งานได้ เพราะพวกเขาแสดงข้อมูลที่ไม่เปลี่ยนรูปซึ่งพบได้ทั่วไปในภาษาที่ใช้งานได้ ฉันจะส่งพวกเขาเป็นตัวแทนของ กระบวนทัศน์ที่แยกจากภาษาที่ใช้งานได้หลายภาษา พวกเขามีอยู่และนำมาใช้เนื่องจากการมีอยู่ของ OTP ดังนั้นฉันจะจัดหมวดหมู่เป็น ภาษาที่เน้นกระบวนการ
ในโพสต์นี้ เราจะพูดถึงความหมายของการเขียนโปรแกรมเชิงกระบวนการเมื่อใช้ภาษาเหล่านี้ สำรวจความแตกต่างและความคล้ายคลึงกันของกระบวนทัศน์อื่น ๆ ดูความหมายสำหรับการฝึกอบรมและการนำไปใช้ และปิดท้ายด้วยตัวอย่างการเขียนโปรแกรมเชิงกระบวนการสั้นๆ
การเขียนโปรแกรมเชิงกระบวนการคืออะไร?
มาเริ่มกันที่คำจำกัดความ: การเขียนโปรแกรมเชิงกระบวนการ เป็นกระบวนทัศน์ที่อิงตามกระบวนการที่ต่อเนื่องกันของการสื่อสาร ซึ่งมีพื้นเพมาจากบทความของ Tony Hoare ในปี 1977 ซึ่งเรียกกันทั่วไปว่าโมเดล นักแสดง ของการทำงานพร้อมกัน ภาษาอื่นที่เกี่ยวข้องกับงานต้นฉบับนี้ ได้แก่ Occam, Limbo และ Go เอกสารอย่างเป็นทางการเกี่ยวข้องกับการสื่อสารแบบซิงโครนัสเท่านั้น โมเดลนักแสดงส่วนใหญ่ (รวมถึง OTP) ใช้การสื่อสารแบบอะซิงโครนัสเช่นกัน เป็นไปได้เสมอที่จะสร้างการสื่อสารแบบซิงโครนัสบนการสื่อสารแบบอะซิงโครนัส และ OTP รองรับทั้งสองรูปแบบ
ในประวัตินี้ OTP ได้สร้างระบบสำหรับการคำนวณที่ทนทานต่อข้อผิดพลาดโดยการสื่อสารกระบวนการตามลำดับ สิ่งอำนวยความสะดวกที่ทนต่อข้อผิดพลาดมาจากแนวทาง "ปล่อยให้มันล้มเหลว" พร้อมการกู้คืนข้อผิดพลาดที่มั่นคงในรูปแบบของหัวหน้างานและการใช้การประมวลผลแบบกระจายที่เปิดใช้งานโดยโมเดลนักแสดง "ปล่อยให้มันล้มเหลว" สามารถตรงกันข้ามกับ "ป้องกันไม่ให้ล้มเหลว" เนื่องจากแบบแรกนั้นรองรับได้ง่ายกว่ามากและได้รับการพิสูจน์แล้วใน OTP ว่ามีความน่าเชื่อถือมากกว่าแบบหลังมาก เหตุผลก็คือความพยายามในการเขียนโปรแกรมที่จำเป็นในการป้องกันความล้มเหลว (ดังที่แสดงใน Java Checked exception model) นั้นมีความเกี่ยวข้องและเรียกร้องมากกว่ามาก
ดังนั้น โปรแกรมเชิงกระบวนการสามารถกำหนดเป็น กระบวนทัศน์ที่โครงสร้างกระบวนการและการสื่อสารระหว่างกระบวนการของระบบเป็นข้อกังวลหลัก
เชิงวัตถุกับการเขียนโปรแกรมเชิงกระบวนการ
ในการเขียนโปรแกรมเชิงวัตถุ โครงสร้างแบบคงที่ของข้อมูลและฟังก์ชันถือเป็นข้อกังวลหลัก วิธีใดที่จำเป็นสำหรับการจัดการข้อมูลที่ปิดล้อม และสิ่งที่ควรเป็นการเชื่อมต่อระหว่างวัตถุหรือคลาส ดังนั้น คลาสไดอะแกรมของ UML จึงเป็นตัวอย่างที่สำคัญของการโฟกัสนี้ ดังที่แสดงในรูปที่ 1
สามารถสังเกตได้ว่าคำวิจารณ์ทั่วไปของการเขียนโปรแกรมเชิงวัตถุคือไม่มีโฟลว์การควบคุมที่มองเห็นได้ เนื่องจากระบบประกอบด้วยคลาส/อ็อบเจ็กต์จำนวนมากที่กำหนดไว้แยกกัน จึงอาจเป็นเรื่องยากสำหรับผู้มีประสบการณ์น้อยในการมองเห็นขั้นตอนการควบคุมของระบบ โดยเฉพาะอย่างยิ่งสำหรับระบบที่มีการสืบทอดจำนวนมาก ซึ่งใช้อินเทอร์เฟซที่เป็นนามธรรมหรือไม่มีการพิมพ์ที่รัดกุม ในกรณีส่วนใหญ่ สิ่งสำคัญสำหรับนักพัฒนาคือต้องจดจำโครงสร้างระบบจำนวนมากเพื่อให้มีประสิทธิภาพ
จุดแข็งของแนวทางการพัฒนาเชิงวัตถุคือ ระบบสามารถขยายเพื่อรองรับวัตถุประเภทใหม่ที่มีผลกระทบจำกัดต่อรหัสที่มีอยู่ ตราบใดที่ประเภทวัตถุใหม่สอดคล้องกับความคาดหวังของรหัสที่มีอยู่
การเขียนโปรแกรมเชิงหน้าที่และเชิงกระบวนการ
ภาษาโปรแกรมเชิงฟังก์ชันหลายภาษาจัดการกับการทำงานพร้อมกันได้หลายวิธี แต่จุดสนใจหลักคือการส่งผ่านข้อมูลระหว่างฟังก์ชันที่ไม่เปลี่ยนรูปแบบ หรือการสร้างฟังก์ชันจากฟังก์ชันอื่นๆ (ฟังก์ชันลำดับสูงกว่าที่สร้างฟังก์ชัน) โดยส่วนใหญ่ จุดเน้นของภาษายังคงเป็นช่องว่างที่อยู่เดียวหรือปฏิบัติการได้ และการสื่อสารระหว่างโปรแกรมเรียกทำงานดังกล่าวจะได้รับการจัดการในลักษณะเฉพาะของระบบปฏิบัติการ
ตัวอย่างเช่น Scala เป็นภาษาที่ใช้งานได้ซึ่งสร้างขึ้นบน Java Virtual Machine แม้ว่าจะสามารถเข้าถึงสิ่งอำนวยความสะดวกของ Java เพื่อการสื่อสารได้ แต่ก็ไม่ได้เป็นส่วนหนึ่งของภาษา แม้ว่ามันจะเป็นภาษาทั่วไปที่ใช้ในการเขียนโปรแกรม Spark แต่ก็เป็นห้องสมุดที่ใช้ร่วมกับภาษาอีกครั้ง
จุดแข็งของกระบวนทัศน์การทำงานคือความสามารถในการเห็นภาพโฟลว์การควบคุมของระบบที่กำหนดฟังก์ชันระดับบนสุด โฟลว์การควบคุมมีความชัดเจนในการที่แต่ละฟังก์ชันเรียกใช้ฟังก์ชันอื่นๆ และส่งข้อมูลทั้งหมดจากที่หนึ่งไปยังอีกที่หนึ่ง ในกระบวนทัศน์การทำงานไม่มีผลข้างเคียง ซึ่งทำให้การกำหนดปัญหาง่ายขึ้น ความท้าทายของระบบการทำงานล้วนๆ คือ "ผลข้างเคียง" จำเป็นต้องมีสถานะคงอยู่ ในระบบที่ได้รับการออกแบบอย่างดี การคงอยู่ของสถานะจะได้รับการจัดการที่ระดับบนสุดของโฟลว์การควบคุม ทำให้ระบบส่วนใหญ่ไม่มีผลข้างเคียง
Elixir/OTP และการเขียนโปรแกรมเชิงกระบวนการ
ใน Elixir/Erlang และ OTP พื้นฐานการสื่อสารเป็นส่วนหนึ่งของเครื่องเสมือนที่รันภาษา ความสามารถในการสื่อสารระหว่างกระบวนการและระหว่างเครื่องถูกสร้างขึ้นและเป็นศูนย์กลางของระบบภาษา สิ่งนี้เน้นย้ำถึงความสำคัญของการสื่อสารในกระบวนทัศน์นี้และในระบบภาษาเหล่านี้
แม้ว่าภาษา Elixir จะ ทำงาน ได้อย่างโดดเด่นในแง่ของตรรกะที่แสดงออกในภาษานั้น การใช้ภาษา Elixir นั้น เน้นไปที่กระบวนการ
การมุ่งเน้นกระบวนการหมายความว่าอย่างไร
การมุ่งเน้นกระบวนการตามที่กำหนดไว้ในโพสต์นี้คือการออกแบบระบบก่อนในรูปแบบของกระบวนการที่มีอยู่และวิธีการสื่อสาร หนึ่งในคำถามหลักคือกระบวนการใดเป็นแบบคงที่และเป็นไดนามิกซึ่งเกิดขึ้นตามความต้องการของคำขอ ซึ่งให้บริการเพื่อวัตถุประสงค์ระยะยาว ซึ่งมีสถานะที่ใช้ร่วมกันหรือส่วนหนึ่งของสถานะที่ใช้ร่วมกันของระบบ และคุณลักษณะใดของ ระบบมีความสอดคล้องกันโดยเนื้อแท้ เช่นเดียวกับที่ OO มีประเภทของอ็อบเจ็กต์ และ functional มีประเภทของฟังก์ชัน การเขียนโปรแกรมเชิงกระบวนการก็มีประเภทของกระบวนการ
ดังนั้น การออกแบบเชิงกระบวนการจึงเป็นการระบุชุดของประเภทกระบวนการที่จำเป็นในการแก้ปัญหาหรือตอบสนองความต้องการ
ด้านของเวลาเข้าสู่ความพยายามในการออกแบบและความต้องการอย่างรวดเร็ว วงจรชีวิตของระบบคืออะไร? ความต้องการที่กำหนดเองใดเป็นครั้งคราวและสิ่งใดที่คงที่ โหลดในระบบอยู่ที่ไหน ความเร็วและปริมาตรที่คาดไว้คือเท่าใด หลังจากการพิจารณาประเภทนี้เป็นที่เข้าใจว่าการออกแบบเชิงกระบวนการเริ่มกำหนดหน้าที่ของแต่ละกระบวนการหรือตรรกะที่จะดำเนินการ
ผลกระทบของการฝึกอบรม
ความหมายของการจัดหมวดหมู่นี้ในการฝึกอบรมคือการฝึกอบรมไม่ควรเริ่มต้นด้วยไวยากรณ์ภาษาหรือตัวอย่าง "Hello World" แต่ควรเริ่มต้นด้วย การคิดทางวิศวกรรมระบบและการออกแบบที่เน้นที่การจัดสรรกระบวนการ
ความกังวลด้านการเข้ารหัสเป็นเรื่องรองในการออกแบบกระบวนการและการจัดสรรซึ่งได้รับการกล่าวถึงได้ดีที่สุดในระดับที่สูงขึ้น และเกี่ยวข้องกับการคิดข้ามสายงานเกี่ยวกับวงจรชีวิต, QA, DevOps และข้อกำหนดทางธุรกิจของลูกค้า หลักสูตรการฝึกอบรมใน Elixir หรือ Erlang ต้องมี (และโดยทั่วไป) รวม OTP และควรมีการวางแนวกระบวนการตั้งแต่เริ่มต้น ไม่ใช่เป็นแนวทางประเภท "ตอนนี้คุณสามารถเขียนโค้ดใน Elixir ได้ เรามาทำแบบพร้อมกัน"
ผลกระทบการรับเลี้ยงบุตรบุญธรรม
นัยสำหรับการนำไปใช้คือ ภาษาและระบบสามารถนำไปใช้กับปัญหาที่ต้องการการสื่อสารและ/หรือการกระจายการคำนวณได้ดีกว่า ปัญหาที่เป็นภาระงานเดียวในคอมพิวเตอร์เครื่องเดียวไม่น่าสนใจในพื้นที่นี้ และอาจแก้ไขได้ดีกว่าด้วยภาษาอื่น ระบบประมวลผลต่อเนื่องที่มีอายุการใช้งานยาวนานเป็นเป้าหมายหลักสำหรับภาษานี้ เนื่องจากมีความทนทานต่อข้อผิดพลาดในตัวตั้งแต่ต้น
สำหรับงานเอกสารและการออกแบบ การใช้สัญลักษณ์กราฟิกจะมีประโยชน์มาก (เช่น รูปที่ 1 สำหรับภาษา OO) คำแนะนำสำหรับ Elixir และการเขียนโปรแกรมเชิงกระบวนการจาก UML จะเป็นแผนภาพลำดับ (ตัวอย่างในรูปที่ 2) เพื่อแสดงความสัมพันธ์ชั่วคราวระหว่างกระบวนการและระบุว่ากระบวนการใดที่เกี่ยวข้องในการให้บริการคำขอ ไม่มีประเภทไดอะแกรม UML สำหรับการจับภาพวงจรชีวิตและโครงสร้างกระบวนการ แต่สามารถแสดงด้วยไดอะแกรมกล่องและลูกศรอย่างง่ายสำหรับประเภทกระบวนการและความสัมพันธ์ ตัวอย่างเช่น รูปที่ 3:
ตัวอย่างการวางแนวกระบวนการ
สุดท้าย เราจะอธิบายตัวอย่างสั้นๆ ของการนำการวางแนวกระบวนการไปใช้กับปัญหา สมมติว่าเราได้รับมอบหมายให้จัดระบบที่สนับสนุนการเลือกตั้งทั่วโลก ปัญหานี้ได้รับเลือกจากกิจกรรมแต่ละรายการจำนวนมากที่ดำเนินการเป็นชุดๆ แต่การรวมหรือการสรุปผลลัพธ์เป็นที่ต้องการในแบบเรียลไทม์และอาจเห็นภาระงานที่มีนัยสำคัญ
การออกแบบและการจัดสรรกระบวนการเบื้องต้น
ในขั้นต้น เราจะเห็นได้ว่าการลงคะแนนเสียงของแต่ละคนเป็นการหลั่งไหลเข้าสู่ระบบจากอินพุตที่ไม่ต่อเนื่องจำนวนมาก ไม่เรียงลำดับตามเวลา และสามารถมีภาระงานสูงได้ เพื่อสนับสนุนกิจกรรมนี้ เราต้องการกระบวนการจำนวนมากที่รวบรวมอินพุตเหล่านี้และส่งต่อไปยังกระบวนการที่เป็นศูนย์กลางมากขึ้นสำหรับการจัดตาราง กระบวนการเหล่านี้อาจตั้งอยู่ใกล้กับประชากรในแต่ละประเทศที่จะทำให้เกิดการลงคะแนนเสียง และทำให้มีเวลาแฝงต่ำ พวกเขาจะเก็บผลลัพธ์ในพื้นที่ บันทึกอินพุตทันที และส่งต่อเพื่อทำการจัดตารางเป็นกลุ่มเพื่อลดแบนด์วิดท์และค่าใช้จ่าย

ในขั้นต้นเราจะเห็นว่าจะต้องมีกระบวนการที่ติดตามการลงคะแนนในแต่ละเขตอำนาจศาลที่จะต้องนำเสนอผลลัพธ์ สมมติว่าสำหรับตัวอย่างนี้ เราต้องติดตามผลลัพธ์สำหรับแต่ละประเทศ และภายในแต่ละประเทศตามจังหวัด/รัฐ เพื่อสนับสนุนกิจกรรมนี้ เราต้องการอย่างน้อยหนึ่งกระบวนการต่อประเทศที่ทำการคำนวณ และคงยอดรวมในปัจจุบัน และอีกชุดหนึ่งสำหรับแต่ละรัฐ/จังหวัดในแต่ละประเทศ นี่ถือว่าเราจำเป็นต้องสามารถตอบผลรวมสำหรับประเทศและรัฐ/จังหวัดในแบบเรียลไทม์หรือเวลาแฝงต่ำ หากสามารถรับผลลัพธ์จากระบบฐานข้อมูล เราอาจเลือกการจัดสรรกระบวนการอื่น โดยที่ผลรวมจะถูกอัพเดตโดยกระบวนการชั่วคราว ข้อดีของการใช้กระบวนการเฉพาะสำหรับการคำนวณเหล่านี้คือ ผลลัพธ์จะเกิดขึ้นที่ความเร็วของหน่วยความจำและสามารถรับได้โดยมีเวลาในการตอบสนองต่ำ
ในที่สุด เราจะเห็นได้ว่าผู้คนจำนวนมากกำลังดูผลลัพธ์ กระบวนการเหล่านี้สามารถแบ่งพาร์ติชันได้หลายวิธี เราอาจต้องการกระจายโหลดโดยวางกระบวนการในแต่ละประเทศที่รับผิดชอบผลลัพธ์ของประเทศนั้น ๆ กระบวนการอาจแคชผลลัพธ์จากกระบวนการคำนวณเพื่อลดภาระการสืบค้นในกระบวนการคำนวณ และ/หรือกระบวนการคำนวณอาจส่งผลลัพธ์ไปยังกระบวนการผลลัพธ์ที่เหมาะสมเป็นระยะ เมื่อผลลัพธ์เปลี่ยนแปลงไปเป็นจำนวนมาก หรือตาม กระบวนการคำนวณที่ไม่ได้ใช้งานซึ่งบ่งชี้ว่าอัตราการเปลี่ยนแปลงช้าลง
ในกระบวนการทั้งสามประเภท เราสามารถปรับขนาดกระบวนการโดยแยกจากกัน กระจายตามพื้นที่ และรับประกันว่าผลลัพธ์จะไม่สูญหายไปจากการรับรู้ถึงการถ่ายโอนข้อมูลระหว่างกระบวนการ
ตามที่กล่าวไว้ เราได้เริ่มตัวอย่างด้วยการออกแบบกระบวนการที่ไม่ขึ้นกับตรรกะทางธุรกิจในแต่ละกระบวนการ ในกรณีที่ตรรกะทางธุรกิจมีข้อกำหนดเฉพาะสำหรับการรวบรวมข้อมูลหรือภูมิศาสตร์ที่อาจส่งผลต่อการจัดสรรกระบวนการซ้ำๆ การออกแบบกระบวนการของเราจนถึงตอนนี้แสดงไว้ในรูปที่ 4
การใช้กระบวนการแยกกันเพื่อรับคะแนนเสียงทำให้แต่ละโหวตได้รับการโหวตโดยไม่ขึ้นกับคะแนนเสียงอื่น เข้าสู่ระบบเมื่อได้รับ และจัดกลุ่มไปยังกระบวนการชุดถัดไป ซึ่งช่วยลดภาระงานในระบบเหล่านั้นได้อย่างมาก สำหรับระบบที่ใช้ข้อมูลจำนวนมาก การลดปริมาณข้อมูลโดยใช้เลเยอร์ของกระบวนการเป็นรูปแบบทั่วไปและมีประโยชน์
การดำเนินการคำนวณในชุดกระบวนการที่แยกออกมา เราสามารถจัดการโหลดของกระบวนการเหล่านั้น และรับรองความเสถียรและความต้องการทรัพยากร
โดยการวางการนำเสนอผลลัพธ์ในชุดกระบวนการที่แยกออกมา เราทั้งคู่ควบคุมโหลดไปยังส่วนที่เหลือของระบบ และอนุญาตให้ชุดของกระบวนการถูกปรับขนาดแบบไดนามิกสำหรับโหลด
ข้อกำหนดเพิ่มเติม
ตอนนี้ มาเพิ่มข้อกำหนดที่ซับซ้อนบางอย่างกัน สมมติว่าในแต่ละเขตอำนาจศาล (ประเทศหรือรัฐ) การจัดตารางคะแนนโหวตอาจส่งผลให้เกิดผลลัพธ์ที่เป็นสัดส่วน ผู้ชนะรับทั้งหมด หรือไม่มีผลใดๆ หากคะแนนโหวตไม่เพียงพอเมื่อเทียบกับจำนวนประชากรในเขตอำนาจศาลนั้น เขตอำนาจศาลแต่ละแห่งมีอำนาจควบคุมด้านเหล่านี้ ด้วยการเปลี่ยนแปลงนี้ ผลลัพธ์ของประเทศต่างๆ จะไม่ใช่การรวมผลการลงคะแนนดิบแบบง่ายๆ แต่เป็นการรวมผลลัพธ์ของรัฐ/จังหวัด สิ่งนี้จะเปลี่ยนการจัดสรรกระบวนการจากเดิมเพื่อให้ผลลัพธ์จากกระบวนการของรัฐ/จังหวัดป้อนเข้าสู่กระบวนการของประเทศ หากโปรโตคอลที่ใช้ระหว่างการรวบรวมคะแนนเสียงกับรัฐ/จังหวัดและกระบวนการจากจังหวัดหนึ่งไปยังอีกประเทศหนึ่งเหมือนกัน ตรรกะการรวมก็สามารถนำมาใช้ซ้ำได้ แต่ต้องใช้กระบวนการที่แตกต่างกันซึ่งเก็บผลลัพธ์และเส้นทางการสื่อสารจะแตกต่างกัน ดังแสดงในรูป 5.
รหัส
เพื่อให้ตัวอย่างสมบูรณ์ เราจะตรวจสอบการใช้งานตัวอย่างใน Elixir OTP เพื่อทำให้สิ่งต่าง ๆ ง่ายขึ้น ตัวอย่างนี้ถือว่าเว็บเซิร์ฟเวอร์เช่น Phoenix ใช้ในการประมวลผลคำขอเว็บจริง และบริการเว็บเหล่านั้นส่งคำขอไปยังกระบวนการที่ระบุไว้ข้างต้น มีข้อได้เปรียบในการลดความซับซ้อนของตัวอย่างและให้ความสำคัญกับ Elixir/OTP ในระบบการผลิต การแยกกระบวนการเหล่านี้มีข้อดีบางประการรวมถึงแยกข้อกังวล ช่วยให้ปรับใช้ที่ยืดหยุ่น กระจายโหลด และลดเวลาแฝง สามารถดูซอร์สโค้ดแบบเต็มพร้อมการทดสอบได้ที่ https://github.com/technomage/voting แหล่งที่มามีตัวย่อในโพสต์นี้เพื่อให้สามารถอ่านได้ แต่ละกระบวนการด้านล่างจะพอดีกับโครงสร้างการดูแล OTP เพื่อให้แน่ใจว่ากระบวนการจะเริ่มต้นใหม่เมื่อเกิดความล้มเหลว ดูแหล่งที่มาสำหรับข้อมูลเพิ่มเติมเกี่ยวกับแง่มุมนี้ของตัวอย่าง
บันทึกการลงคะแนน
กระบวนการนี้ได้รับการโหวต บันทึกพวกเขาไปยังร้านค้าถาวร และจัดกลุ่มผลลัพธ์ไปยังผู้รวบรวม โมดูล VoteRecoder ใช้ Task.Supervisor เพื่อจัดการงานอายุสั้นเพื่อบันทึกการลงคะแนนแต่ละครั้ง
defmodule Voting.VoteRecorder do @moduledoc """ This module receives votes and sends them to the proper aggregator. This module uses supervised tasks to ensure that any failure is recovered from and the vote is not lost. """ @doc """ Start a task to track the submittal of a vote to an aggregator. This is a supervised task to ensure completion. """ def cast_vote where, who do Task.Supervisor.async_nolink(Voting.VoteTaskSupervisor, fn -> Voting.Aggregator.submit_vote where, who end) |> Task.await end end
ผู้ลงคะแนนเสียง
กระบวนการนี้จะรวบรวมคะแนนโหวตภายในเขตอำนาจศาล คำนวณผลลัพธ์สำหรับเขตอำนาจศาลนั้น และส่งต่อสรุปการลงคะแนนไปยังกระบวนการที่สูงกว่าถัดไป (เขตอำนาจศาลระดับสูงกว่า หรือผู้นำเสนอผลลัพธ์)
defmodule Voting.Aggregator do use GenStage ... @doc """ Submit a single vote to an aggregator """ def submit_vote id, candidate do pid = __MODULE__.via_tuple(id) :ok = GenStage.call pid, {:submit_vote, candidate} end @doc """ Respond to requests """ def handle_call {:submit_vote, candidate}, _from, state do n = state.votes[candidate] || 0 state = %{state | votes: Map.put(state.votes, candidate, n+1)} {:reply, :ok, [%{state.id => state.votes}], state} end @doc """ Handle events from subordinate aggregators """ def handle_events events, _from, state do votes = Enum.reduce events, state.votes, fn e, votes -> Enum.reduce e, votes, fn {k,v}, votes -> Map.put(votes, k, v) # replace any entries for subordinates end end # Any jurisdiction specific policy would go here # Sum the votes by candidate for the published event merged = Enum.reduce votes, %{}, fn {j, jv}, votes -> # Each jourisdiction is summed for each candidate Enum.reduce jv, votes, fn {candidate, tot}, votes -> Logger.debug "@@@@ Votes in #{inspect j} for #{inspect candidate}: #{inspect tot}" n = votes[candidate] || 0 Map.put(votes, candidate, n + tot) end end # Return the published event and the state which retains # Votes by jourisdiction {:noreply, [%{state.id => merged}], %{state | votes: votes}} end end
ผู้นำเสนอผลงาน
กระบวนการนี้ได้รับคะแนนโหวตจากผู้รวบรวมและแคชผลลัพธ์เหล่านั้นไปยังคำขอบริการเพื่อนำเสนอผลลัพธ์
defmodule Voting.ResultPresenter do use GenStage … @doc """ Handle requests for results """ def handle_call :get_votes, _from, state do {:reply, {:ok, state.votes}, [], state} end @doc """ Obtain the results from this presenter """ def get_votes id do pid = Voting.ResultPresenter.via_tuple(id) {:ok, votes} = GenStage.call pid, :get_votes votes end @doc """ Receive votes from aggregator """ def handle_events events, _from, state do Logger.debug "@@@@ Presenter received: #{inspect events}" votes = Enum.reduce events, state.votes, fn v, votes -> Enum.reduce v, votes, fn {k,v}, votes -> Map.put(votes, k, v) end end {:noreply, [], %{state | votes: votes}} end end
ซื้อกลับบ้าน
โพสต์นี้สำรวจ Elixir/OTP จากศักยภาพของมันในฐานะภาษาเชิงกระบวนการ เปรียบเทียบสิ่งนี้กับกระบวนทัศน์เชิงวัตถุและเชิงฟังก์ชัน และทบทวนความหมายของสิ่งนี้ต่อการฝึกอบรมและการนำไปใช้
โพสต์ยังมีตัวอย่างสั้นๆ ของการนำการวางแนวนี้ไปใช้กับปัญหาตัวอย่างด้วย ในกรณีที่คุณต้องการตรวจสอบโค้ดทั้งหมด นี่คือลิงก์ไปยังตัวอย่างของเราบน GitHub อีกครั้ง เพื่อให้คุณไม่ต้องเลื่อนกลับไปหา
ประเด็นสำคัญคือการมองว่าระบบเป็นชุดของกระบวนการสื่อสาร วางแผนระบบจากมุมมองของการออกแบบกระบวนการก่อน และมุมมองการเข้ารหัสลอจิกที่สอง