Архитектура приложений, работающих без сети

Иван Корнев·28.04.2026·5 мин

Чтобы сделать приложение, работающее без интернета, необходимо внедрить архитектуру Offline-First. Это означает, что локальная база данных на устройстве пользователя является единственным источником истины (Single Source of Truth), а сервер используется только для фоновой синхронизации. Ключевые элементы решения: использование встроенных БД (SQLite, Realm, WatermelonDB), очередь исходящих запросов и алгоритмы разрешения конфликтов при восстановлении связи.

Такой подход гарантирует мгновенный отклик интерфейса и работоспособность сервиса в метро, самолете или зонах с плохим покрытием.

Главный принцип: Пользователь всегда взаимодействует с локальными данными. Сеть — это просто способ сохранить изменения и получить обновления, но не условие для работы интерфейса.

Основы архитектуры Offline-First

Традиционные приложения часто строятся по схеме «Запрос-Ответ»: интерфейс блокируется или показывает лоадер, пока ждет данные от API. В Offline-First подходе эта парадигма меняется.

Локальная база данных как центр системы

Вместо кэширования ответов сервера, вы храните полноценную структуру данных на устройстве.

  • Чтение: Данные всегда берутся из локальной БД. Это обеспечивает нулевую задержку (0 ms latency) для UI.
  • Запись: Изменения сначала сохраняются локально, затем помечаются как «ожидающие синхронизации» (pending).

Выбор технологий хранения

Выбор инструмента зависит от платформы и сложности данных:

ТехнологияПлатформаОсобенностиЛучшее применение
SQLite / RoomAndroid, iOS, Cross-platformНадежность, стандарт индустрии, сложный SQLСложные связи, большие объемы данных
RealmMobile (Native, RN, Flutter)Объектная модель, высокая скорость, реактивностьБыстрые мобильные приложения
WatermelonDBReact Native, WebЛенивая загрузка, оптимизирована для синхронизацииСоциальные сети, чаты, сложные списки
PouchDB / RxDBWeb, HybridРабота с JSON, встроенная репликация с CouchDBВеб-приложения, PWA
Core DataiOS/macOSНативная интеграция, граф объектовЭкосистема Apple

Стратегии синхронизации данных

Самая сложная часть офлайн-разработки — это не хранение, а корректная передача изменений на сервер и обратно, когда сеть появляется.

1. Очередь исходящих изменений (Outbox Pattern)

Когда пользователь создает или редактирует запись офлайн, приложение не отправляет HTTP-запрос немедленно. Вместо этого:

  1. Запись сохраняется в локальную таблицу entities.
  2. Мета-информация об изменении (тип операции, ID, timestamp) сохраняется в специальную таблицу sync_queue или outbox.
  3. Фоновый процесс (воркер) мониторит наличие сети. При появлении соединения он берет задачи из очереди и отправляет их на сервер.
  4. После успешного ответа сервера запись удаляется из очереди.

Риск потери данных: Если приложение будет удалено или очищен кэш до синхронизации, данные из очереди могут быть потеряны. Всегда сохраняйте очередь в энергонезависимую память (диск), а не в оперативную.

2. Инкрементальная синхронизация

Не нужно скачивать всю базу данных при каждом подключении. Используйте механизмы дельта-синхронизации:

  • Клиент отправляет серверу last_sync_timestamp или cursor.
  • Сервер возвращает только те записи, которые изменились или появились после этой метки.
  • Это критично для экономии трафика и батареи.

Разрешение конфликтов (Conflict Resolution)

Конфликт возникает, когда одна и та же запись изменена и на клиенте (офлайн), и на сервере (другим устройством или пользователем) за время разрыва связи.

Основные стратегии

  1. Last Write Wins (LWW)

    • Логика: Побеждает изменение с более поздним временным штампом.
    • Плюсы: Простота реализации.
    • Минусы: Можно потерять важные данные, если часы на устройствах рассинхронизированы или изменения были сделаны почти одновременно.
  2. Client Wins / Server Wins

    • Логика: Безусловный приоритет одной из сторон.
    • Пример: В заметках часто выбирают «Client Wins», чтобы не раздражать пользователя потерей его текста. В финансовых транзакциях — «Server Wins» для целостности баланса.
  3. Слияние (Merge)

    • Логика: Алгоритм пытается объединить изменения (как в Git).
    • Применение: Подходит для текстовых документов или независимых полей объекта. Требует сложной логики на бэкенде или клиенте.
  4. Ручное разрешение

    • Логика: Приложение сохраняет обе версии и показывает пользователю интерфейс выбора: «Оставить вашу версию» или «Загрузить версию с сервера».
    • Применение: Критически важные данные, где автоматическое решение недопустимо.

Для реализации LWW используйте не время устройства, а логические часы (например, векторные часы или UUID, содержащий таймстемп сервера), чтобы избежать проблем с часовыми поясами и ручным переводом времени.

Управление состоянием сети и UX

Пользователь должен понимать, в каком режиме работает приложение. Прозрачность статуса сети повышает доверие.

Индикаторы состояния

  • Зеленый значок: Синхронизация завершена, данные актуальны.
  • Желтый/Серый значок: Режим офлайн. Изменения сохраняются локально и будут отправлены позже.
  • Красный значок: Ошибка синхронизации (требуется действие пользователя, например, повторная попытка).

Оптимистичный интерфейс (Optimistic UI)

Обновляйте интерфейс сразу после действия пользователя, не дожидаясь ответа сервера.

  • Пример: Пользователь ставит «лайк». Сердечко загорается мгновенно. В фоне отправляется запрос. Если запрос упал (ошибка сети), сердечко возвращается в исходное состояние, и показывается уведомление об ошибке.

Фоновые задачи

Используйте нативные возможности ОС для фоновой синхронизации:

  • iOS: Background Fetch, BGTaskScheduler.
  • Android: WorkManager. Это позволяет обновлять контент, даже если приложение закрыто, но делает это экономно, учитывая заряд батареи.

Частые ошибки при разработке

  1. Игнорирование конфликтов. Разработка ведется в предположении, что конфликты невозможны. При первом же реальном кейсе данные портятся.
  2. Хранение чувствительных данных в открытом виде. Локальные БД часто шифруются недостаточно надежно. Используйте SQLCipher или нативные механизмы шифрования (Keychain/Keystore).
  3. Отсутствие очистки старых данных. Локальная БД может разрастись до гигабайтов, если не реализована политика удаления устаревших или синхронизированных архивных записей.
  4. Блокировка UI при синхронизации. Тяжелые операции сравнения данных не должны выполняться в главном потоке.

FAQ

В чем разница между кэшированием и Offline-First? Кэширование — это временное сохранение данных для ускорения повторного доступа, при этом приложение может не работать без сети. Offline-First предполагает, что приложение полноценно функционирует без сети, используя локальную БД как основную.

Как тестировать офлайн-режим? Используйте эмуляторы с отключением сети, инструменты вроде Charles Proxy для имитации задержек и разрывов соединения, а также режим «В самолете» на реальных устройствах. Обязательно тестируйте сценарии: «офлайн -> изменение -> онлайн -> конфликт».

Нужен ли специальный бэкенд для Offline-First? Желательно, но не обязателен. Специализированные решения (Firebase Firestore, Supabase, AWS AppSync, CouchDB) имеют встроенную синхронизацию. Для обычного REST/GraphQL API вам придется самостоятельно реализовывать логику очереди и разрешения конфликтов.