

หน้าฟอร์มแก้ข้อมูลอาจถูกเปิดค้างไว้นานกว่าที่เราคิด ระหว่างคนหนึ่งกำลังปรับรายละเอียด อีกคนอาจเข้ามาแก้สถานะหรือชื่อไปก่อนแล้ว หากปุ่มบันทึกส่งข้อมูลทั้งก้อนกลับไปโดยไม่ตรวจรุ่น คนที่กดทีหลังก็อาจเขียนทับการแก้ของคนแรกได้
ทั้งสองคำขออาจผ่านการตรวจรูปแบบและสิทธิ์อย่างถูกต้องด้วย ปัญหาจึงไม่ได้อยู่ที่ว่าใครไม่มีสิทธิ์แก้ แต่เป็นข้อมูลที่แต่ละคนเห็นก่อนเริ่มแก้ไม่ใช่ฉบับเดียวกันอีกแล้ว การตอบว่าบันทึกสำเร็จทุกครั้งอาจซ่อนความสูญเสียของงานอีกคนไว้
ทางหนึ่งคือให้การบันทึกมีเงื่อนไขว่ารุ่นที่กำลังอ้างอิงยังตรงกับปัจจุบันอยู่หรือไม่ครับ แล้วออกแบบทางให้คนใช้ตัดสินใจต่อเมื่อพบความต่าง
เมื่อส่งข้อมูลให้หน้าจอ บริการอาจส่งรหัสรุ่นมาด้วย เช่น version ที่เพิ่มตามการเปลี่ยน หรือ ETag ที่สร้างตามกติกาของ representation นั้น ผู้เรียกจึงเก็บทั้งข้อมูลที่เห็นกับตัวอ้างอิงฉบับ ไม่เก็บเพียงค่าที่แก้ในช่องฟอร์ม
ตัวอย่างสมมติคือสองคนอ่านฉบับ r7 พร้อมกัน คนแรกบันทึกแล้วระบบเปลี่ยนเป็น r8 ส่วนคนที่สองยังอ้าง r7 อยู่ หากเขาส่งคำขอว่าแก้ได้เมื่อยังเป็นฉบับที่อ่านมา ระบบก็มีข้อมูลพอให้ตรวจความขัดแย้งก่อนเขียนทับ
รหัสรุ่นนี้ไม่จำเป็นต้องเป็นตัวเลขที่หน้าจอคำนวณเอง ควรเป็นค่าที่บริการนิยามและส่งให้ใช้ตามสัญญา ไม่ให้ client เพิ่มเลขแล้วถือว่าเป็นข้อมูลฉบับล่าสุดของฐาน ทั้งที่ยังไม่เคยเห็นการเปลี่ยนระหว่างทาง

HTTP มี If-Match สำหรับกำหนดเงื่อนไขจาก entity tag ที่อ้างถึง representation ปัจจุบัน ซึ่งใช้ช่วยป้องกันการเขียนทับโดยไม่ตั้งใจได้ ตัวอย่าง header ต่อไปนี้ใช้ค่าแต่งเพื่ออธิบายเท่านั้น ไม่ได้มาจาก API ของผู้ใช้
If-Match: "sample-r7"
การเปรียบเทียบสำหรับ If-Match เป็นแบบ strong ตามมาตรฐาน จึงต้องดูว่า ETag ที่บริการให้เหมาะกับเงื่อนไขนี้หรือไม่ ไม่หยิบ weak ETag ที่มีคำนำหน้า W/ มาใช้เหมือนกันโดยไม่ตรวจความหมาย และ If-Match ที่เป็นดาวก็ตรวจเรื่องการมี representation ไม่ได้เลือกฉบับที่คนนี้อ่านมาอย่างเฉพาะเจาะจง
เมื่อเงื่อนไขไม่ผ่าน โดยทั่วไปบริการจะตอบ412 Precondition Failed ตามพฤติกรรมที่กำหนดในมาตรฐานและสัญญา API ส่วนกรณีที่ยืนยันได้ว่าการเปลี่ยนเดียวกันถูกทำไปแล้วมีรายละเอียดแยกตาม RFC ไม่ควรตีความว่าทุกคำขอที่พบรุ่นต่างต้องถูกบังคับให้เขียนซ้ำเพื่อให้สำเร็จ
หากบริการอ่านรุ่นก่อน แล้วค่อยสั่ง update โดยไม่ใส่เงื่อนไขรุ่น ระหว่างสองขั้นอาจมีอีกคำขอเข้ามาแก้ได้อยู่ดี การมี header ในคำขอจึงยังไม่พอ ต้องนำเงื่อนไขไปบังคับกับจุดที่เขียนข้อมูลจริงด้วย
ตัวอย่างแนวคิดสำหรับ PostgreSQL อาจใช้คำสั่งแบบมีเงื่อนไขร่วมกัน โดยชื่อ table กับ field ต่อไปนี้เป็น schema สมมติ และ parameter ของ tenant ต้องมาจากบริบทที่ตรวจสิทธิ์แล้ว ไม่ใช่เชื่อค่าจากฟอร์มโดยตรง
UPDATE example_items
SET title = $1, version = version + 1
WHERE id = $2
AND tenant_id = $3
AND version = $4;
แอปต้องตรวจจำนวนแถวที่เปลี่ยนจริงและจัดการผลต่อ คำสั่งที่ไม่แก้แถวใดอาจเกิดจากรุ่นเปลี่ยน ไม่พบข้อมูล หรือไม่มีรายการในขอบเขตที่อนุญาต จึงควรตอบตามนโยบายโดยไม่เปิดข้อมูลที่ผู้เรียกไม่มีสิทธิ์รู้ และไม่รายงานสำเร็จเพียงเพราะคำสั่ง SQL ไม่โยน error

ถ้าหน้าจออ่านข้อมูลที่ประกอบจากหลายตาราง แต่ตรวจรุ่นเฉพาะแถวหนึ่ง ต้องรู้ว่าการเปลี่ยนส่วนอื่นควรทำให้เงื่อนไขขัดแย้งหรือไม่ ไม่ถือว่าเลข version หนึ่งค่าให้การป้องกันทุกข้อมูลที่เคยแสดงในหน้าโดยอัตโนมัติ
เส้นทางเขียนอื่นก็ต้องรักษากติกานี้ด้วย เช่น งานนำเข้าและ worker ที่เปลี่ยนรายการเดียวกัน หากบางเส้นทางแก้ข้อมูลโดยไม่เปลี่ยนรุ่น ตัวที่เปิดฟอร์มอยู่ก็อาจยังผ่านการตรวจทั้งที่สิ่งที่เห็นตอนต้นเปลี่ยนแล้ว จึงควรมีเจ้าของกติกาและตรวจทุกทางที่แก้ส่วนที่คุ้มครอง
ในทางกลับกัน หากให้ทุกการเปลี่ยนเล็กน้อยไปเพิ่ม version เดียวกันทั้งหมด ก็อาจเกิดความขัดแย้งบ่อยกว่าที่ผู้ใช้จำเป็นต้องจัดการ การแบ่งขอบเขตควรอิงความสัมพันธ์ของข้อมูลและความเสี่ยง ไม่เพิ่มความละเอียดเพียงเพื่อให้ไม่เคยตอบ conflict เลย
หน้าจอควรอธิบายว่าข้อมูลถูกเปลี่ยนหลังจากที่เปิดมา พร้อมรักษาค่าที่คนกำลังแก้ไว้ ไม่รีเฟรชทั้งหน้าแล้วทำข้อความที่กรอกหาย วิธีแสดงฉบับปัจจุบันกับฉบับที่แก้อาจช่วยให้คนเห็นว่าจะเก็บส่วนใดหรือเริ่มจากข้อมูลล่าสุด
การรวมให้อัตโนมัติต้องมีเงื่อนไขชัด ข้อมูลที่เป็นข้อความคนละช่องอาจมีทางเทียบต่างจากการอนุมัติหรือเปลี่ยนสถานะ การให้คำขอเดิม retry พร้อมรหัสรุ่นใหม่ทันทีโดยไม่ทบทวน จะกลับไปทำการเขียนทับที่ตั้งใจป้องกันอยู่ดี
ยังต้องมีวิธีตรวจผลเมื่อคำตอบการบันทึกขาดกลางทาง เพราะการป้องกันรุ่นเก่าทับข้อมูลกับการจัดการคำสั่งเดิมส่งซ้ำเป็นคนละปัญหา ตัวอ้างอิงรุ่นช่วยตัดสินสภาพก่อนเขียน แต่ไม่ใช่บันทึกตัวตนของทุกคำสั่งแทนกลไก idempotency ทั้งหมด
ชุดตรวจควรมีสองผู้เรียกอ่านรุ่นเดียวกันแล้วส่งคำขอเหลื่อมกัน พร้อมตรวจทั้งผลในฐาน รหัสตอบกลับและข้อความที่ยังค้างในฟอร์ม รวมถึงเส้นทาง worker ที่เปลี่ยนข้อมูลระหว่างนั้น การเห็นหน้าแจ้ง conflict อย่างเดียวไม่พอ หากคำสั่งเขียนได้เปลี่ยนข้อมูลไปก่อนตอบแล้ว
การป้องกันเซฟทับเริ่มจากพารุ่นที่อ่านกลับมากับคำขอ แล้วตรวจเงื่อนไขพร้อมการเขียนในขอบเขตที่นิยามไว้ เมื่อทุกเส้นทางรักษารุ่นตรงกันและหน้าจอช่วยจัดการ conflict โดยเก็บงานที่กรอก เราจะให้คนแก้ข้อมูลร่วมกันได้มีหลักขึ้น บทนี้เป็นรูปแบบออกแบบกับ query สมมติ ยังไม่ได้เปลี่ยน API หรือทดสอบการเขียนพร้อมกันในฐานจริง