Agent Dev Thai Academyprinciples · patterns · architecture

แอปไม่ได้ไม่มีบั๊ก มันแค่ยังไม่มีคนใช้

ลูกค้าโดนตัดเงินสองรอบเพราะกดซ้ำบนเน็ตช้า การส่งซ้ำเกิดแน่นอน ออกแบบให้ส่งกี่รอบก็ได้ผลเดียว ปิดปุ่ม ส่ง idempotency key ใส่ unique constraint และเทสต์ด้วยการยิงซ้ำ

ลูกค้าทักมาว่าโดนตัดเงินสองรอบ

เปิดดู มีออร์เดอร์สองอัน เวลาห่างกันสี่วินาที ของเดียวกัน คนเดียวกัน

ลองกดเองสิบรอบ ไม่เคยเกิด

แล้วก็สรุปกับตัวเองว่าคงเป็นที่เขา


ไม่ใช่ที่เขา

ตอนเราเทสต์ เราอยู่บนไวไฟบ้าน กดปุ๊บขึ้นปั๊บ เรารู้ว่าระบบทำงานยังไงเพราะเราเขียนเอง เรากดครั้งเดียวแล้วรอ

ลูกค้าอยู่บน 4G ในลิฟต์ กดแล้วหน้าจอนิ่ง สองวินาทีผ่านไปไม่มีอะไรเกิดขึ้น

เขาก็กดอีกที

นั่นแหละ ไม่ได้ทำอะไรผิดเลยสักนิด เป็นสิ่งที่มนุษย์ทุกคนบนโลกทำ

แล้วก็ไม่ใช่แค่นิ้วคน เน็ตหลุดกลางทางแล้วมือถือส่งซ้ำให้เอง กดปุ่ม back แล้วกดใหม่ รีเฟรชหน้าจ่ายเงิน ปิดแอปแล้วเปิดใหม่ ทั้งหมดนี้คือคำสั่งเดิมที่วิ่งเข้ามาสองรอบ

แอปเราไม่ได้ไม่มีบั๊กพวกนี้ เราแค่เป็นผู้ใช้คนเดียว และผู้ใช้คนเดียวไม่เคยกดซ้ำ


หลักการมีข้อเดียว

การส่งคำสั่งซ้ำจะเกิดขึ้นแน่นอน ไม่ใช่เรื่องจะเกิดหรือไม่เกิด

เพราะฉะนั้นอย่าไปพยายามห้ามไม่ให้เกิด แต่ออกแบบให้ส่งกี่รอบก็ได้ผลลัพธ์เดียว

อันนี้เรียกว่า idempotent

กดจ่ายเงินสิบรอบ ต้องได้ออร์เดอร์เดียว ไม่ใช่สิบออร์เดอร์แล้วค่อยมาไล่ลบทีหลัง


ทำจริงมีสามชั้น แต่ละชั้นทำหน้าที่ต่างกัน

ชั้นแรก ปิดปุ่มทันทีที่กด แล้วขึ้นสถานะให้เห็นว่ากำลังทำงานอยู่

ชั้นนี้ช่วยเรื่องความรู้สึกเป็นหลัก คนจะไม่กดซ้ำถ้าเห็นว่ามีอะไรเกิดขึ้น แต่มันกันได้แค่นิ้ว กันเน็ตส่งซ้ำไม่ได้ ใครที่แก้แค่ชั้นนี้แล้วคิดว่าจบ เดี๋ยวจะกลับมาเจอกันใหม่

ชั้นที่สอง ส่งรหัสประจำคำสั่งไปด้วย

ตอนเปิดหน้าจ่ายเงิน สร้างรหัสสุ่มขึ้นมาหนึ่งอัน แล้วแนบไปกับทุกครั้งที่ยิงคำสั่ง ฝั่งเซิร์ฟเวอร์จำไว้ว่ารหัสไหนเคยทำไปแล้ว เจอรหัสซ้ำก็คืนผลลัพธ์เดิมกลับไป ไม่สร้างใหม่

ตัวรหัสนี้เรียกว่า idempotency key ระบบจ่ายเงินใหญ่ๆ ใช้กันหมด

ชั้นที่สาม ล็อกที่ฐานข้อมูล

ตั้ง unique constraint บนสิ่งที่ต้องไม่ซ้ำ เช่น รหัสคำสั่งนั้น ถ้าสองคำสั่งวิ่งเข้ามาพร้อมกันจริงๆ ฐานข้อมูลจะยอมให้ผ่านแค่อันเดียว อีกอันเด้งกลับ

ชั้นนี้ไม่สวย ไม่มีใครเห็น แต่เป็นชั้นเดียวที่การันตีได้จริง เพราะสองชั้นบนยังมีช่องว่างที่คำสั่งสองอันเข้ามาพร้อมกันเป๊ะๆ ได้ อาการนั้นเรียกว่า race condition

จำง่ายๆ ชั้นบนช่วยให้คนไม่กดซ้ำ ชั้นกลางตัดสินใจ ชั้นล่างการันตี


ทีนี้เรื่องที่เกี่ยวกับ AI โดยตรง

AI เขียนเส้นทางที่ทุกอย่างราบรื่นให้เราเก่งมาก กดแล้วสำเร็จ จบ สวยงาม

มันจะไม่เขียนเรื่องกดซ้ำให้ ถ้าเราไม่บอก เพราะเราไม่ได้ขอ และเพราะคนที่ขอส่วนใหญ่ก็ไม่รู้ว่าต้องขอ

ลองไปเปิดโค้ดหน้าจ่ายเงินที่ AI เขียนให้ดู แล้วหาว่ามีอะไรกันการกดซ้ำมั้ย

ส่วนใหญ่ไม่มี และมันไม่ได้ทำผิด เราไม่ได้บอก


เอาไปใช้วันนี้ เริ่มจากทดสอบก่อน

เปิดแอปตัวเอง ไปหน้าที่สำคัญที่สุด แล้วกดปุ่มรัวๆ ห้าครั้งเร็วๆ

ถ้าได้ผลลัพธ์มากกว่าหนึ่ง แปลว่าลูกค้าคนแรกที่เน็ตช้าจะเจอสิ่งเดียวกัน

แล้วบอก AI แบบนี้

flow นี้ต้องกดกี่ครั้งก็ได้ผลเดียว (idempotent) ทำให้หน่อย
- ฝั่ง client: ปิดปุ่มตอนกำลังส่ง แล้วขึ้นสถานะให้เห็น
- ฝั่ง server: รับ idempotency key ถ้าเคยเห็นรหัสนี้แล้ว ให้คืนผลเดิม ห้ามสร้างใหม่
- ฝั่ง database: ใส่ unique constraint กันไว้อีกชั้น
- เขียนเทสต์ที่ยิงคำสั่งเดิมสองรอบ แล้ว assert ว่าได้ผลลัพธ์อันเดียว

ข้อสุดท้ายสำคัญที่สุด ถ้าไม่มีเทสต์ที่ยิงซ้ำ เราจะไม่มีวันรู้ว่าที่แก้ไปมันได้ผลจริงหรือเปล่า


ศัพท์เอาไปค้นต่อ

idempotent คือทำกี่รอบก็ได้ผลเดียว
idempotency key คือรหัสประจำคำสั่ง ไว้ให้เซิร์ฟเวอร์รู้ว่าเคยเห็นแล้ว
unique constraint คือกฎในฐานข้อมูลที่ห้ามข้อมูลซ้ำ
race condition คือสองคำสั่งวิ่งเข้ามาพร้อมกันจนผลลัพธ์เพี้ยน
double submit คือชื่อเรียกอาการกดซ้ำนี้ทั้งหมด

แอปที่ยังไม่มีคนใช้ ไม่ได้แปลว่าไม่มีบั๊ก แปลว่ายังไม่มีใครมากดให้มันโผล่