State Machine สามารถเก็บ State (สถานะ) ทั้งหมดที่เป็นไปได้ของ สิ่งใดสิ่งหนึ่ง และ Transition (การเปลี่ยนแปลงสถานะ) ที่อนุญาตระหว่าง State เหล่านั้น ตัวอย่างเช่น State Machine ของประตูจะมีแค่ 2 State (open และ closed) และ 2 Transition (opening และ closing)
แต่ State Machine ที่ซับซ้อนอาจมีหลาย State ที่แตกต่างกัน พร้อม Transition ร้อยๆ ระหว่างกัน จริงๆ แล้วถ้าคุณมองดูรอบๆ จะพบ Finite State Machines ล้อมรอบอยู่ทุกที่ — เมื่อคุณซื้อของบนเว็บไซต์ หยิบขนมจากเครื่องจ่ายอัตโนมัติ หรือแม้แต่ถอนเงินจาก ATM
ในบทความนี้ เราจะดูวิธีตั้งค่า State Machine ใน Ruby และใช้ Gem state_machines
State Machine ในการพัฒนา
เราต้องการ State Machine ในการพัฒนาเมื่อไหร่? คำตอบง่ายๆ คือเมื่อคุณต้องการจำลองกฎหลายอย่างสำหรับ State Transition หรือทำ Side-effect บน Transition บางอย่าง จุดสำคัญคือการระบุส่วนของแอปพลิเคชันที่จะได้ประโยชน์จาก State Machine ตัวอย่างที่ดีและใช้ได้ตลอดคือ Order ในบริบทของแอป E-commerce
สำหรับแอปขายสินค้าออนไลน์ทั่วไป Order อาจอยู่ใน State ต่อไปนี้:
createdprocessingreadyshippeddeliveredvoid
คุณสามารถเห็น Transition ที่อนุญาตใน Diagram ต่อไปนี้

การมอง Order ในรูป State Machine ทำให้เราเข้าใจ Flow ทั้งหมดตั้งแต่สั่งสินค้าจนถึงการจัดส่งได้ชัดเจน และสำหรับเรา Developer มันให้ขั้นตอนที่ชัดเจนว่า สิ่งไหนทำได้/ไม่ได้ ที่จุดต่างๆ ใน Flow นั้น
ด้วยนั้น ง่ายมากที่จะหลงทางลง Rabbit Hole ด้วยการนำไปใช้กับปัญหากว้างเกินไป และสุดท้ายได้หลายร้อย State ที่ยากต่อการเข้าใจและติดตาม ดังนั้นควรมีไอเดียภาพรวมและ State Chart สำหรับ State Machine ที่เสนอก่อนจะ Implement
State Machine แรกของเราใน Ruby
ลอง Implement OrderStateMachine ด้วย Ruby
Implementation นี้เรียบง่ายพอ สำหรับแต่ละ Transition ที่เป็นไปได้ใน State Machine เรากำหนด Method ใหม่ที่ทำ Sanity Check และ Perform Transition
จุดดีของ Implementation ข้างบนคือทุกอย่างชัดเจน (Explicit): Developer ใหม่เข้าใจขอบเขต State Machine ได้เร็วมาก
ลองดูการเพิ่ม Side-effects เข้า Transition เราจะ Ship Order อัตโนมัติเมื่อ Packed และ Deliverable ได้:
แม้ Implementation แบบ Naïve จะทำงานได้ดี แต่มัน Verbose (冗长) เกินไป โดยเฉพาะเมื่อมี Transition และ Condition มาก
เช็คเร็วบน Ruby Toolbox จะพบ Gem หลายตัวสำหรับ State Machine state_machines และ aasm เป็นที่นิยมที่สุด ทั้งคู่มี ActiveRecord Adapter หากต้องการใช้กับ Rails ทั้งคู่ทดสอบครบถ้วนและ Production-ready แล้ว ลองดูถ้าคุณต้อง Implement State Machine
ใช้ Gem state_machines ใน Ruby
ในบทความนี้ ผมจะอธิบายการ Model State Machine สำหรับ Order ด้วย Gem state_machines
Class ด้านบนกำหนด State Machine เรียบง่ายและ Transition ทั้งหมดที่เป็นไปได้ นอกจากนี้ยัง Expose Utility Methods มากมายให้ Order อัตโนมัติ:
Execute Side-Effects ด้วย Gem state_machines ใน Ruby
เช่นปกติในระบบจริง Transition หลายอย่างใน State Machine จะมาพร้อม Side-effects Gem state_machines ทำให้กำหนดและ Execute ได้ง่าย มาเพิ่ม Side-effects 2 อย่าง:
- บน Transition ทั้งหมดไป
readyสร้าง Delivery Consignment หาก Orderdeliverable - บน Event
fail_deliveryทั้งหมด ส่ง Email แจ้งเตือน User เกี่ยวกับ Event
เราสามารถ Perform Transition ตามปกติใน Order:
แนะนำให้เช็ค Github Page ของ Gem state_machines เพื่อเรียนรู้ Options ทั้งหมดที่ Gem นำเสนอ
ใช้ Gem state_machines กับ ActiveRecord ใน Ruby on Rails
เช่นที่พูดไว้ ทั้ง state_machines และ aasm รองรับ ActiveRecord ตอนใช้ Gem state_machines กับ ActiveRecord ไม่มีอะไรเปลี่ยนแปลงมากกับ Implementation คุณยังคงใช้สิ่งที่พูดไปในส่วนก่อนหน้านี้ได้ส่วนใหญ่
นี่คือ Feature เพิ่มเติมที่คุณได้:
- Side-effects เกิดขึ้นภายใน Transaction หมายความว่าถ้าคุณทำ Database Operations ภายใน Transition Hooks และ Transaction ล้มเหลว State Change จะไม่ถูก Commit ดู
use_transactionsหากต้องการปิดพฤติกรรมนี้ - เหมือน ActiveRecord Callbacks ทั้งหมด หากคุณต้องการ Update Attribute ของ Model ปัจจุบันจาก Transition คุณต้องทำภายใน Callback
before_transitionเพื่อ Update Attribute ภายในafter_transitionคุณต้อง Save Model อีกครั้ง ดูตัวอย่าง:
- Gem ให้ Scopes อัตโนมัติเพื่อ Filter Model ตาม State เช่น
Order.with_state(:processing)หา Order ที่กำลัง Process หรือOrder.without_state(:processing)หา Order ที่ไม่ได้ Process - หากพยายามเปลี่ยน State โดยไม่มี Transition ที่ตรงกัน (เช่นจาก
processingไปdelivered) Record จะ Save ไม่ได้ และ Error จะถูกเพิ่มใน Validation Errors หากต้องการ Internationalize Error Messages ที่สร้างขึ้น ให้เพิ่ม Key ให้ Translation สำหรับ States และ Events ใน Config Files (เช่นen.yml):
Side note: ดูบทความนี้เพื่อ Troubleshoot ปัญหาประสิทธิภาพ ActiveRecord
สรุป: สร้าง State Machines ใน Ruby
ในบทความนี้ เราสำรวจว่าทำไมต้องใช้ State Machine ในการพัฒนา ก่อนสร้าง State Machine เรียบง่าย สุดท้ายดูการใช้ Gem state_machines ใน Ruby และ Ruby on Rails
ตอนนี้คุณเข้าใจ State Machine พื้นฐานแล้ว มั่นใจว่าคุณจะจดจำสถานการณ์หลายอย่างรอบตัวที่ใช้ State Machine อยู่แล้ว หรือจะได้ประโยชน์มากจากมัน
จนกว่าจะเจอกันใหม่ ขอให้โชคดีกับการสร้าง State Machines!
P.S. หากต้องการอ่านบทความ Ruby Magic ทันทีที่เผยแพร่ สมัคร Newsletter Ruby Magic ของเราและจะไม่พลาดบทความเด็ดๆ ไป!
Pulkit Goyal
ผู้เขียน אורח Pulkit เป็น Senior Full-stack Engineer และ Consultant เวลาว่างเขียนประสบการณ์บนบล็อกตัวเอง
บทความทั้งหมดโดย Pulkit Goyal