Tới thẳng nội dung

Riêng tư

Chúng tôi đã thử đồng bộ iCloud rồi gỡ nó ra

Nó đồng bộ các dòng dữ liệu, không đồng bộ tệp. Máy thứ hai của bạn sẽ có đủ mọi tấm thẻ mà thiếu sạch ảnh.

Timegrove giữ khu vườn của bạn ngay trên iPhone. Không phải “chủ yếu ở trên máy”, cũng không phải “trên máy, kèm tuỳ chọn đám mây” — cơ sở dữ liệu là một tệp nằm trong vùng riêng của ứng dụng, còn ảnh, video và các bản ghi âm là những tệp nằm cạnh nó trên đĩa. Đó là quyết định kiến trúc đầu tiên chúng tôi đưa ra, và suốt mấy tuần mùa xuân vừa rồi, nó cũng là quyết định chúng tôi đang tìm cách rút lại.

Lý do chẳng có gì bí ẩn. Người ta có iPhone và có cả iPad. Câu đầu tiên ai cũng hỏi về một ứng dụng giữ nhiều năm cuộc đời mình là: nó có đồng bộ không. Hỏi vậy là đúng. Nên chúng tôi làm thật, chạy thật, nhìn kết quả, rồi gỡ ra.

“Làm thật” trên iOS nghĩa là gì

Ít hơn bạn tưởng nhiều. SwiftData có thể soi chiếu toàn bộ kho dữ liệu sang cơ sở dữ liệu CloudKit riêng của chính người dùng, và phần tích hợp chỉ là một tham số:

ModelConfiguration(schema: schema, isStoredInMemoryOnly: false,
                   cloudKitDatabase: .automatic)

Phần khó Apple đã làm hộ. Dữ liệu nằm trong cơ sở dữ liệu riêng của người dùng, dưới Apple Account của họ, trong một container mà chúng tôi có muốn cũng không đọc được. Với một ứng dụng đặt quyền riêng tư lên đầu thì gần như là lý tưởng: đồng bộ mà không có máy chủ nào của chúng tôi nằm giữa đường.

Đổi lại có một cái giá phải trả trước. Muốn soi chiếu thì schema phải theo luật của CloudKit: mọi thuộc tính đều phải có giá trị mặc định hoặc là optional, mọi quan hệ đều phải optional, và không thuộc tính nào được đánh dấu là duy nhất. Chúng tôi viết lại mô hình dữ liệu cho đủ cả ba.

Rồi chúng tôi nhìn sang máy thứ hai

Thẻ nào cũng có đủ. Đủ ngày tháng, đủ chú thích, đủ thẻ gắn, đủ tên người, đủ số đo. Và không một tấm ảnh nào.

Đây không phải lỗi, cũng không phải cấu hình sai. Đây là chỗ thiết kế của ứng dụng gặp thiết kế của cơ chế đồng bộ. Trong Timegrove, một kỷ niệm ảnh gồm hai thứ: một dòng dữ liệu nói rằng ở đây có một tấm ảnh, chụp lúc này, nặng 4,2 MB, mã băm là đây — và một tệp. Dòng dữ liệu thì bé xíu. Tệp mới là ký ức. Soi chiếu kho dữ liệu chỉ mang theo các dòng.

Máy thứ hai trông không có vẻ gì là hỏng. Nó trông như một ứng dụng vừa đồng bộ xong. Với một bản sao lưu thì đó là kiểu hỏng tệ nhất: kiểu tự trình bày rằng mình đã đầy đủ.

Chúng tôi có thể nhét luôn phần byte vào cơ sở dữ liệu để nó đi cùng. Đó là một hướng có thật, và nhiều ứng dụng chọn hướng đó. Nhưng làm vậy nghĩa là dựng lại toàn bộ tầng media quanh các thuộc tính nhị phân, chấp nhận trần dung lượng đi kèm, và bắt cả kho ảnh của một người tải lại về một chiếc máy vốn đã có gần hết. Đâu đó trong kế hoạch ấy có một phiên bản Timegrove dành nguyên lần mở đầu tiên trên chiếc iPad mới để kéo bốn gigabyte qua wifi khách sạn, kèm một vòng xoay, và không có cách nào nói “cho tôi hai năm gần nhất thôi”.

Chúng tôi cũng có thể dựng một máy chủ. Nhưng vì đã nói ở đủ nhiều nơi rằng ứng dụng không nói chuyện với máy chủ nào của chúng tôi, thì cũng nên nói thẳng cái giá của câu đó: nó đánh đổi bằng chính khả năng đồng bộ. Không có phương án thứ ba khôn khéo nào nấp sau phương án thứ hai cả.

Thứ chúng tôi làm thay vào đó

Một bản sao lưu bạn nhìn thấy được. Theo lịch bạn chọn — hằng ngày, hằng tuần, hằng tháng, hoặc tắt — Timegrove ghi một gói .tmln đầy đủ vào iCloud Drive của chính bạn. Không phải một container ẩn: là thư mục tên Timegrove hiện ra trong ứng dụng Tệp, thư mục bạn mở được, chạm vào được, xem được nó nặng bao nhiêu, và chép sang laptop được. Năm bản mới nhất được giữ, bản cũ nhất bị quét đi — vì gói iCloud miễn phí có 5 GB dùng chung với mọi thứ khác của người ta, và một cơ chế sao lưu làm đầy ổ rồi ngừng thì còn tệ hơn là không có.

Tệp đó mang theo phần byte thật. Ảnh, video, ghi âm, bản quét — đủ cả, và lúc quay về đều được đối chiếu với mã băm đã ghi lúc xuất. Khôi phục cũng chính là luồng nhập bạn vẫn dùng khi đổi máy, vẫn cái danh sách chọn cây ấy, nên không tồn tại một đường khôi phục riêng chỉ được chạy đúng vào ngày tệ nhất trong năm của ai đó. Chúng tôi viết kỹ về định dạng này trong bài định dạng .tmln, và vì sao lấy dữ liệu ra luôn miễn phí.

Người ta thật ra đang hỏi gì

Hoá ra “có đồng bộ không?” là hai câu hỏi khoác chung một cái áo, và chúng có hai câu trả lời khác nhau.

“Tôi sắp đổi máy mới”

Đây là ca phổ biến, hai ba năm xảy ra một lần, và nó được giải quyết trọn vẹn. Bản sao lưu mới nhất của bạn vốn đã nằm sẵn trong iCloud Drive. Cài Timegrove lên máy mới, nhập tệp đó vào, tích những cây bạn muốn, xong — kèm đủ mọi tấm ảnh, mọi bản ghi âm, mọi bản quét, cái nào cũng được đối chiếu với mã băm lúc ghi. Đường chuyển máy và đường cứu hộ khi mất dữ liệu là cùng một đường, nghĩa là nó được người bình thường chạy trong việc bình thường, chứ không chỉ được chạy bởi người xui.

“Tối nay tôi thêm kỷ niệm trên iPad, mai muốn thấy nó trên điện thoại”

Ca này chúng tôi không làm. Không có cách diễn đạt nào khiến câu đó nghe dễ chịu hơn. Nếu đó là cách bạn định dùng ứng dụng thì nên biết trước khi bắt đầu, chứ đừng biết sau ba năm — nên nó được ghi ở trang giá chứ không chỉ ở đây.

Thứ chúng tôi không muốn là khả năng thứ ba: một ứng dụng trả lời “có” cho cả hai câu hỏi, rồi trả lời câu thứ hai bằng một lưới ô xám ở chỗ đáng lẽ là ảnh.

Nói thẳng cái giá

Bạn không có chuyện sửa song song trên nhiều máy. Nếu trong cùng một tuần bạn thêm kỷ niệm trên iPad và trên iPhone, đó là hai khu vườn, và muốn gộp thì phải xuất một bên rồi nhập vào bên kia. Bản nhập hợp nhất theo id, bản sửa mới hơn thắng; bản bị từ chối không bị vứt đi mà nằm chờ ở mục Xem lại phần đã nhập, để bạn xem cỗ máy đã quyết gì và cãi lại nếu muốn. Đó là một cơ chế gộp tử tế. Nhưng nó vẫn là gộp, và bạn phải chủ động yêu cầu.

Chúng tôi tin đánh đổi theo chiều này là đúng, vì cùng một lý do đã khiến chúng tôi đi nhìn cái máy thứ hai ngay từ đầu: một bản sao lưu cầm được trong tay hơn một bản đồng bộ không kiểm chứng được. Bản của chúng tôi là một tệp, nằm trong một thư mục bạn tìm ra, chép đi đâu cũng được. Ngày mai Timegrove có biến mất thì tệp đó vẫn còn, và vẫn đọc được.

Thứ chúng tôi giữ lại từ lần thử ấy

Chính mấy ràng buộc của schema. Mặc định cho mọi thuộc tính, quan hệ optional, không có thuộc tính duy nhất — viết ra vì CloudKit, rồi phát hiện chúng đơn giản là những quy tắc tốt. Một mô hình mà trường nào cũng có trạng thái rỗng được định nghĩa rõ là mô hình sống sót nổi khi phải nhập dữ liệu từ chính phiên bản cũ của nó, mà đó lại đúng là việc bộ nhập phải làm. Cờ cloudKitEnabled vẫn nằm trong mã nguồn, đang để false, và màn hình Báo cáo quyền riêng tư đọc thẳng cờ đó — để ứng dụng không bao giờ khoe được một kiểu đồng bộ mà bản dựng này không làm.

Chúng tôi cũng giữ lại cái thói quen đã dẫn tới quyết định: làm xong thì bê một khu vườn thật sang máy thứ hai và ngồi nhìn. Tài liệu đề xuất viết “media đồng bộ qua CloudKit asset”, và tài liệu đề xuất sai theo kiểu không ai phát hiện được bằng cách đọc.

Một chuyện về chính trang web này

“Không quảng cáo, không tài khoản, không máy chủ” là mô tả về ứng dụng. Nó không mô tả trang web này. Trang này chạy WordPress trên một máy chủ đi thuê, và nó nạp analytics nếu — và chỉ nếu — bạn đã bấm đồng ý ở thanh cookie. Trang quyền riêng tư ghi rõ cookie nào, theo thứ tự nào, và đổi ý thì làm sao. Hai phần mềm khác nhau với hai câu chuyện riêng tư khác nhau; gộp chúng lại cho gọn lời quảng cáo thì đúng là cái việc mà bài này viết ra để phản đối.

Muốn xem ứng dụng làm gì với một khu vườn đã có vài năm tuổi, mời ghé các khu vườn mẫu — đó là bộ dữ liệu mẫu thật mà app đang mang theo, dựng lại ngay trong trình duyệt. Các quyết định cùng dạng nằm ở mục Riêng tư.