Bỏ qua, đến nội dung chính
Bài viếtFirmwareMới

Kiến trúc local-first: Giữ dữ liệu không lưu trên cloud

Cách mình thiết kế ứng dụng local-first với dữ liệu trên thiết bị, Automerge, đồng bộ tùy chọn và backup để ứng dụng vẫn hữu ích mà không phải đưa dữ liệu làm việc lên cloud.

Chia sẻ

LinkedInFacebookX
A local-first notes app and an external drive on a real developer desk

Mình thích những ứng dụng vẫn có cảm giác “thuộc về mình” khi biểu tượng Wi-Fi biến mất. Ghi chú mở ngay, chỉnh sửa không phải chờ spinner và muốn xuất dữ liệu cũng không cần xin phép một dịch vụ nào.

Đó là lời hứa thực tế của kiến trúc local-first: bản trên thiết bị là bản làm việc chính. Server có thể hỗ trợ đồng bộ hoặc backup, nhưng không đứng giữa mọi thao tác đọc và ghi.

Để viết bài này, mình follow Automerge, thư viện CRDT open source, giấy phép MIT, có lõi Rust và binding JavaScript/WASM. Cách tổ chức của dự án vẫn đáng học nếu sau này bạn chọn SQLite hoặc một cơ sở dữ liệu khác: lưu tại chỗ trước, mô tả thay đổi rõ ràng rồi mới đồng bộ các bản sao.

Local-first không phải là cache lớn hơn

Trong ứng dụng cloud thông thường, server giữ bản chuẩn, còn client chỉ có cache có thể bỏ đi. Khi phiên đăng nhập hết hạn hoặc API ngừng hoạt động, cache đó đôi khi chẳng còn dùng được.

Mình thường kiểm tra local-first bằng bốn câu hỏi:

  • Tạo, đọc, sửa, tìm kiếm và xóa có chạy khi mất mạng không?
  • Đóng rồi mở lại ứng dụng có giữ những thay đổi chưa đồng bộ không?
  • Người dùng có thể xuất được dữ liệu ở định dạng đọc được hoặc có tài liệu mô tả không?
  • Đồng bộ lỗi chỉ đổi trạng thái, hay làm cả ứng dụng ngừng làm việc?

Nếu ứng dụng chỉ xếp hàng một form khi offline, mình sẽ gọi nó là “có hỗ trợ offline”, chưa phải local-first.

Thẻ nhớ và ổ SSD không logo đặt cạnh laptop trên bàn gỗ để lưu dữ liệu tại chỗ

Local-first bắt đầu bằng một lời hứa rất đời thường: bản dữ liệu hữu ích nằm trên thiết bị và vẫn mở được khi mạng không hoạt động.

Đặt kho dữ liệu cục bộ trên luồng chính

Thiết kế gọn nhất khá đơn giản:

  1. Giao diện gửi lệnh xuống domain layer.
  2. Domain layer ghi vào IndexedDB, SQLite hoặc một tài liệu CRDT cục bộ.
  3. Giao diện render ngay trạng thái đã commit trên máy.
  4. Worker chạy nền trao đổi thay đổi với thiết bị khác hoặc relay sau đó.

Mạng không nằm giữa cú nhấp và lần lưu thành công. Cách này giảm khá nhiều logic rollback của optimistic update, nhưng đổi lại migration, xử lý xung đột, giới hạn dung lượng và khôi phục dữ liệu đều trở thành tính năng thật của sản phẩm.

Sơ đồ giao diện đọc ghi vào cơ sở dữ liệu cục bộ trước rồi mới đồng bộ tùy chọn sang thiết bị khác

Luồng đọc và ghi luôn đi qua dữ liệu cục bộ. Đồng bộ chạy nền, không phải điều kiện để dùng ứng dụng.

Ví dụ nhỏ theo cách Automerge tổ chức dữ liệu

Automerge lưu một tài liệu gần giống JSON và ghi lại thay đổi để hai bản được sửa độc lập có thể merge. Một tài liệu ghi chú tối thiểu có thể viết như sau:

import * as Automerge from "@automerge/automerge";

type Note = { id: string; text: string; updatedAt: number };
type NotesDoc = { notes: Note[] };

let doc = Automerge.from<NotesDoc>({ notes: [] });

doc = Automerge.change(doc, "add note", (draft) => {
  draft.notes.push({
    id: crypto.randomUUID(),
    text: "Nội dung này được lưu mà không cần gọi mạng.",
    updatedAt: Date.now(),
  });
});

const bytes = Automerge.save(doc);
await localDocumentStore.put("notes", bytes); // adapter IndexedDB hoặc file

Khi khởi động, hãy load số byte này trước khi mở kết nối đồng bộ. Khi hai bản sao gặp lại nhau, chúng trao đổi thay đổi rồi lưu tài liệu đã merge xuống máy lần nữa.

CRDT không làm mọi quyết định sản phẩm tự biến mất. Hai người cùng đổi tên một board vẫn cần kết quả dễ hiểu. File đính kèm, quyền truy cập, xóa vĩnh viễn và migration schema cũng cần quy tắc riêng.

Đồng bộ có thể tùy chọn nhưng phải nói rõ

Mình sẽ chọn và công bố rõ một trong ba chế độ:

Chế độDữ liệu di chuyển thế nàoPhù hợp với
Một thiết bịKhông tự đồng bộ; xuất file hoặc backup có mã hóaNhật ký, dụng cụ xưởng, log cảm biến cá nhân
Mạng nội bộPeer-to-peer hoặc relay nhỏ tự hostNhà, phòng lab, nhóm nhỏ trong LAN tin cậy
Relay riêngThay đổi đã mã hóa đi qua relay InternetNhiều thiết bị dùng ở ngoài nhà

Với relay riêng, chỉ mã hóa đường truyền là chưa đủ nếu lời hứa là “server không đọc được dữ liệu”. Bạn cần mã hóa thay đổi hoặc snapshot ngay trên thiết bị, không đưa khóa lên relay, xác thực từng thiết bị và thiết kế cách khôi phục khóa trước khi quảng cáo end-to-end encryption. Automerge giải quyết merge; nó không tự giải quyết danh tính, phân quyền hay quản lý bí mật.

Một laptop hiện ứng dụng ghi chú đã đồng bộ cạnh router gia đình bốn ăng-ten và chậu cây xanh

Ứng dụng vẫn lưu tại chỗ trước; mạng gia đình chỉ cần khi bạn bật đồng bộ chạy nền.

Những phần chán mới quyết định độ tin cậy

Trước khi gọi một ứng dụng là local-first, mình kiểm tra các tình huống này:

  • Storage bị dọn: trình duyệt có thể xóa dữ liệu, nên hãy xin persistent storage khi nền tảng hỗ trợ và luôn có chức năng export.
  • Ghi bị ngắt giữa chừng: dùng transaction hoặc thay file kiểu atomic; đừng ghi đè trực tiếp lên snapshot tốt duy nhất.
  • Migration lỗi: giữ dữ liệu cũ đến khi schema mới mở thành công.
  • Mất thiết bị: mã hóa dữ liệu nhạy cảm và nói rõ màn hình khóa không bảo vệ được điều gì.
  • Xóa dữ liệu: xác định xóa có xung đột không, tombstone được giữ bao lâu và khi nào mọi bản sao cùng quên một mục.
  • Khôi phục backup: phải thử thật. Nút backup mà chưa từng restore chỉ là đồ trang trí.

Bản đầu tiên của mình sẽ thật nhỏ

Mình sẽ bắt đầu với một loại tài liệu, một local adapter và chức năng xuất file mã hóa thủ công. Sau đó, thử chế độ máy bay, buộc tắt ứng dụng giữa lúc ghi, nâng schema và khôi phục trên một thiết bị mới.

Khi phần đó chạy chắc, mình mới thêm peer sync. Local-first không chủ yếu là cài thêm một dependency CRDT; cốt lõi là làm cho bản dữ liệu trên thiết bị đầy đủ, bền và tự nó đã hữu ích. Cloud khi ấy chỉ là người chuyển thư tùy chọn, không còn là chủ sở hữu của mọi cú nhấp.

Nguồn tham khảo và nguồn ảnh

Chia sẻ

LinkedInFacebookX

Tiếp tục khám phá

Đọc tiếp

Bài viết liên quan

Xem thêm trong Bài viết

Nastrotek sử dụng cookie để phân tích truy cập và cá nhân hóa quảng cáo, giúp hiểu cách website được sử dụng. Bạn có thể chấp nhận hoặc từ chối các cookie không thiết yếu.