← Quay lại danh sách2026-09-18

Offline-First Architecture trong ứng dụng Mobile

Introduction

Trong nhiều ứng dụng mobile, kết nối Internet thường được mặc định là luôn sẵn sàng. Tuy nhiên, người dùng có thể đi vào vùng sóng yếu, chuyển mạng, bật chế độ máy bay hoặc đơn giản là muốn sử dụng ứng dụng trong thời gian không có Internet.

Offline-First là cách tiếp cận đặt dữ liệu local và khả năng hoạt động độc lập với mạng ở vị trí trung tâm của kiến trúc. Offline không được xem là một trạng thái lỗi; đó là một trạng thái hoạt động mà ứng dụng cần hỗ trợ một cách có chủ đích.

Trong bài viết này, tôi mở rộng từ nguyên tắc ban đầu của article-03: dữ liệu cục bộ là trung tâm, người dùng vẫn có thể đọc, tạo và chỉnh sửa dữ liệu khi kết nối không ổn định.

Offline-First là gì?

Với kiến trúc truyền thống, UI thường gọi API và chờ server trả về dữ liệu. Khi mạng lỗi, màn hình có thể hiển thị loading vô hạn hoặc error.

Với Offline-First, ứng dụng ưu tiên đọc và ghi vào local database. Server trở thành nguồn đồng bộ khi có kết nối, thay vì là điều kiện bắt buộc cho mọi thao tác.

UI
 ↓
ViewModel / Use Case
 ↓
Repository
 ↙       ↘
Local DB  Remote API

Mục tiêu không phải là loại bỏ server, mà là thiết kế để trải nghiệm chính vẫn hoạt động khi server tạm thời không thể truy cập.

Vì sao chọn Offline-First?

Offline-First đặc biệt phù hợp với ứng dụng học tập, ghi chú, quản lý công việc và các sản phẩm cần phản hồi nhanh. Với ứng dụng Tiếng Nhật IT, người dùng cần mở flashcard, làm quiz và xem tiến độ ở bất cứ đâu, kể cả khi đang di chuyển hoặc không có Internet.

Các lợi ích chính:

  • Màn hình hiển thị dữ liệu gần như ngay lập tức.
  • Người dùng có thể học và cập nhật tiến độ khi offline.
  • Ứng dụng ít phụ thuộc vào độ ổn định của mạng.
  • Dữ liệu học tập được lưu trên thiết bị và có thể xử lý theo từng thao tác.
  • Trải nghiệm nhất quán hơn thay vì liên tục chuyển giữa loading và error.

Data Flow Architecture

Một data flow rõ ràng giúp tách UI khỏi chi tiết lưu trữ và đồng bộ:

  1. Người dùng thực hiện thao tác trên UI.
  2. ViewModel hoặc Use Case kiểm tra nghiệp vụ.
  3. Repository quyết định đọc/ghi local database hay gọi remote API.
  4. Local database cập nhật trạng thái trước để UI phản hồi nhanh.
  5. Sync worker xử lý các thay đổi chưa đồng bộ khi có mạng.
  6. Kết quả đồng bộ cập nhật lại local database, sau đó UI đọc dữ liệu mới.
User action
    ↓
ViewModel / Use Case
    ↓
Repository
    ↓
Write local data ──→ UI observes local data
    ↓
Pending sync queue
    ↓ (when online)
Remote API
    ↓
Update local data

Điểm quan trọng là UI quan sát local state thay vì phụ thuộc trực tiếp vào response của API. Nhờ đó, cùng một luồng hiển thị có thể hoạt động cho cả online và offline.

Local Storage

Local database là nền tảng của Offline-First. Với Tiếng Nhật IT, dữ liệu từ vựng, trạng thái đã học, từ khó, danh sách yêu thích và tiến độ được lưu trực tiếp trên thiết bị.

Stack sử dụng cho ứng dụng gồm:

  • Flutter và Dart cho lớp ứng dụng.
  • SQLite làm nền tảng lưu trữ.
  • Drift Database để định nghĩa bảng, query và mapping dữ liệu có type safety.
  • Riverpod để quản lý state và dependency.

Một mô hình dữ liệu đơn giản có thể gồm:

Vocabulary
- id
- kanji
- hiragana
- meaning

LearningProgress
- vocabularyId
- reviewCount
- correctCount
- lastReviewedAt
- isFavorite

Local database không chỉ là cache của API. Nó là nguồn dữ liệu mà các use case đọc và cập nhật trong suốt thời gian ứng dụng hoạt động.

Sync Strategy

Không phải ứng dụng Offline-First nào cũng cần đồng bộ cloud. Trong Tiếng Nhật IT, nội dung từ vựng được đóng gói trong ứng dụng và tiến độ học tập chủ yếu lưu local, nên mô hình local-first giúp giảm độ phức tạp và bảo vệ phản hồi tức thời.

Nếu cần Cloud Sync trong tương lai, mỗi thay đổi có thể được ghi vào một hàng đợi:

PendingChange
- id
- entityId
- operation
- payload
- createdAt
- retryCount
- status

Khi có mạng, worker gửi các thay đổi theo thứ tự, đánh dấu thành công hoặc retry với backoff khi thất bại. Cần có chiến lược xử lý xung đột rõ ràng, chẳng hạn last-write-wins cho dữ liệu đơn giản hoặc merge theo từng trường cho dữ liệu tiến độ.

Đồng bộ cũng cần idempotency: retry cùng một thao tác không được tạo ra dữ liệu trùng hoặc làm sai tiến độ. Trạng thái pending, syncing, synced và failed nên được mô hình hóa rõ ràng thay vì ẩn trong UI.

Ưu điểm

  • Phản hồi nhanh: UI đọc từ local database, không phải chờ network round trip.
  • Tin cậy: các thao tác quan trọng vẫn hoạt động khi mạng mất.
  • Tiết kiệm dữ liệu: chỉ đồng bộ phần thay đổi thay vì tải lại toàn bộ.
  • Dễ kiểm thử: use case có thể kiểm thử với local data độc lập với server.
  • Trải nghiệm tự nhiên: người dùng không cần suy nghĩ ứng dụng đang online hay offline.

Thách thức

Offline-First không chỉ là thêm SQLite vào dự án. Các vấn đề cần giải quyết gồm:

  • Thiết kế schema và migration khi dữ liệu thay đổi.
  • Xác định source of truth giữa local và remote.
  • Xử lý conflict khi một dữ liệu được thay đổi ở nhiều nơi.
  • Hiển thị trạng thái đồng bộ mà không làm người dùng lo lắng.
  • Retry an toàn khi API lỗi hoặc mạng chập chờn.
  • Kiểm thử các trạng thái online, offline, timeout và khôi phục kết nối.
  • Quản lý kích thước database và vòng đời dữ liệu trên thiết bị.

Một nguyên tắc quan trọng là không che giấu trạng thái dữ liệu. Nếu thao tác chưa được đồng bộ, ứng dụng nên thể hiện điều đó ở mức vừa đủ và không làm gián đoạn tác vụ chính.

Kinh nghiệm áp dụng thực tế trong Tiếng Nhật IT

Khi phát triển Tiếng Nhật IT, Offline-First được lựa chọn vì hành vi học tập cần tính liên tục. Người dùng có thể mở ứng dụng trên tàu, trong giờ nghỉ hoặc tại nơi kết nối không ổn định; flashcard và quiz vẫn phải phản hồi ngay.

Thay vì gọi API cho mỗi lần mở một từ, ứng dụng đọc dữ liệu từ SQLite thông qua Drift. Khi người dùng trả lời quiz, điểm số và tiến độ được cập nhật local trước. Riverpod giúp các màn hình quan sát state mới mà không cần tự quản lý nhiều callback.

Kinh nghiệm thực tế cho thấy trải nghiệm offline tốt cần bắt đầu từ nghiệp vụ, không phải từ một lớp cache thêm vào sau cùng. Cần xác định dữ liệu nào bắt buộc phải có trên thiết bị, thao tác nào phải phản hồi ngay và trạng thái nào có thể chờ đồng bộ.

Kết luận

Offline-First đặt dữ liệu cục bộ ở vị trí trung tâm thay vì xem offline như một trạng thái lỗi. Với kiến trúc phù hợp, người dùng vẫn có thể đọc, tạo và chỉnh sửa dữ liệu khi kết nối không ổn định.

Đối với ứng dụng mobile như Tiếng Nhật IT, Flutter kết hợp với SQLite, Drift và Riverpod tạo ra nền tảng phù hợp để xây dựng trải nghiệm nhanh, ổn định và có khả năng mở rộng. Khi cần cloud sync, có thể bổ sung queue, retry và conflict resolution mà không phải thay đổi toàn bộ UI.