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

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

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

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

ปัญหา: ผู้ใช้แก้ไขแบบออฟไลน์และโลกเปลี่ยนแปลง

มีคนเริ่มงานขณะที่สัญญาณดี แล้วเดินเข้าไปในลิฟต์ มุมโกดัง หรืออุโมงค์รถไฟใต้ดิน แอปยังทำงานอยู่ ดังนั้นพวกเขาก็ยังทำงานต่อ พวกเขากดบันทึก เพิ่มโน้ต เปลี่ยนสถานะ หรืออาจสร้างระเบียนใหม่ ทุกอย่างดูเรียบร้อยเพราะหน้าจออัปเดตทันที

ต่อมาเมื่อสัญญาณกลับมา แอปพยายามซิงค์เบื้องหลัง นั่นแหละที่การซิงค์เบื้องหลังทำให้ผู้ใช้ประหลาดใจได้

ถ้าแอปไม่ระมัดระวัง การกระทำเดียวกันอาจถูกส่งสองครั้ง (ซ้ำ) หรือการเปลี่ยนแปลงล่าสุดจากเซิร์ฟเวอร์มาเขียนทับสิ่งที่ผู้ใช้เพิ่งทำ (การแก้ไขหาย) บางครั้งแอปแสดงสถานะสับสนเช่น “บันทึกแล้ว” และ “ยังไม่ได้บันทึก” พร้อมกัน หรือระเบียนปรากฏ หาย แล้วปรากฏอีกหลังซิงค์

ข้อขัดแย้งเกิดขึ้นง่ายๆ: มีการเปลี่ยนสองแบบบนสิ่งเดียวกันก่อนที่แอปจะมีโอกาสประสาน ตัวอย่างเช่น เจ้าหน้าที่ซัพพอร์ตเปลี่ยนความสำคัญของตั๋วเป็นสูงขณะออฟไลน์ แต่เพื่อนร่วมทีมที่ออนไลน์ปิดตั๋วนั้นไป เมื่อโทรศัพท์ออฟไลน์เชื่อมต่อใหม่ ทั้งสองการเปลี่ยนไม่สามารถใช้ร่วมกันได้โดยไม่มีกฎ

เป้าหมายไม่ใช่ทำให้ออฟไลน์สมบูรณ์แบบ แต่ทำให้คาดเดาได้:

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

สิ่งข้างต้นเป็นจริงไม่ว่าคุณจะเขียนด้วยมือใน Kotlin/SwiftUI หรือสร้างแอปเนทีฟด้วยแพลตฟอร์มไม่ต้องเขียนโค้ดอย่าง AppMaster สิ่งที่ยากไม่ใช่วิดเจ็ต UI แต่คือการตัดสินใจว่าแอปจะพฤติกรรมอย่างไรเมื่อโลกเปลี่ยนขณะผู้ใช้ออฟไลน์

โมเดลง่ายๆ แบบออฟไลน์เป็นหลัก (ไม่ใช้คำศัพท์เทคนิคมาก)

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

คำศัพท์สี่คำครอบคลุมส่วนใหญ่:

  • แคชในเครื่อง: ข้อมูลเก็บบนอุปกรณ์เพื่อให้แอปแสดงผลได้ทันที
  • คิวการซิงค์: รายการการกระทำที่ผู้ใช้ทำขณะออฟไลน์ (หรือขณะเครือข่ายไม่เสถียร)
  • ข้อมูลบนเซิร์ฟเวอร์: เวอร์ชันที่เก็บบนแบ็กเอนด์ซึ่งทุกคนแชร์ในที่สุด
  • ข้อขัดแย้ง: เมื่อการเปลี่ยนแปลงที่อยู่ในคิวของผู้ใช้ไม่สามารถใช้ได้อย่างสะอาดเพราะเวอร์ชันบนเซิร์ฟเวอร์เปลี่ยนไป

แบบจำลองที่เป็นประโยชน์คือแยกการอ่านออกจากการเขียน

การอ่านมักตรงไปตรงมา: แสดงข้อมูลให้ดีที่สุดที่มี (มักจากแคชในเครื่อง) แล้วรีเฟรชอย่างเงียบเมื่อเครือข่ายกลับมา

การเขียนต่างออกไป อย่าเก็บความหวังไว้กับ “การบันทึกทั้งระเบียนทีเดียว” วิธีนี้พังทันทีเมื่อออฟไลน์

แทนที่จะเป็นเช่นนั้น ให้บันทึกสิ่งที่ผู้ใช้ทำเป็นรายการเล็กๆ ในบันทึกการเปลี่ยนแปลง เช่น: “ตั้งสถานะเป็น Approved”, “เพิ่มคอมเมนต์ X”, “เปลี่ยนปริมาณจาก 2 เป็น 3” แต่ละรายการเข้าไปในคิวการซิงค์พร้อมเวลาและ ID แล้วซิงค์เบื้องหลังจะพยายามส่งมัน

ผู้ใช้ยังทำงานต่อได้ขณะที่การเปลี่ยนแปลงย้ายจากสถานะรอดำเนินการไปเป็นซิงค์แล้ว

ถ้าคุณใช้แพลตฟอร์มไม่ต้องเขียนโค้ดอย่าง AppMaster คุณยังต้องการบล็อกการสร้างแบบเดียวกัน: อ่านจากแคชเพื่อหน้าจอที่เร็ว และคิวการกระทำของผู้ใช้ที่ชัดเจนซึ่งสามารถลองใหม่ ผสาน หรือทำเครื่องหมายเมื่อเกิดข้อขัดแย้ง

ตัดสินใจว่าอะไรที่ต้องรองรับออฟไลน์จริงๆ

คำว่าออฟไลน์เป็นหลักอาจฟังดูเหมือน “ทุกอย่างทำงานได้โดยไม่ต้องเชื่อมต่อ” แต่คำสัญญานั้นทำให้อีกหลายแอปเจอปัญหา เลือกส่วนที่ได้ประโยชน์จากออฟไลน์จริงๆ และเก็บส่วนที่เหลือให้ชัดเจนว่าออนไลน์เท่านั้น

คิดจากเจตนาของผู้ใช้: คนต้องทำอะไรในชั้นใต้ดิน บนเครื่องบิน หรือในโกดังที่สัญญาณไม่เสถียร? ค่าเริ่มต้นที่ดีคือรองรับการกระทำที่สร้างหรืออัปเดตงานประจำวัน และบล็อกการกระทำที่ต้องการ “ความจริงล่าสุด”

ชุดการกระทำที่เป็นมิตรต่อออฟไลน์บ่อยครั้งรวมถึงการสร้างและแก้ไขระเบียนหลัก (โน้ต งาน ตรวจสอบ ตั๋ว), ร่างคอมเมนต์, และแนบรูปถ่าย (เก็บไว้ในเครื่อง อัปโหลดทีหลัง) การลบทำได้เช่นกัน แต่ปลอดภัยกว่าเมื่อทำเป็น soft delete พร้อมหน้าต่างยกเลิกจนกว่าเซิร์ฟเวอร์ยืนยัน

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

กำหนดความสดใหม่ของข้อมูลแบบชัดเจน “ออฟไลน์” ไม่ใช่ค่าทวิภาค กำหนดได้ว่าข้อมูลเก่าถึงระดับไหนได้: นาที ชั่วโมง หรือ “ครั้งถัดไปที่เปิดแอป” ใส่กฎนั้นใน UI ด้วยคำง่ายๆ เช่น “อัพเดตล่าสุด 2 ชั่วโมงที่แล้ว” และ “กำลังซิงค์เมื่อออนไลน์”

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

ถ้าคุณสร้างใน AppMaster ขั้นตอนการตัดสินใจนี้ช่วยคุณโมเดลข้อมูลและกฎธุรกิจเพื่อให้แอปเก็บร่างที่ปลอดภัยแบบออฟไลน์ในขณะที่บังคับให้การกระทำที่มีความเสี่ยงต้องออนไลน์

ออกแบบคิวการซิงค์: เก็บอะไรสำหรับแต่ละการเปลี่ยนแปลง

เมื่อผู้ใช้ทำงานออฟไลน์ อย่าพยายาม “ซิงค์ฐานข้อมูลทั้งก้อน” ให้ซิงค์การกระทำของผู้ใช้ คิวการกระทำที่ชัดเจนคือกระดูกสันหลังของการซิงค์เบื้องหลัง และมันยังเข้าใจได้เมื่อเกิดปัญหา

เก็บการกระทำให้เล็กและสื่อถึงสิ่งที่ผู้ใช้ทำจริง:

  • สร้างระเบียน
  • อัปเดตฟิลด์เฉพาะ
  • เปลี่ยนสถานะ (ยื่น ส่ง อนุมัติ จัดเก็บ)
  • ลบ (ควรเป็น soft delete จนกว่าจะยืนยัน)

การกระทำเล็กๆ แก้ไขง่ายกว่า ถ้าฝ่ายซัพพอร์ตต้องช่วยผู้ใช้ อ่านว่า “เปลี่ยนสถานะ Draft -> Submitted” ง่ายกว่าตรวจสอบบล็อบ JSON ขนาดใหญ่

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

  • ตัวระบุระเบียน (และ ID ชั่วคราวในเครื่องสำหรับระเบียนใหม่)
  • เวลาการกระทำและตัวระบุอุปกรณ์
  • เวอร์ชันที่คาดหวัง (หรือเวลาที่อัปเดตล่าสุดที่รู้) ของระเบียน
  • เพย์โหลด (ฟิลด์ที่เปลี่ยนและค่าก่อนหน้าถ้าทำได้)
  • คีย์ป้องกันการทำซ้ำ (idempotency key)

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

บางการกระทำต้องถูกใช้ร่วมกันเพราะผู้ใช้เห็นเป็นก้าวเดียวกัน เช่น “สร้างคำสั่งซื้อ” บวก “เพิ่มรายการสินค้า 3 รายการ” ควรสำเร็จหรือไม่สำเร็จเป็นหน่วยเดียว เก็บ group ID (หรือ transaction ID) เพื่อให้เครื่องยนต์ซิงค์ส่งพวกมันพร้อมกันและยืนยันทั้งหมดหรือคงไว้เป็น pending ทั้งหมด

ไม่ว่าคุณจะสร้างเองหรือใน AppMaster เป้าหมายเหมือนกัน: ทุกการเปลี่ยนถูกบันทึกครั้งเดียว เล่นซ้ำอย่างปลอดภัย และอธิบายได้เมื่อมีบางอย่างไม่ตรงกัน

กฎการแก้ข้อขัดแย้งที่อธิบายให้ผู้ใช้เข้าใจได้

ทำให้หน้าจอใช้งานได้แบบออฟไลน์
สร้างฟอร์มและหน้าจอที่ใช้งานได้เมื่อออฟไลน์
ลองเลย

ข้อขัดแย้งเป็นเรื่องปกติ เป้าหมายไม่ใช่ทำให้เป็นไปไม่ได้ แต่ทำให้มันหายาก ปลอดภัย และอธิบายง่ายเมื่อเกิดขึ้น

ตั้งชื่อช่วงเวลาที่เกิดข้อขัดแย้ง: แอปส่งการเปลี่ยนแปลง แล้วเซิร์ฟเวอร์ตอบว่า “ระเบียนนี้ไม่ใช่เวอร์ชันที่คุณเริ่มแก้ไข” นี่แหละเหตุผลที่การเวอร์ชันสำคัญ

เก็บค่าสองค่าไว้กับแต่ละระเบียน:

  • เวอร์ชันบนเซิร์ฟเวอร์ (Server version)
  • เวอร์ชันที่คาดหวัง (Expected version) ที่โทรศัพท์คิดว่าแก้ไขอยู่

ถ้าเวอร์ชันที่คาดหวังตรงกัน ยอมรับการอัปเดตและเพิ่มเวอร์ชันบนเซิร์ฟเวอร์ หากไม่ตรง ให้ใช้กฎข้อขัดแย้งของคุณ

เลือกกฎตามประเภทข้อมูล (ไม่ใช่กฎเดียวใช้กับทุกอย่าง)

ข้อมูลต่างชนิดต้องการกฎต่างกัน ฟิลด์สถานะไม่เหมือนกับโน้ตยาว ๆ

กฎที่ผู้ใช้มักเข้าใจได้:

  • เขียนล่าสุดชนะ: เหมาะกับฟิลด์ความเสี่ยงต่ำ เช่น การตั้งค่าการดู
  • ผสานฟิลด์: ดีเมื่อฟิลด์เป็นอิสระต่อกัน (สถานะ vs โน้ต)
  • ถามผู้ใช้: ดีสุดสำหรับการแก้ไขความเสี่ยงสูง เช่น ราคา สิทธิ์ หรือยอดรวม
  • เซิร์ฟเวอร์ชนะแต่เก็บสำเนา: เก็บค่าบนเซิร์ฟเวอร์ไว้ แต่บันทึกการแก้ของผู้ใช้เป็นร่างให้เขาสามารถนำกลับมาใช้ใหม่

ใน AppMaster กฎเหล่านี้แผนที่ได้ดีในตรรกะเชิงภาพ: ตรวจสอบเวอร์ชัน เปรียบเทียบฟิลด์ แล้วเลือกเส้นทาง

ตัดสินใจพฤติกรรมของการลบ (ไม่อย่างนั้นคุณจะเสียข้อมูล)

การลบเป็นกรณีที่ซับซ้อน ใช้หลุมฝังศพ (tombstone — เครื่องหมาย “deleted”) แทนการลบระเบียนทันที แล้วตัดสินใจว่าจะเกิดอะไรขึ้นถ้ามีคนแก้ไขระเบียนที่ถูกลบที่อื่น

กฎที่ชัดเจนตัวอย่าง: “การลบชนะ แต่คุณสามารถกู้คืนได้” เช่น: พนักงานขายแก้โน้ตลูกค้าขณะออฟไลน์ ขณะที่ผู้ดูแลระบบลบลูกค้านั้น เมื่อซิงค์แล้ว แอปจะแสดงว่า “ลูกค้าถูกลบ กู้คืนเพื่อใช้โน้ตของคุณ?” วิธีนี้หลีกเลี่ยงการสูญเสียแบบเงียบและให้ผู้ใช้ควบคุม

การลองใหม่และสถานะล้มเหลว: ทำให้น่าเชื่อถือ

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

เริ่มด้วยโมเดลสถานะเล็กๆ ที่มองเห็นได้และคงที่ในทุกหน้าจอ:

  • Queued: เก็บบนอุปกรณ์ รอเครือข่าย
  • Syncing: กำลังส่งตอนนี้
  • Sent: ยืนยันโดยเซิร์ฟเวอร์แล้ว
  • Failed: ส่งไม่ได้ จะลองใหม่หรือรอการแก้ไข
  • Needs review: ส่งแล้วแต่เซิร์ฟเวอร์ปฏิเสธหรือทำเครื่องหมายไว้

การลองใหม่ควรประหยัดแบตและข้อมูล เริ่มด้วยการลองเร็วๆ ตอนแรก (เพื่อจัดการการหลุดสั้นๆ) แล้วค่อยช้าลง เช่น backoff แบบง่าย 1 นาที, 5 นาที, 15 นาที แล้วเป็นรายชั่วโมง การคิดแบบนี้เข้าใจง่าย และลองใหม่เฉพาะเมื่อมีเหตุผล (อย่าพยายามซ้ำกับการเปลี่ยนที่ไม่ถูกต้อง)

ปฏิบัติต่อข้อผิดพลาดต่างกัน เพราะการกระทำถัดไปต่างกัน:

  • ออฟไลน์ / ไม่มีเครือข่าย: คงไว้ในคิว รอลองใหม่เมื่อออนไลน์
  • หมดเวลา / เซิร์ฟเวอร์ไม่พร้อมใช้งาน: ทำเครื่องหมายล้มเหลว ลองใหม่อัตโนมัติด้วย backoff
  • การตรวจสอบสิทธิ์หมดอายุ: หยุดการซิงค์และขอให้ผู้ใช้ลงชื่อเข้าใช้อีกครั้ง
  • การตรวจสอบความถูกต้องล้มเหลว (ข้อมูลไม่ถูกต้อง): ต้องรอการตรวจสอบ แจ้งให้แก้ไข
  • ข้อขัดแย้ง (ระเบียนเปลี่ยน): ต้องรอการตรวจสอบ และใช้กฎข้อขัดแย้งของคุณ

idempotency คือสิ่งที่ทำให้การลองใหม่ปลอดภัย ทุกการเปลี่ยนควรมี ID การกระทำที่ไม่ซ้ำ (มักเป็น UUID) ส่งมากับคำขอ ถ้าแอปส่งการกระทำเดียวกันซ้ำ เซิร์ฟเวอร์ควรจดจำ ID นั้นและคืนผลลัพธ์เดิมแทนการสร้างซ้ำ

ตัวอย่าง: ช่างบันทึกงานว่างานเสร็จแบบออฟไลน์ แล้วเข้าไปในลิฟต์ แอปส่งอัปเดตแล้วหมดเวลา จากนั้นลองใหม่ทีหลัง ด้วย action ID การส่งครั้งที่สองจะไม่เป็นอันตราย ถ้าไม่มีอาจสร้างเหตุการณ์ “เสร็จแล้ว” ซ้ำได้

ใน AppMaster ให้ปฏิบัติต่อสถานะและกฎเหล่านี้เป็นฟิลด์และตรรกะระดับหนึ่งในกระบวนการซิงค์ เพื่อให้แอป Kotlin และ SwiftUI ของคุณทำงานแบบเดียวกันทุกที่

UX สำหรับรายการรอดำเนินการ: ผู้ใช้เห็นและทำอะไรได้บ้าง

นำเช็คลิสต์ไปปฏิบัติ
เปลี่ยนเช็คลิสต์ในโพสต์นี้เป็นโฟลว์แอปที่ใช้งานได้ซึ่งคุณสาธิตได้ในไม่กี่ชั่วโมง
เริ่มตอนนี้

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

ตัวบ่งชี้แบบอ่อนทำงานดีกว่าแบนเนอร์เตือน เช่น แสดงไอคอน “กำลังซิงค์” เล็กๆ ในหัว หรือป้ายเงียบๆ ว่า “3 รายการรอดำเนินการ” บนหน้าที่แก้ไข ใช้สีเตือนเฉพาะเมื่อเกิดอันตราจริง (เช่น “อัปโหลดไม่ได้เพราะคุณออกจากระบบ”)

ให้ผู้ใช้มีที่เดียวที่เข้าใจว่าเกิดอะไรขึ้น หน้ากล่องส่งหรือหน้ารายการรอดำเนินการที่เรียบง่ายสามารถแสดงรายการพร้อมข้อความธรรมดา เช่น “เพิ่มคอมเมนต์ใน Ticket 104” หรือ “อัปเดตรูปโปรไฟล์” ความโปร่งใสนี้ลดความตื่นตระหนกและลดตั๋วซัพพอร์ต

ผู้ใช้ทำอะไรได้บ้าง

ผู้ใช้ส่วนใหญ่ต้องการการกระทำไม่กี่อย่าง และควรสอดคล้องทั่วแอป:

  • ลองใหม่ตอนนี้
  • แก้ไขอีกครั้ง (จะสร้างการเปลี่ยนใหม่)
  • ทิ้งการเปลี่ยนแปลงในเครื่อง
  • คัดลอกรายละเอียด (มีประโยชน์เวลารายงานปัญหา)

เก็บป้ายสถานะให้เรียบง่าย: Pending, Syncing, Failed เมื่อบางอย่างล้มเหลว อธิบายด้วยภาษาที่เข้าใจง่าย: “อัปโหลดไม่ได้ ไม่มีอินเทอร์เน็ต” หรือ “ปฏิเสธเพราะระเบียนนี้ถูกแก้ไขโดยคนอื่น” หลีกเลี่ยงรหัสข้อผิดพลาด

อย่าบล็อกทั้งแอป

บล็อกเฉพาะการกระทำที่ต้องออนไลน์จริงๆ เช่น “จ่ายด้วย Stripe” หรือ “เชิญผู้ใช้ใหม่” ทุกอย่างอื่นควรยังทำงานได้ รวมถึงดูข้อมูลล่าสุดและสร้างร่างใหม่

โฟลว์ที่สมจริง: ช่างสนามแก้รายงานงานในชั้นใต้ดิน แอปแสดง “1 รายการรอดำเนินการ” และให้เขาทำงานต่อ ต่อมาเปลี่ยนเป็น “กำลังซิงค์” แล้วล้างโดยอัตโนมัติ ถ้าล้มเหลว รายงานยังคงเข้าถึงได้ มีป้ายว่า “ล้มเหลว” พร้อมปุ่ม “ลองใหม่ตอนนี้” เดียว

ถ้าคุณสร้างใน AppMaster ให้โมเดลสถานะเหล่านี้เป็นส่วนหนึ่งของแต่ละระเบียน (pending, failed, synced) เพื่อให้ UI แสดงได้ทุกที่โดยไม่ต้องทำกรณีพิเศษ

การยืนยันตัวตน สิทธิ์ และความปลอดภัยขณะออฟไลน์

ไปเนทีฟโดยไม่ต้องเขียนใหม่
ใช้เอาต์พุตเนทีฟ Kotlin และ SwiftUI โดยไม่ต้องเขียนใหม่
เริ่มใช้งาน

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

การหมดอายุการล็อกอินขณะออฟไลน์

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

เมื่อออนไลน์อีกครั้ง ให้พยายามรีเฟรชแบบเงียบก่อน ถ้าจำเป็นต้องให้ผู้ใช้ลงชื่อเข้าใหม่ ให้ถามครั้งเดียว แล้วดำเนินการซิงค์ต่อโดยอัตโนมัติ

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

การเปลี่ยนสิทธิ์และการกระทำที่ถูกห้าม

สิทธิ์อาจเปลี่ยนขณะผู้ใช้ออฟไลน์ การแก้ไขที่อนุญาตเมื่อวานอาจถูกห้ามวันนี้ จัดการเรื่องนี้อย่างชัดเจน:

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

ตัวอย่าง: เจ้าหน้าที่ซัพพอร์ตแก้โน้ตลูกค้าออฟไลน์บนเที่ยวบิน ข้ามคืนบทบาทของเขาถูกถอด เมื่อซิงค์ เซิร์ฟเวอร์ตอบปฏิเสธ แอปควรแสดงว่า “ไม่สามารถอัปโหลด: คุณไม่มีสิทธิ์แล้ว” และเก็บโน้ตเป็นร่างท้องถิ่น

ข้อมูลอ่อนไหวที่เก็บในเครื่อง

เก็บเฉพาะสิ่งที่จำเป็นเพื่อแสดงหน้าจอและเล่นซ้ำคิว เข้ารหัสที่เก็บข้อมูลในอุปกรณ์ หลีกเลี่ยงการแคชความลับ และตั้งกฎชัดเจนสำหรับการออกจากระบบ (เช่น: ลบข้อมูลท้องถิ่น หรือเก็บเฉพาะร่างหลังจากได้รับความยินยอมจากผู้ใช้) ถ้าคุณสร้างด้วย AppMaster เริ่มจากโมดูลการยืนยันตัวตนของมันและออกแบบคิวให้รอเซสชันที่ถูกต้องก่อนส่งการเปลี่ยนแปลง

กับดักทั่วไปที่ทำให้งานหายหรือเกิดระเบียนซ้ำ

บั๊กออฟไลน์ส่วนใหญ่ไม่ใช่เรื่องซับซ้อน พวกมันมาจากการตัดสินใจเล็กๆ ไม่กี่ข้อที่ดูไม่เป็นไรเมื่อทดสอบด้วย Wi‑Fi สมบูรณ์ แล้วพังในสถานการณ์จริง

ปัญหาหนึ่งที่พบบ่อยคือการเขียนทับแบบเงียบ ๆ ถ้าแอปอัปโหลดเวอร์ชันเก่าและเซิร์ฟเวอร์ยอมรับโดยไม่ตรวจสอบ คุณอาจลบการแก้ไขใหม่ของคนอื่น ผู้ใช้ไม่รู้จนกว่าจะสาย ให้ซิงค์ด้วยหมายเลขเวอร์ชัน (หรือ timestamp updatedAt) และปฏิเสธการเขียนทับเมื่อเซิร์ฟเวอร์เคลื่อนที่ไปข้างหน้า ผู้ใช้จะได้ตัวเลือกชัดเจน

กับดักอีกอย่างคือการพายุลองซ้ำ เมื่อโทรศัพท์ได้สัญญาณอ่อน แอปอาจทุบ backend ทุกสองสามวินาที ทำให้แบตหมดและเขียนซ้ำซ้อน การลองใหม่ควรสงบ: ช้าลงหลังจากล้มเหลวแต่ละครั้งและใส่ความสุ่มเล็กน้อยเพื่อไม่ให้หลายพันอุปกรณ์ลองพร้อมกัน

ข้อผิดพลาดที่มักนำไปสู่การสูญเสียงานหรือซ้ำ:

  • ถือว่าทุกความล้มเหลวเป็น “เครือข่าย”: แยกข้อผิดพลาดถาวร (ข้อมูลไม่ถูกต้อง สิทธิ์ขาด) ออกจากชั่วคราว (timeout)
  • ซ่อนความล้มเหลวของการซิงค์: ถ้าคนมองไม่เห็นสิ่งที่ล้มเหลว เขาจะทำซ้ำงานและสร้างระเบียนสองอัน
  • ส่งการเปลี่ยนเดียวกันสองครั้งโดยไม่มีการป้องกัน: แนบ request ID ไม่ซ้ำเสมอเพื่อให้เซิร์ฟเวอร์ละเว้นซ้ำ
  • ผสานฟิลด์ข้อความอัตโนมัติโดยไม่บอกใคร: ถ้าคุณรวมการแก้ไขโดยอัตโนมัติ ให้ผู้ใช้ตรวจผลเมื่อสำคัญ
  • สร้างระเบียนออฟไลน์โดยไม่มี ID ที่เสถียร: ใช้ ID ชั่วคราวในเครื่องและแมปกับ ID เซิร์ฟเวอร์หลังอัปโหลด เพื่อให้การแก้ไขภายหลังไม่สร้างสำเนาที่สอง

ตัวอย่างอย่างเร็ว: ช่างสนามสร้าง “Site Visit” ใหม่ออฟไลน์ แล้วแก้อีกสองครั้งก่อนเชื่อมต่อ ถ้าเรียกสร้างถูกลองซ้ำและสร้างระเบียนบนเซิร์ฟเวอร์สองอัน การแก้ไขหลังๆ อาจผูกกับของผิดได้ ID เสถียรและการป้องกันการทำซ้ำฝั่งเซิร์ฟเวอร์ป้องกันเรื่องนี้

ถ้าคุณสร้างด้วย AppMaster กฎไม่เปลี่ยน ความต่างคือที่ที่คุณทำมัน: ในตรรกะการซิงค์ โมเดลข้อมูล และหน้าจอที่แสดงการเปลี่ยนแปลง “ล้มเหลว” เทียบกับ “ส่งแล้ว”

สถานการณ์ตัวอย่าง: สองคนแก้ไขระเบียนเดียวกัน

ส่งแอปเต็มรูปแบบโดยไม่ต้องเขียนโค้ด
สร้าง backend, เว็บ และแอปมือถือเนทีฟที่พร้อมใช้งานสำหรับ production จากโปรเจกต์เดียว
สร้างแอป

ช่างสนาม Maya กำลังอัปเดตตั๋ว “Job #1842” ในชั้นใต้ดินที่ไม่มีสัญญาณ เธอเปลี่ยนสถานะจาก “In progress” เป็น “Completed” และเพิ่มโน้ตว่า “เปลี่ยนวาล์ว ทดสอบเรียบร้อย” แอปบันทึกทันทีและแสดงเป็นรอดำเนินการ

ข้างบน Leo เพื่อนร่วมทีมของเธอออนไลน์และแก้ไขงานเดียวกันพร้อมๆ กัน เขาเปลี่ยนเวลานัดหมายและมอบหมายงานให้ช่างคนอื่นเพราะลูกค้าโทรมาอัปเดต

เมื่อ Maya ได้สัญญาณอีกครั้ง การซิงค์เบื้องหลังเริ่มอย่างเงียบ ๆ นี่คือสิ่งที่จะเกิดขึ้นในโฟลว์ที่คาดเดาได้และเป็นมิตรต่อผู้ใช้:

  1. การเปลี่ยนของ Maya ยังคงอยู่ในคิวการซิงค์ (ID งาน ฟิลด์ที่เปลี่ยน เวลา และเวอร์ชันที่เธอเห็นล่าสุด)
  2. แอปพยายามอัปโหลด เซิร์ฟเวอร์ตอบว่า: “งานนี้ถูกอัปเดตตั้งแต่เวอร์ชันที่คุณเห็น” (เกิดข้อขัดแย้ง)
  3. กฎข้อขัดแย้งของคุณทำงาน: สถานะและโน้ตสามารถผสาน แต่การเปลี่ยนการมอบหมายให้ชนะถ้ามันเกิดหลังบนเซิร์ฟเวอร์
  4. เซิร์ฟเวอร์ยอมรับผลลัพธ์แบบผสาน: status = “Completed” (จาก Maya), โน้ตถูกเพิ่ม (จาก Maya), ผู้รับมอบหมาย = การเลือกของ Leo (จาก Leo)
  5. งานกลับมาในแอปของ Maya พร้อมแบนเนอร์ชัดเจน: “ซิงค์แล้วพร้อมอัปเดต การมอบหมายเปลี่ยนขณะคุณออฟไลน์” ปุ่มเล็กๆ “ทบทวน” แสดงการเปลี่ยนที่เกิดขึ้น

ถ้าเพิ่มช่วงล้มเหลว: โทเค็นล็อกอินของ Maya หมดอายุขณะออฟไลน์ ความพยายามซิงค์ครั้งแรกล้มด้วย “ต้องลงชื่อเข้าใช้” แอปเก็บการแก้ไขของเธอ ทำเครื่องหมายเป็น “หยุดชั่วคราว” และโชว์พรอมต์เดียวง่ายๆ หลังจากเธอลงชื่อเข้าใหม่ การซิงค์จะกลับมาทำงานอัตโนมัติโดยไม่ต้องพิมพ์อะไรใหม่

ถ้ามีปัญหาการตรวจสอบความถูกต้อง (เช่น สถานะ “Completed” ต้องมีรูปถ่าย) แอปไม่ควรเดา มันทำเครื่องหมายรายการเป็น “ต้องแก้ไข” บอกอย่างชัดเจนว่าจะเพิ่มอะไร แล้วให้เธอส่งใหม่

แพลตฟอร์มอย่าง AppMaster ช่วยได้เพราะคุณออกแบบคิว กฎข้อขัดแย้ง และ UX สถานะรอดำเนินการแบบเชิงภาพ ในขณะที่ยังส่งแอปเนทีฟ Kotlin และ SwiftUI จริงๆ ได้

เช็คลิสต์ด่วนและขั้นตอนต่อไป

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

เช็คลิสต์สั้นๆ เพื่อยืนยันพื้นฐานแน่น:

  • คิวการซิงค์เก็บบนอุปกรณ์ และแต่ละการเปลี่ยนมี ID ท้องถิ่นที่เสถียรพร้อม ID เซิร์ฟเวอร์เมื่อต้องได้
  • มีสถานะชัดเจน (queued, syncing, sent, failed, needs review) และใช้สอดคล้องกัน
  • คำขอเป็น idempotent (ปลอดภัยที่จะลองใหม่) และแต่ละการปฏิบัติรวมคีย์ป้องกันการทำซ้ำ
  • ระเบียนมีการเวอร์ชัน (updatedAt, หมายเลข revision, หรือ ETag) เพื่อให้ตรวจจับข้อขัดแย้งได้
  • กฎข้อขัดแย้งเขียนเป็นภาษาง่าย (อะไรชนะ อะไรผสาน เมื่อไหร่ให้ถามผู้ใช้)

เมื่อสิ่งเหล่านี้เรียบร้อย ให้ตรวจสอบประสบการณ์ใช้งานว่าทัดเทียมกับโมเดลข้อมูล ผู้ใช้ควรเห็นสิ่งที่รอดำเนินการ เข้าใจว่าพังอะไร และทำอะไรได้โดยไม่กลัวสูญงาน

ทดสอบด้วยสถานการณ์ที่เหมือนชีวิตจริง:

  • แก้ไขในโหมดเครื่องบิน: สร้าง อัปเดต ลบ แล้วเชื่อมต่ออีกครั้ง
  • เครือข่ายไม่เสถียร: หลุดกลางทางขณะซิงค์ และยืนยันว่าการลองใหม่ไม่ทำซ้ำ
  • ปิดแอปตอนส่ง: บังคับปิดระหว่างส่ง เปิดใหม่ ยืนยันคิวกู้คืนได้
  • เวลาเครื่องเพี้ยน: เวลาของอุปกรณ์ผิด ตรวจสอบว่าการตรวจจับข้อขัดแย้งยังทำงาน
  • กดซ้ำ: ผู้ใช้กดบันทึกสองครั้ง ยืนยันว่ากลายเป็นการเปลี่ยนเดียวบนเซิร์ฟเวอร์

ทำต้นแบบโฟลว์เต็มก่อนขัดเกลา UI สร้างหน้าจอเดียว ประเภทระเบียนเดียว และกรณีความขัดแย้งเดียว (สองคนแก้ฟิลด์เดียว) เพิ่มพื้นที่สถานะซิงค์ง่ายๆ ปุ่มลองใหม่สำหรับล้มเหลว และหน้าข้อขัดแย้งที่ชัดเจน เมื่อใช้งานได้ ให้ทำซ้ำกับหน้าจออื่น

ถ้าคุณสร้างโดยไม่เขียนโค้ด AppMaster (appmaster.io) สามารถสร้างแอป Kotlin และ SwiftUI เนทีฟพร้อม backend ให้คุณมุ่งที่คิว การตรวจสอบเวอร์ชัน และสถานะที่เห็นได้โดยผู้ใช้ แทนการเดินสายเชื่อมต่อทุกอย่างเอง

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

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

เริ่ม