30 เม.ย. 2568·อ่าน 2 นาที

ซิงค์พื้นหลังแบบ offline-first สำหรับแอปมือถือ: ข้อขัดแย้ง การลองใหม่ และ UX

วางแผนการซิงค์เบื้องหลังแบบ offline-first สำหรับแอปมือถือด้วยกฎจัดการข้อขัดแย้ง ตรรกะการลองใหม่ และ UX รายการรอดำเนินการที่เรียบง่ายสำหรับแอปเนทีฟ Kotlin และ SwiftUI

ซิงค์พื้นหลังแบบ offline-first สำหรับแอปมือถือ: ข้อขัดแย้ง การลองใหม่ และ UX

ปัญหา: ผู้ใช้แก้ไขแบบออฟไลน์และโลกเปลี่ยนแปลง\n\nมีคนเริ่มงานขณะที่สัญญาณดี แล้วเดินเข้าไปในลิฟต์ มุมโกดัง หรืออุโมงค์รถไฟใต้ดิน แอปยังทำงานอยู่ ดังนั้นพวกเขาก็ยังทำงานต่อ พวกเขากดบันทึก เพิ่มโน้ต เปลี่ยนสถานะ หรืออาจสร้างระเบียนใหม่ ทุกอย่างดูเรียบร้อยเพราะหน้าจออัปเดตทันที\n\nต่อมาเมื่อสัญญาณกลับมา แอปพยายามซิงค์เบื้องหลัง นั่นแหละที่การซิงค์เบื้องหลังทำให้ผู้ใช้ประหลาดใจได้\n\nถ้าแอปไม่ระมัดระวัง การกระทำเดียวกันอาจถูกส่งสองครั้ง (ซ้ำ) หรือการเปลี่ยนแปลงล่าสุดจากเซิร์ฟเวอร์มาเขียนทับสิ่งที่ผู้ใช้เพิ่งทำ (การแก้ไขหาย) บางครั้งแอปแสดงสถานะสับสนเช่น “บันทึกแล้ว” และ “ยังไม่ได้บันทึก” พร้อมกัน หรือระเบียนปรากฏ หาย แล้วปรากฏอีกหลังซิงค์\n\nข้อขัดแย้งเกิดขึ้นง่ายๆ: มีการเปลี่ยนสองแบบบนสิ่งเดียวกันก่อนที่แอปจะมีโอกาสประสาน ตัวอย่างเช่น เจ้าหน้าที่ซัพพอร์ตเปลี่ยนความสำคัญของตั๋วเป็นสูงขณะออฟไลน์ แต่เพื่อนร่วมทีมที่ออนไลน์ปิดตั๋วนั้นไป เมื่อโทรศัพท์ออฟไลน์เชื่อมต่อใหม่ ทั้งสองการเปลี่ยนไม่สามารถใช้ร่วมกันได้โดยไม่มีกฎ\n\nเป้าหมายไม่ใช่ทำให้ออฟไลน์สมบูรณ์แบบ แต่ทำให้คาดเดาได้:\n\n- คนสามารถทำงานต่อโดยไม่กลัวว่าจะสูญเสียงาน\n- การซิงค์เกิดขึ้นทีหลังโดยไม่มีการสร้างซ้ำที่ลึกลับ\n- เมื่อมีสิ่งที่ต้องสนใจ แอปต้องบอกอย่างชัดเจนว่าเกิดอะไรขึ้นและต้องทำอย่างไรต่อ\n\nสิ่งข้างต้นเป็นจริงไม่ว่าคุณจะเขียนด้วยมือใน Kotlin/SwiftUI หรือสร้างแอปเนทีฟด้วยแพลตฟอร์มไม่ต้องเขียนโค้ดอย่าง AppMaster สิ่งที่ยากไม่ใช่วิดเจ็ต UI แต่คือการตัดสินใจว่าแอปจะพฤติกรรมอย่างไรเมื่อโลกเปลี่ยนขณะผู้ใช้ออฟไลน์\n\n## โมเดลง่ายๆ แบบออฟไลน์เป็นหลัก (ไม่ใช้คำศัพท์เทคนิคมาก)\n\nแอปแบบออฟไลน์เป็นหลักถือว่าโทรศัพท์จะหลุดเครือข่ายเป็นครั้งคราว แต่แอปยังควรรู้สึกใช้งานได้ หน้าจอควรโหลดและปุ่มควรทำงานแม้จะเชื่อมต่อเซิร์ฟเวอร์ไม่ได้\n\nคำศัพท์สี่คำครอบคลุมส่วนใหญ่:\n\n- แคชในเครื่อง: ข้อมูลเก็บบนอุปกรณ์เพื่อให้แอปแสดงผลได้ทันที\n- คิวการซิงค์: รายการการกระทำที่ผู้ใช้ทำขณะออฟไลน์ (หรือขณะเครือข่ายไม่เสถียร)\n- ข้อมูลบนเซิร์ฟเวอร์: เวอร์ชันที่เก็บบนแบ็กเอนด์ซึ่งทุกคนแชร์ในที่สุด\n- ข้อขัดแย้ง: เมื่อการเปลี่ยนแปลงที่อยู่ในคิวของผู้ใช้ไม่สามารถใช้ได้อย่างสะอาดเพราะเวอร์ชันบนเซิร์ฟเวอร์เปลี่ยนไป\n\nแบบจำลองที่เป็นประโยชน์คือแยกการอ่านออกจากการเขียน\n\nการอ่านมักตรงไปตรงมา: แสดงข้อมูลให้ดีที่สุดที่มี (มักจากแคชในเครื่อง) แล้วรีเฟรชอย่างเงียบเมื่อเครือข่ายกลับมา\n\nการเขียนต่างออกไป อย่าเก็บความหวังไว้กับ “การบันทึกทั้งระเบียนทีเดียว” วิธีนี้พังทันทีเมื่อออฟไลน์\n\nแทนที่จะเป็นเช่นนั้น ให้บันทึกสิ่งที่ผู้ใช้ทำเป็นรายการเล็กๆ ในบันทึกการเปลี่ยนแปลง เช่น: “ตั้งสถานะเป็น Approved”, “เพิ่มคอมเมนต์ X”, “เปลี่ยนปริมาณจาก 2 เป็น 3” แต่ละรายการเข้าไปในคิวการซิงค์พร้อมเวลาและ ID แล้วซิงค์เบื้องหลังจะพยายามส่งมัน\n\nผู้ใช้ยังทำงานต่อได้ขณะที่การเปลี่ยนแปลงย้ายจากสถานะรอดำเนินการไปเป็นซิงค์แล้ว\n\nถ้าคุณใช้แพลตฟอร์มไม่ต้องเขียนโค้ดอย่าง AppMaster คุณยังต้องการบล็อกการสร้างแบบเดียวกัน: อ่านจากแคชเพื่อหน้าจอที่เร็ว และคิวการกระทำของผู้ใช้ที่ชัดเจนซึ่งสามารถลองใหม่ ผสาน หรือทำเครื่องหมายเมื่อเกิดข้อขัดแย้ง\n\n## ตัดสินใจว่าอะไรที่ต้องรองรับออฟไลน์จริงๆ\n\nคำว่าออฟไลน์เป็นหลักอาจฟังดูเหมือน “ทุกอย่างทำงานได้โดยไม่ต้องเชื่อมต่อ” แต่คำสัญญานั้นทำให้อีกหลายแอปเจอปัญหา เลือกส่วนที่ได้ประโยชน์จากออฟไลน์จริงๆ และเก็บส่วนที่เหลือให้ชัดเจนว่าออนไลน์เท่านั้น\n\nคิดจากเจตนาของผู้ใช้: คนต้องทำอะไรในชั้นใต้ดิน บนเครื่องบิน หรือในโกดังที่สัญญาณไม่เสถียร? ค่าเริ่มต้นที่ดีคือรองรับการกระทำที่สร้างหรืออัปเดตงานประจำวัน และบล็อกการกระทำที่ต้องการ “ความจริงล่าสุด”\n\nชุดการกระทำที่เป็นมิตรต่อออฟไลน์บ่อยครั้งรวมถึงการสร้างและแก้ไขระเบียนหลัก (โน้ต งาน ตรวจสอบ ตั๋ว), ร่างคอมเมนต์, และแนบรูปถ่าย (เก็บไว้ในเครื่อง อัปโหลดทีหลัง) การลบทำได้เช่นกัน แต่ปลอดภัยกว่าเมื่อทำเป็น soft delete พร้อมหน้าต่างยกเลิกจนกว่าเซิร์ฟเวอร์ยืนยัน\n\nจากนั้นตัดสินใจว่าควรออนไลน์จริงๆ เพราะความเสี่ยงสูง เช่น การชำระเงิน การเปลี่ยนสิทธิ์ การอนุมัติ และข้อมูลที่อ่อนไหว โดยปกติควรต้องเชื่อมต่อ ถ้าผู้ใช้ไม่สามารถแน่ใจว่าการกระทำถูกต้องโดยไม่ตรวจสอบเซิร์ฟเวอร์ อย่าอนุญาตให้ทำแบบออฟไลน์ แสดงข้อความชัดเจนว่า “ต้องเชื่อมต่อ” แทนที่จะเป็นข้อผิดพลาดลึกลับ\n\nกำหนดความสดใหม่ของข้อมูลแบบชัดเจน “ออฟไลน์” ไม่ใช่ค่าทวิภาค กำหนดได้ว่าข้อมูลเก่าถึงระดับไหนได้: นาที ชั่วโมง หรือ “ครั้งถัดไปที่เปิดแอป” ใส่กฎนั้นใน UI ด้วยคำง่ายๆ เช่น “อัพเดตล่าสุด 2 ชั่วโมงที่แล้ว” และ “กำลังซิงค์เมื่อออนไลน์”\n\nสุดท้าย ทำเครื่องหมายข้อมูลที่มีความขัดแย้งสูงตั้งแต่ต้น นับสินค้าคงคลัง งานที่แชร์ และข้อความทีมเป็นจุดที่มักเกิดข้อขัดแย้งเพราะหลายคนแก้ไขเร็ว สำหรับข้อมูลพวกนี้ให้พิจารณาจำกัดการแก้ไขแบบออฟไลน์ไว้เป็นร่าง หรือลงทะเบียนการเปลี่ยนแปลงเป็นเหตุการณ์แยกแทนการเขียนทับค่าตัวเดียว\n\nถ้าคุณสร้างใน AppMaster ขั้นตอนการตัดสินใจนี้ช่วยคุณโมเดลข้อมูลและกฎธุรกิจเพื่อให้แอปเก็บร่างที่ปลอดภัยแบบออฟไลน์ในขณะที่บังคับให้การกระทำที่มีความเสี่ยงต้องออนไลน์\n\n## ออกแบบคิวการซิงค์: เก็บอะไรสำหรับแต่ละการเปลี่ยนแปลง\n\nเมื่อผู้ใช้ทำงานออฟไลน์ อย่าพยายาม “ซิงค์ฐานข้อมูลทั้งก้อน” ให้ซิงค์การกระทำของผู้ใช้ คิวการกระทำที่ชัดเจนคือกระดูกสันหลังของการซิงค์เบื้องหลัง และมันยังเข้าใจได้เมื่อเกิดปัญหา\n\nเก็บการกระทำให้เล็กและสื่อถึงสิ่งที่ผู้ใช้ทำจริง:\n\n- สร้างระเบียน\n- อัปเดตฟิลด์เฉพาะ\n- เปลี่ยนสถานะ (ยื่น ส่ง อนุมัติ จัดเก็บ)\n- ลบ (ควรเป็น soft delete จนกว่าจะยืนยัน)\n\nการกระทำเล็กๆ แก้ไขง่ายกว่า ถ้าฝ่ายซัพพอร์ตต้องช่วยผู้ใช้ อ่านว่า “เปลี่ยนสถานะ Draft -> Submitted” ง่ายกว่าตรวจสอบบล็อบ JSON ขนาดใหญ่\n\nสำหรับการกระทำที่อยู่ในคิวแต่ละรายการ ให้เก็บเมตาดาต้าที่เพียงพอเพื่อเล่นซ้ำอย่างปลอดภัยและตรวจจับข้อขัดแย้ง:\n\n- ตัวระบุระเบียน (และ ID ชั่วคราวในเครื่องสำหรับระเบียนใหม่)\n- เวลาการกระทำและตัวระบุอุปกรณ์\n- เวอร์ชันที่คาดหวัง (หรือเวลาที่อัปเดตล่าสุดที่รู้) ของระเบียน\n- เพย์โหลด (ฟิลด์ที่เปลี่ยนและค่าก่อนหน้าถ้าทำได้)\n- คีย์ป้องกันการทำซ้ำ (idempotency key)\n\nเวอร์ชันที่คาดหวังนี่แหละคือกุญแจสำหรับการจัดการข้อขัดแย้งอย่างตรงไปตรงมา หากเวอร์ชันบนเซิร์ฟเวอร์เปลี่ยนไป คุณสามารถหยุดและขอคำตัดสินแทนการเขียนทับคนอื่นแบบเงียบๆ\n\nบางการกระทำต้องถูกใช้ร่วมกันเพราะผู้ใช้เห็นเป็นก้าวเดียวกัน เช่น “สร้างคำสั่งซื้อ” บวก “เพิ่มรายการสินค้า 3 รายการ” ควรสำเร็จหรือไม่สำเร็จเป็นหน่วยเดียว เก็บ group ID (หรือ transaction ID) เพื่อให้เครื่องยนต์ซิงค์ส่งพวกมันพร้อมกันและยืนยันทั้งหมดหรือคงไว้เป็น pending ทั้งหมด\n\nไม่ว่าคุณจะสร้างเองหรือใน AppMaster เป้าหมายเหมือนกัน: ทุกการเปลี่ยนถูกบันทึกครั้งเดียว เล่นซ้ำอย่างปลอดภัย และอธิบายได้เมื่อมีบางอย่างไม่ตรงกัน\n\n## กฎการแก้ข้อขัดแย้งที่อธิบายให้ผู้ใช้เข้าใจได้\n\nข้อขัดแย้งเป็นเรื่องปกติ เป้าหมายไม่ใช่ทำให้เป็นไปไม่ได้ แต่ทำให้มันหายาก ปลอดภัย และอธิบายง่ายเมื่อเกิดขึ้น\n\nตั้งชื่อช่วงเวลาที่เกิดข้อขัดแย้ง: แอปส่งการเปลี่ยนแปลง แล้วเซิร์ฟเวอร์ตอบว่า “ระเบียนนี้ไม่ใช่เวอร์ชันที่คุณเริ่มแก้ไข” นี่แหละเหตุผลที่การเวอร์ชันสำคัญ\n\nเก็บค่าสองค่าไว้กับแต่ละระเบียน:\n\n- เวอร์ชันบนเซิร์ฟเวอร์ (Server version)\n- เวอร์ชันที่คาดหวัง (Expected version) ที่โทรศัพท์คิดว่าแก้ไขอยู่\n\nถ้าเวอร์ชันที่คาดหวังตรงกัน ยอมรับการอัปเดตและเพิ่มเวอร์ชันบนเซิร์ฟเวอร์ หากไม่ตรง ให้ใช้กฎข้อขัดแย้งของคุณ\n\n### เลือกกฎตามประเภทข้อมูล (ไม่ใช่กฎเดียวใช้กับทุกอย่าง)\n\nข้อมูลต่างชนิดต้องการกฎต่างกัน ฟิลด์สถานะไม่เหมือนกับโน้ตยาว ๆ\n\nกฎที่ผู้ใช้มักเข้าใจได้:\n\n- เขียนล่าสุดชนะ: เหมาะกับฟิลด์ความเสี่ยงต่ำ เช่น การตั้งค่าการดู\n- ผสานฟิลด์: ดีเมื่อฟิลด์เป็นอิสระต่อกัน (สถานะ vs โน้ต)\n- ถามผู้ใช้: ดีสุดสำหรับการแก้ไขความเสี่ยงสูง เช่น ราคา สิทธิ์ หรือยอดรวม\n- เซิร์ฟเวอร์ชนะแต่เก็บสำเนา: เก็บค่าบนเซิร์ฟเวอร์ไว้ แต่บันทึกการแก้ของผู้ใช้เป็นร่างให้เขาสามารถนำกลับมาใช้ใหม่\n\nใน AppMaster กฎเหล่านี้แผนที่ได้ดีในตรรกะเชิงภาพ: ตรวจสอบเวอร์ชัน เปรียบเทียบฟิลด์ แล้วเลือกเส้นทาง\n\n### ตัดสินใจพฤติกรรมของการลบ (ไม่อย่างนั้นคุณจะเสียข้อมูล)\n\nการลบเป็นกรณีที่ซับซ้อน ใช้หลุมฝังศพ (tombstone — เครื่องหมาย “deleted”) แทนการลบระเบียนทันที แล้วตัดสินใจว่าจะเกิดอะไรขึ้นถ้ามีคนแก้ไขระเบียนที่ถูกลบที่อื่น\n\nกฎที่ชัดเจนตัวอย่าง: “การลบชนะ แต่คุณสามารถกู้คืนได้” เช่น: พนักงานขายแก้โน้ตลูกค้าขณะออฟไลน์ ขณะที่ผู้ดูแลระบบลบลูกค้านั้น เมื่อซิงค์แล้ว แอปจะแสดงว่า “ลูกค้าถูกลบ กู้คืนเพื่อใช้โน้ตของคุณ?” วิธีนี้หลีกเลี่ยงการสูญเสียแบบเงียบและให้ผู้ใช้ควบคุม\n\n## การลองใหม่และสถานะล้มเหลว: ทำให้น่าเชื่อถือ\n\nเมื่อซิงค์ล้มเหลว ผู้ใช้ส่วนใหญ่ไม่สนใจว่าทำไม พวกเขาสนใจว่างานของพวกเขาปลอดภัยไหมและจะเกิดอะไรต่อไป ชุดสถานะที่คาดเดาได้ช่วยป้องกันความตื่นตระหนกและตั๋วซัพพอร์ต\n\nเริ่มด้วยโมเดลสถานะเล็กๆ ที่มองเห็นได้และคงที่ในทุกหน้าจอ:\n\n- Queued: เก็บบนอุปกรณ์ รอเครือข่าย\n- Syncing: กำลังส่งตอนนี้\n- Sent: ยืนยันโดยเซิร์ฟเวอร์แล้ว\n- Failed: ส่งไม่ได้ จะลองใหม่หรือรอการแก้ไข\n- Needs review: ส่งแล้วแต่เซิร์ฟเวอร์ปฏิเสธหรือทำเครื่องหมายไว้\n\nการลองใหม่ควรประหยัดแบตและข้อมูล เริ่มด้วยการลองเร็วๆ ตอนแรก (เพื่อจัดการการหลุดสั้นๆ) แล้วค่อยช้าลง เช่น backoff แบบง่าย 1 นาที, 5 นาที, 15 นาที แล้วเป็นรายชั่วโมง การคิดแบบนี้เข้าใจง่าย และลองใหม่เฉพาะเมื่อมีเหตุผล (อย่าพยายามซ้ำกับการเปลี่ยนที่ไม่ถูกต้อง)

\nปฏิบัติต่อข้อผิดพลาดต่างกัน เพราะการกระทำถัดไปต่างกัน:\n\n- ออฟไลน์ / ไม่มีเครือข่าย: คงไว้ในคิว รอลองใหม่เมื่อออนไลน์\n- หมดเวลา / เซิร์ฟเวอร์ไม่พร้อมใช้งาน: ทำเครื่องหมายล้มเหลว ลองใหม่อัตโนมัติด้วย backoff\n- การตรวจสอบสิทธิ์หมดอายุ: หยุดการซิงค์และขอให้ผู้ใช้ลงชื่อเข้าใช้อีกครั้ง\n- การตรวจสอบความถูกต้องล้มเหลว (ข้อมูลไม่ถูกต้อง): ต้องรอการตรวจสอบ แจ้งให้แก้ไข\n- ข้อขัดแย้ง (ระเบียนเปลี่ยน): ต้องรอการตรวจสอบ และใช้กฎข้อขัดแย้งของคุณ\n\nidempotency คือสิ่งที่ทำให้การลองใหม่ปลอดภัย ทุกการเปลี่ยนควรมี ID การกระทำที่ไม่ซ้ำ (มักเป็น UUID) ส่งมากับคำขอ ถ้าแอปส่งการกระทำเดียวกันซ้ำ เซิร์ฟเวอร์ควรจดจำ ID นั้นและคืนผลลัพธ์เดิมแทนการสร้างซ้ำ\n\nตัวอย่าง: ช่างบันทึกงานว่างานเสร็จแบบออฟไลน์ แล้วเข้าไปในลิฟต์ แอปส่งอัปเดตแล้วหมดเวลา จากนั้นลองใหม่ทีหลัง ด้วย action ID การส่งครั้งที่สองจะไม่เป็นอันตราย ถ้าไม่มีอาจสร้างเหตุการณ์ “เสร็จแล้ว” ซ้ำได้\n\nใน AppMaster ให้ปฏิบัติต่อสถานะและกฎเหล่านี้เป็นฟิลด์และตรรกะระดับหนึ่งในกระบวนการซิงค์ เพื่อให้แอป Kotlin และ SwiftUI ของคุณทำงานแบบเดียวกันทุกที่\n\n## UX สำหรับรายการรอดำเนินการ: ผู้ใช้เห็นและทำอะไรได้บ้าง\n\nคนควรรู้สึกปลอดภัยเมื่อใช้แอปออฟไลน์ UX รายการรอดำเนินการที่ดีจะสงบและคาดเดาได้: ยอมรับว่างานถูกเก็บไว้ในเครื่อง และทำให้ขั้นตอนถัดไปชัดเจน\n\nตัวบ่งชี้แบบอ่อนทำงานดีกว่าแบนเนอร์เตือน เช่น แสดงไอคอน “กำลังซิงค์” เล็กๆ ในหัว หรือป้ายเงียบๆ ว่า “3 รายการรอดำเนินการ” บนหน้าที่แก้ไข ใช้สีเตือนเฉพาะเมื่อเกิดอันตราจริง (เช่น “อัปโหลดไม่ได้เพราะคุณออกจากระบบ”)\n\nให้ผู้ใช้มีที่เดียวที่เข้าใจว่าเกิดอะไรขึ้น หน้ากล่องส่งหรือหน้ารายการรอดำเนินการที่เรียบง่ายสามารถแสดงรายการพร้อมข้อความธรรมดา เช่น “เพิ่มคอมเมนต์ใน Ticket 104” หรือ “อัปเดตรูปโปรไฟล์” ความโปร่งใสนี้ลดความตื่นตระหนกและลดตั๋วซัพพอร์ต\n\n### ผู้ใช้ทำอะไรได้บ้าง\n\nผู้ใช้ส่วนใหญ่ต้องการการกระทำไม่กี่อย่าง และควรสอดคล้องทั่วแอป:\n\n- ลองใหม่ตอนนี้\n- แก้ไขอีกครั้ง (จะสร้างการเปลี่ยนใหม่)\n- ทิ้งการเปลี่ยนแปลงในเครื่อง\n- คัดลอกรายละเอียด (มีประโยชน์เวลารายงานปัญหา)\n\nเก็บป้ายสถานะให้เรียบง่าย: Pending, Syncing, Failed เมื่อบางอย่างล้มเหลว อธิบายด้วยภาษาที่เข้าใจง่าย: “อัปโหลดไม่ได้ ไม่มีอินเทอร์เน็ต” หรือ “ปฏิเสธเพราะระเบียนนี้ถูกแก้ไขโดยคนอื่น” หลีกเลี่ยงรหัสข้อผิดพลาด\n\n### อย่าบล็อกทั้งแอป\n\nบล็อกเฉพาะการกระทำที่ต้องออนไลน์จริงๆ เช่น “จ่ายด้วย Stripe” หรือ “เชิญผู้ใช้ใหม่” ทุกอย่างอื่นควรยังทำงานได้ รวมถึงดูข้อมูลล่าสุดและสร้างร่างใหม่\n\nโฟลว์ที่สมจริง: ช่างสนามแก้รายงานงานในชั้นใต้ดิน แอปแสดง “1 รายการรอดำเนินการ” และให้เขาทำงานต่อ ต่อมาเปลี่ยนเป็น “กำลังซิงค์” แล้วล้างโดยอัตโนมัติ ถ้าล้มเหลว รายงานยังคงเข้าถึงได้ มีป้ายว่า “ล้มเหลว” พร้อมปุ่ม “ลองใหม่ตอนนี้” เดียว\n\nถ้าคุณสร้างใน AppMaster ให้โมเดลสถานะเหล่านี้เป็นส่วนหนึ่งของแต่ละระเบียน (pending, failed, synced) เพื่อให้ UI แสดงได้ทุกที่โดยไม่ต้องทำกรณีพิเศษ\n\n## การยืนยันตัวตน สิทธิ์ และความปลอดภัยขณะออฟไลน์\n\nโหมดออฟไลน์เปลี่ยนโมเดลความปลอดภัย ผู้ใช้สามารถกระทำการเมื่อไม่มีการเชื่อมต่อ แต่เซิร์ฟเวอร์ยังคงเป็นแหล่งความจริง ให้ถือทุกรายการในคิวว่าเป็น “คำขอ” ไม่ใช่ “ได้รับอนุมัติ”\n\n### การหมดอายุการล็อกอินขณะออฟไลน์\n\nโทเค็นหมดอายุ เมื่อเกิดขึ้นขณะออฟไลน์ ให้ผู้ใช้ยังสร้างการแก้ไขต่อและเก็บไว้เป็นรอดำเนินการ อย่าแสดงว่าการกระทำที่ต้องยืนยันจากเซิร์ฟเวอร์เสร็จสิ้น ให้แสดงสถานะรอดำเนินการจนกว่าจะมีการรีเฟรชการยืนยันตัวตนสำเร็จ\n\nเมื่อออนไลน์อีกครั้ง ให้พยายามรีเฟรชแบบเงียบก่อน ถ้าจำเป็นต้องให้ผู้ใช้ลงชื่อเข้าใหม่ ให้ถามครั้งเดียว แล้วดำเนินการซิงค์ต่อโดยอัตโนมัติ\n\nหลังลงชื่อเข้าใหม่ ให้ตรวจสอบแต่ละรายการในคิวก่อนส่ง ซ้ำว่าอัตลักษณ์ผู้ใช้อาจเปลี่ยนไป (อุปกรณ์ที่ใช้ร่วมกัน) และการแก้ไขเก่าไม่ควรถูกซิงค์ในบัญชีผิด\n\n### การเปลี่ยนสิทธิ์และการกระทำที่ถูกห้าม\n\nสิทธิ์อาจเปลี่ยนขณะผู้ใช้ออฟไลน์ การแก้ไขที่อนุญาตเมื่อวานอาจถูกห้ามวันนี้ จัดการเรื่องนี้อย่างชัดเจน:\n\n- ตรวจสอบสิทธิ์ที่ฝั่งเซิร์ฟเวอร์สำหรับแต่ละการกระทำในคิว\n- ถ้าถูกห้าม หยุดรายการนั้นและแสดงเหตุผลชัดเจน\n- เก็บการแก้ไขในเครื่องของผู้ใช้เพื่อให้เขาสามารถคัดลอกหรือร้องขอการเข้าถึง\n- หลีกเลี่ยงการลองซ้ำสำหรับข้อผิดพลาดแบบ “ถูกห้าม”\n\nตัวอย่าง: เจ้าหน้าที่ซัพพอร์ตแก้โน้ตลูกค้าออฟไลน์บนเที่ยวบิน ข้ามคืนบทบาทของเขาถูกถอด เมื่อซิงค์ เซิร์ฟเวอร์ตอบปฏิเสธ แอปควรแสดงว่า “ไม่สามารถอัปโหลด: คุณไม่มีสิทธิ์แล้ว” และเก็บโน้ตเป็นร่างท้องถิ่น\n\n### ข้อมูลอ่อนไหวที่เก็บในเครื่อง\n\nเก็บเฉพาะสิ่งที่จำเป็นเพื่อแสดงหน้าจอและเล่นซ้ำคิว เข้ารหัสที่เก็บข้อมูลในอุปกรณ์ หลีกเลี่ยงการแคชความลับ และตั้งกฎชัดเจนสำหรับการออกจากระบบ (เช่น: ลบข้อมูลท้องถิ่น หรือเก็บเฉพาะร่างหลังจากได้รับความยินยอมจากผู้ใช้) ถ้าคุณสร้างด้วย AppMaster เริ่มจากโมดูลการยืนยันตัวตนของมันและออกแบบคิวให้รอเซสชันที่ถูกต้องก่อนส่งการเปลี่ยนแปลง\n\n## กับดักทั่วไปที่ทำให้งานหายหรือเกิดระเบียนซ้ำ\n\nบั๊กออฟไลน์ส่วนใหญ่ไม่ใช่เรื่องซับซ้อน พวกมันมาจากการตัดสินใจเล็กๆ ไม่กี่ข้อที่ดูไม่เป็นไรเมื่อทดสอบด้วย Wi‑Fi สมบูรณ์ แล้วพังในสถานการณ์จริง\n\nปัญหาหนึ่งที่พบบ่อยคือการเขียนทับแบบเงียบ ๆ ถ้าแอปอัปโหลดเวอร์ชันเก่าและเซิร์ฟเวอร์ยอมรับโดยไม่ตรวจสอบ คุณอาจลบการแก้ไขใหม่ของคนอื่น ผู้ใช้ไม่รู้จนกว่าจะสาย ให้ซิงค์ด้วยหมายเลขเวอร์ชัน (หรือ timestamp updatedAt) และปฏิเสธการเขียนทับเมื่อเซิร์ฟเวอร์เคลื่อนที่ไปข้างหน้า ผู้ใช้จะได้ตัวเลือกชัดเจน\n\nกับดักอีกอย่างคือการพายุลองซ้ำ เมื่อโทรศัพท์ได้สัญญาณอ่อน แอปอาจทุบ backend ทุกสองสามวินาที ทำให้แบตหมดและเขียนซ้ำซ้อน การลองใหม่ควรสงบ: ช้าลงหลังจากล้มเหลวแต่ละครั้งและใส่ความสุ่มเล็กน้อยเพื่อไม่ให้หลายพันอุปกรณ์ลองพร้อมกัน\n\nข้อผิดพลาดที่มักนำไปสู่การสูญเสียงานหรือซ้ำ:\n\n- ถือว่าทุกความล้มเหลวเป็น “เครือข่าย”: แยกข้อผิดพลาดถาวร (ข้อมูลไม่ถูกต้อง สิทธิ์ขาด) ออกจากชั่วคราว (timeout)\n- ซ่อนความล้มเหลวของการซิงค์: ถ้าคนมองไม่เห็นสิ่งที่ล้มเหลว เขาจะทำซ้ำงานและสร้างระเบียนสองอัน\n- ส่งการเปลี่ยนเดียวกันสองครั้งโดยไม่มีการป้องกัน: แนบ request ID ไม่ซ้ำเสมอเพื่อให้เซิร์ฟเวอร์ละเว้นซ้ำ\n- ผสานฟิลด์ข้อความอัตโนมัติโดยไม่บอกใคร: ถ้าคุณรวมการแก้ไขโดยอัตโนมัติ ให้ผู้ใช้ตรวจผลเมื่อสำคัญ\n- สร้างระเบียนออฟไลน์โดยไม่มี ID ที่เสถียร: ใช้ ID ชั่วคราวในเครื่องและแมปกับ ID เซิร์ฟเวอร์หลังอัปโหลด เพื่อให้การแก้ไขภายหลังไม่สร้างสำเนาที่สอง\n\nตัวอย่างอย่างเร็ว: ช่างสนามสร้าง “Site Visit” ใหม่ออฟไลน์ แล้วแก้อีกสองครั้งก่อนเชื่อมต่อ ถ้าเรียกสร้างถูกลองซ้ำและสร้างระเบียนบนเซิร์ฟเวอร์สองอัน การแก้ไขหลังๆ อาจผูกกับของผิดได้ ID เสถียรและการป้องกันการทำซ้ำฝั่งเซิร์ฟเวอร์ป้องกันเรื่องนี้\n\nถ้าคุณสร้างด้วย AppMaster กฎไม่เปลี่ยน ความต่างคือที่ที่คุณทำมัน: ในตรรกะการซิงค์ โมเดลข้อมูล และหน้าจอที่แสดงการเปลี่ยนแปลง “ล้มเหลว” เทียบกับ “ส่งแล้ว”\n\n## สถานการณ์ตัวอย่าง: สองคนแก้ไขระเบียนเดียวกัน\n\nช่างสนาม Maya กำลังอัปเดตตั๋ว “Job #1842” ในชั้นใต้ดินที่ไม่มีสัญญาณ เธอเปลี่ยนสถานะจาก “In progress” เป็น “Completed” และเพิ่มโน้ตว่า “เปลี่ยนวาล์ว ทดสอบเรียบร้อย” แอปบันทึกทันทีและแสดงเป็นรอดำเนินการ\n\nข้างบน Leo เพื่อนร่วมทีมของเธอออนไลน์และแก้ไขงานเดียวกันพร้อมๆ กัน เขาเปลี่ยนเวลานัดหมายและมอบหมายงานให้ช่างคนอื่นเพราะลูกค้าโทรมาอัปเดต\n\nเมื่อ Maya ได้สัญญาณอีกครั้ง การซิงค์เบื้องหลังเริ่มอย่างเงียบ ๆ นี่คือสิ่งที่จะเกิดขึ้นในโฟลว์ที่คาดเดาได้และเป็นมิตรต่อผู้ใช้:\n\n1) การเปลี่ยนของ Maya ยังคงอยู่ในคิวการซิงค์ (ID งาน ฟิลด์ที่เปลี่ยน เวลา และเวอร์ชันที่เธอเห็นล่าสุด)\n2) แอปพยายามอัปโหลด เซิร์ฟเวอร์ตอบว่า: “งานนี้ถูกอัปเดตตั้งแต่เวอร์ชันที่คุณเห็น” (เกิดข้อขัดแย้ง)\n3) กฎข้อขัดแย้งของคุณทำงาน: สถานะและโน้ตสามารถผสาน แต่การเปลี่ยนการมอบหมายให้ชนะถ้ามันเกิดหลังบนเซิร์ฟเวอร์\n4) เซิร์ฟเวอร์ยอมรับผลลัพธ์แบบผสาน: status = “Completed” (จาก Maya), โน้ตถูกเพิ่ม (จาก Maya), ผู้รับมอบหมาย = การเลือกของ Leo (จาก Leo)\n5) งานกลับมาในแอปของ Maya พร้อมแบนเนอร์ชัดเจน: “ซิงค์แล้วพร้อมอัปเดต การมอบหมายเปลี่ยนขณะคุณออฟไลน์” ปุ่มเล็กๆ “ทบทวน” แสดงการเปลี่ยนที่เกิดขึ้น\n\nถ้าเพิ่มช่วงล้มเหลว: โทเค็นล็อกอินของ Maya หมดอายุขณะออฟไลน์ ความพยายามซิงค์ครั้งแรกล้มด้วย “ต้องลงชื่อเข้าใช้” แอปเก็บการแก้ไขของเธอ ทำเครื่องหมายเป็น “หยุดชั่วคราว” และโชว์พรอมต์เดียวง่ายๆ หลังจากเธอลงชื่อเข้าใหม่ การซิงค์จะกลับมาทำงานอัตโนมัติโดยไม่ต้องพิมพ์อะไรใหม่\n\nถ้ามีปัญหาการตรวจสอบความถูกต้อง (เช่น สถานะ “Completed” ต้องมีรูปถ่าย) แอปไม่ควรเดา มันทำเครื่องหมายรายการเป็น “ต้องแก้ไข” บอกอย่างชัดเจนว่าจะเพิ่มอะไร แล้วให้เธอส่งใหม่\n\nแพลตฟอร์มอย่าง AppMaster ช่วยได้เพราะคุณออกแบบคิว กฎข้อขัดแย้ง และ UX สถานะรอดำเนินการแบบเชิงภาพ ในขณะที่ยังส่งแอปเนทีฟ Kotlin และ SwiftUI จริงๆ ได้\n\n## เช็คลิสต์ด่วนและขั้นตอนต่อไป\n\nปฏิบัติต่อการซิงค์ออฟไลน์เหมือนฟีเจอร์ตั้งแต่ต้นถึงปลายที่ต้องทดสอบ ไม่ใช่กองของบั๊ก เป้าหมายเรียบง่าย: ผู้ใช้ไม่สงสัยว่างานของพวกเขาถูกเก็บไหม และแอปไม่สร้างระเบียนซ้ำโดยไม่คาดคิด\n\nเช็คลิสต์สั้นๆ เพื่อยืนยันพื้นฐานแน่น:\n\n- คิวการซิงค์เก็บบนอุปกรณ์ และแต่ละการเปลี่ยนมี ID ท้องถิ่นที่เสถียรพร้อม ID เซิร์ฟเวอร์เมื่อต้องได้\n- มีสถานะชัดเจน (queued, syncing, sent, failed, needs review) และใช้สอดคล้องกัน\n- คำขอเป็น idempotent (ปลอดภัยที่จะลองใหม่) และแต่ละการปฏิบัติรวมคีย์ป้องกันการทำซ้ำ\n- ระเบียนมีการเวอร์ชัน (updatedAt, หมายเลข revision, หรือ ETag) เพื่อให้ตรวจจับข้อขัดแย้งได้\n- กฎข้อขัดแย้งเขียนเป็นภาษาง่าย (อะไรชนะ อะไรผสาน เมื่อไหร่ให้ถามผู้ใช้)\n\nเมื่อสิ่งเหล่านี้เรียบร้อย ให้ตรวจสอบประสบการณ์ใช้งานว่าทัดเทียมกับโมเดลข้อมูล ผู้ใช้ควรเห็นสิ่งที่รอดำเนินการ เข้าใจว่าพังอะไร และทำอะไรได้โดยไม่กลัวสูญงาน\n\nทดสอบด้วยสถานการณ์ที่เหมือนชีวิตจริง:\n\n- แก้ไขในโหมดเครื่องบิน: สร้าง อัปเดต ลบ แล้วเชื่อมต่ออีกครั้ง\n- เครือข่ายไม่เสถียร: หลุดกลางทางขณะซิงค์ และยืนยันว่าการลองใหม่ไม่ทำซ้ำ\n- ปิดแอปตอนส่ง: บังคับปิดระหว่างส่ง เปิดใหม่ ยืนยันคิวกู้คืนได้\n- เวลาเครื่องเพี้ยน: เวลาของอุปกรณ์ผิด ตรวจสอบว่าการตรวจจับข้อขัดแย้งยังทำงาน\n- กดซ้ำ: ผู้ใช้กดบันทึกสองครั้ง ยืนยันว่ากลายเป็นการเปลี่ยนเดียวบนเซิร์ฟเวอร์\n\nทำต้นแบบโฟลว์เต็มก่อนขัดเกลา UI สร้างหน้าจอเดียว ประเภทระเบียนเดียว และกรณีความขัดแย้งเดียว (สองคนแก้ฟิลด์เดียว) เพิ่มพื้นที่สถานะซิงค์ง่ายๆ ปุ่มลองใหม่สำหรับล้มเหลว และหน้าข้อขัดแย้งที่ชัดเจน เมื่อใช้งานได้ ให้ทำซ้ำกับหน้าจออื่น\n\nถ้าคุณสร้างโดยไม่เขียนโค้ด AppMaster (appmaster.io) สามารถสร้างแอป Kotlin และ SwiftUI เนทีฟพร้อม backend ให้คุณมุ่งที่คิว การตรวจสอบเวอร์ชัน และสถานะที่เห็นได้โดยผู้ใช้ แทนการเดินสายเชื่อมต่อทุกอย่างเอง\n

ง่ายต่อการเริ่มต้น
สร้างบางสิ่งที่ น่าทึ่ง

ทดลองกับ AppMaster ด้วยแผนฟรี
เมื่อคุณพร้อม คุณสามารถเลือกการสมัครที่เหมาะสมได้

เริ่ม
ซิงค์พื้นหลังแบบ offline-first สำหรับแอปมือถือ: ข้อขัดแย้ง การลองใหม่ และ UX | AppMaster