Đang nhận dự án và hợp tác sản phẩm mới

Offline-first là quyết định về mô hình dữ liệu, không phải một tính năng cache

Phần lớn ứng dụng gắn hỗ trợ offline vào phút chót rồi phát hiện đó vốn là câu hỏi kiến trúc. Đây là những thứ thật sự phải thay đổi, và các quy tắc giải quyết xung đột bạn buộc phải chọn giữa.

"Làm cho nó chạy offline" nghe như một tính năng thêm vào trong một sprint. Không phải. Nó thay đổi chỗ dữ liệu của bạn nằm và ai quyết định điều gì là đúng, và điều đó chạm vào mọi thứ.

Tin tốt là công việc này đã được hiểu rõ. Tin xấu là gắn nó vào sau tốn gấp vài lần so với xây từ đầu.

Hai kiểu kiến trúc

Kiến trúc mặc định là máy chủ nắm quyền. Màn hình phản ánh máy chủ. Một hành động gửi request, chờ, và cập nhật khi có phản hồi. Offline nghĩa là hỏng, vì không có gì để hiển thị và không có chỗ để ghi.

Kiểu còn lại là local-first. Thiết bị có cơ sở dữ liệu riêng và giao diện đọc từ đó. Các thao tác ghi đi thẳng vào bộ nhớ cục bộ ngay lập tức và màn hình cập nhật từ đó. Một tiến trình nền đối chiếu với máy chủ khi có kết nối.

Sự đảo ngược đó là toàn bộ vấn đề: mạng thôi nằm giữa người dùng và dữ liệu của họ. Mọi thứ khác đi theo.

Những gì thật sự thay đổi

Mỗi bản ghi cần một định danh sinh được khi offline

Nếu máy chủ cấp ID, thiết bị không thể tạo gì khi offline mà không bịa ra một ID tạm rồi viết lại mọi tham chiếu sau đó. Hãy sinh ID ở client, bằng UUID hoặc tương tự, ngay từ ngày đầu. Đúng một quyết định này rất rẻ lúc đầu và rất đau khi gắn về sau.

Thao tác ghi trở thành hàng đợi, không phải lời gọi

Một hành động ghi ý định vào một hàng đợi cục bộ bền vững rồi trả về ngay. Một tiến trình đồng bộ sẽ rút cạn hàng đợi đó. Nghĩa là hàng đợi phải sống sót qua việc app bị tắt, phải thử lại có giãn cách, và phải bất biến khi lặp, vì bạn sẽ gửi một số thao tác hai lần.

Xoá thôi là xoá

Nếu một thiết bị xoá một bản ghi khi offline, và đồng bộ chỉ gửi những gì đang tồn tại, máy chủ sẽ không bao giờ biết và sẽ nhiệt tình gửi lại bản ghi đó. Bạn cần bia mộ: việc xoá được ghi lại như một dữ kiện có mốc thời gian, giữ đủ lâu để mọi thiết bị nhìn thấy.

Giao diện phải thể hiện trạng thái đồng bộ

Một khi thao tác ghi có thể thành công cục bộ rồi thất bại sau đó, người dùng cần biết. Không phải spinner trên mọi thứ, mà một chỉ báo trung thực: đã lưu cục bộ, đang đồng bộ, đã đồng bộ, thất bại. Giấu điều này tạo ra kết cục tệ nhất, là ai đó tin rằng công việc của mình an toàn trong khi không phải vậy.

Chọn quy tắc xử lý xung đột

Hai thiết bị sửa cùng một bản ghi khi offline. Cả hai quay lại. Phải có gì đó quyết định. Không có đáp án đúng phổ quát; chỉ có đáp án đúng cho dữ liệu của bạn.

Chiến lượcCách hoạt độngHợp với
Ghi sau thắngMốc thời gian lớn nhất ghi đèCài đặt, trạng thái theo thiết bị, trường ít rủi ro
Trộn theo trườngMốc thời gian cho từng trường; trộn các trường không xung độtBản ghi mà người ta sửa các phần khác nhau
Nhật ký thao tácLưu ý định, phát lại theo thứ tựBộ đếm, danh sách, mọi thứ mang tính cộng dồn
CRDTCấu trúc trộn được một cách tất địnhVăn bản cộng tác và tài liệu chia sẻ
Hỏi người dùngĐưa cả hai ra, để họ chọnChỉ dành cho xung đột hiếm và giá trị cao

Hai cảnh báo từ kinh nghiệm. "Ghi sau thắng" dựa trên đồng hồ thiết bị sẽ âm thầm làm mất dữ liệu, vì đồng hồ thiết bị sai; hãy dùng mốc do máy chủ cấp hoặc đồng hồ logic. Và "hỏi người dùng" không phải mặc định; nó là lối thoát hiểm cho những trường hợp quá tốn kém để tự giải quyết. Bị hỏi đủ nhiều lần, người ta sẽ bấm bừa mà không đọc.

Hãy chọn quy tắc xung đột theo từng loại dữ liệu, không phải theo cả ứng dụng. Tiến độ flashcard của người dùng có thể cộng dồn và trộn sạch sẽ. Tên hiển thị của họ thì "ghi sau thắng" là đủ. Dùng chung một quy tắc cho cả hai là sai với một trong hai.

Bản thân việc đồng bộ

Hình dạng khả dụng cho phần lớn sản phẩm:

  1. Mỗi bản ghi mang một số phiên bản hoặc mốc cập nhật do máy chủ gán.
  2. Client lưu một con trỏ: điểm mà nó đã theo kịp.
  3. Khi đồng bộ, client gửi các thao tác trong hàng đợi cùng con trỏ của mình.
  4. Máy chủ áp dụng các thao tác, giải quyết xung đột, và trả về mọi thay đổi kể từ con trỏ đó cộng với một con trỏ mới.
  5. Client áp dụng thay đổi và dịch con trỏ một cách nguyên tử.

Tính nguyên tử ở bước 5 quan trọng hơn vẻ ngoài của nó. Nếu bạn dịch con trỏ rồi thất bại lúc áp dụng, bạn đã âm thầm bỏ qua các thay đổi và sẽ không bao giờ có gì báo cho bạn biết.

Hãy chủ động kiểm thử các tình huống xấu

Lỗi offline không xuất hiện trong kiểm thử bình thường, vì kiểm thử bình thường có wifi. Hãy đưa những thứ này vào kế hoạch test:

  • Bật chế độ máy bay một tuần, rồi kết nối lại với hàng trăm thao tác trong hàng đợi.
  • Bị giết giữa lúc đồng bộ. Tắt cứng ứng dụng khi một lô đang bay. Không được mất gì và không được áp dụng hai lần.
  • Cổng đăng nhập wifi công cộng. Trạng thái mạng tệ nhất: thiết bị báo có kết nối, mọi request trả về trang đăng nhập. Request phải thất bại sạch sẽ, không được làm hỏng trạng thái.
  • Lệch đồng hồ. Chỉnh đồng hồ thiết bị lệch một ngày và xác nhận thứ tự vẫn đúng.
  • Hai thiết bị, cùng tài khoản, cùng offline, cùng sửa một bản ghi. Đây là nơi quy tắc xung đột chứng minh giá trị.

Khi nào không nên làm

Offline-first là công việc thật, và không phải lúc nào cũng đáng. Hãy bỏ qua khi ứng dụng dù sao cũng vô nghĩa nếu không có mạng, như chat trực tiếp hay thanh toán; khi dữ liệu vốn dĩ được chia sẻ và đồng thời, nơi bạn cần real-time hơn là offline; hoặc khi đó là công cụ nội bộ ít dùng mà chi phí lớn hơn lợi ích.

Hãy làm khi người ta dùng ứng dụng ở nơi kết nối chập chờn: lúc đi làm, trên tàu xe, trên máy bay, vùng nông thôn, trong toà nhà sóng yếu. Với một ứng dụng học tập thì đó là phần lớn số phiên, nên chúng tôi coi nó là giả định khởi đầu chứ không phải một yêu cầu tính năng.

Phiên bản rẻ nhất

Nếu kiến trúc local-first đầy đủ là quá sức ở giai đoạn hiện tại, có một khoảng giữa rất rộng đáng lấy:

  • Cache phần đọc thật mạnh để ứng dụng mở ra là có nội dung chứ không phải spinner.
  • Xếp hàng các thao tác ghi một cách bền vững ngay cả khi chưa đồng bộ đầy đủ, để một hành động không bao giờ mất vì rớt mạng.
  • Sinh ID ở client ngay từ bây giờ, vì hôm nay nó không tốn gì và về sau nó mở khoá mọi thứ.

Ba việc đó mang lại phần lớn lợi ích cảm nhận với một phần nhỏ công sức, và không việc nào bị lãng phí nếu sau này bạn đi xa hơn.

Mobile Kỹ thuật Kiến trúc