У каждого разработчика иногда ночью возникают безумные мысли. У меня она была простой:
«Что, если я просто отключу интернет и посмотрю, как поведёт себя приложение?»
И я так и сделал. Я потянулся за стол, выдернул Ethernet-кабель из роутера и понаблюдал, как мигает маленький индикатор Wi-Fi, как будто всё в порядке.
Но моё приложение знало правду.
Экран входа мгновенно заблокировал меня.
В ленте новостей не было ничего, кроме бесконечного спиннера.
Даже страница настроек не работала — как будто для включения тёмного режима нужен интернет.
Вот тут-то меня и осенило.
Моё приложение на самом деле не было приложением. Оно было просто марионеткой, которой управлял интернет, дергая за все ниточки.
А когда я отключил ниточки, марионетка рухнула.
И я принял решение:
Я переделаю своё приложение так, чтобы оно работало только в offline режиме.
Никаких оправданий. Никаких сообщений «попробуйте ещё раз позже». Только локальная архитектура.
И этот выбор изменил всё в моём подходе к разработке приложений.
Когда интернет исчез
Вот что сломалось первым:
- Вход в систему: без сервера я не мог пройти даже приветственный экран
- Вызовы API: каждый список, информация и действие зависели от облака
- Навигация: некоторые экраны работали только при отправке данных API
- Кэширование: мой так называемый «кэш» представлял собой просто остаточный JSON, а не настоящее хранилище
- Обработка ошибок: вместо дружелюбного сообщения «Вы офлайн» я получал пугающие сообщения об ошибках
По сути, моё приложение представляло собой замаскированную вкладку браузера.
Поэтому я принял смелое решение. Я заставлю это приложение работать только в офлайн-режиме.
И вот тут-то и началось настоящее приключение.
Большой сдвиг в архитектуре
Мне пришлось всё переосмыслить. Вот как изменилось моё мышление.
1. От API-First к Local-First
До:
- Каждый экран ждал ответа API
- Сервер был «истиной»
- Локальное хранилище было всего лишь коротким кэшем
После:
- Локальная база данных стала «истиной»
- Каждый фрагмент данных сначала находится на устройстве
- Сервер используется только для синхронизации
Или, проще говоря, моё приложение перестало зависеть от облака. Оно стало работать самостоятельно.
2. Локальная база данных становится главной
Я годами игнорировал базы данных. Теперь они стали моими лучшими друзьями.
- iOS: Я использовал Core Data. Немного сложно настроить, но очень мощно.
- Flutter: Я попробовал Hive для простого хранения и Drift, когда мне нужен был SQL.
Новое правило: в пользовательском интерфейсе всегда отображается то, что находится в локальной базе данных.
3. Слой синхронизации: новое сердце
Это был самый большой сдвиг в мышлении.
Старый способ:
UI -> Call API -> Show Results
Новый способ:
UI -> Save to Local DB -> Queue for Sync -> Push to Server (when online)
Приложение работает даже без интернета. Когда устройство снова подключилось к сети, синхронизатор передал изменения.
4. Разрешение конфликтов: сложная часть
Этот этап заставил меня потерять сон.
- Что, если пользователь редактирует что-то офлайн… а на сервере уже есть новое обновление?
- Чья версия правильная?
Я пробовал разные стратегии:
- Последняя запись выигрывает: просто, но опасно — пользовательские правки могут исчезнуть
- Временные метки и слияние: лучше, но запутанно
- CRDT (замысловатые алгоритмы): слишком сложно для моего случая
В итоге я выбрал сочетание:
- Для текста: выигрывает последнее редактирование
- Для списков: слияние элементов
- Для счётчиков: добавлять значения
Не идеально. Но гораздо лучше, чем молчаливая потеря данных.
5. UX тоже нужно изменить
Офлайн-приложения ощущаются по-другому. Мне пришлось переработать интерфейс.
- Оптимистичный UI: изменения отображаются сразу, не дожидаясь сервера. Значки синхронизации: небольшой символ облака, отображающий состояние синхронизации.
- Оповещения о конфликтах: запросить у пользователя, какую версию он хочет сохранить
- Очереди повторных попыток: обеспечить безопасность операций до восстановления интернет-соединения
Два моих эксперимента: iOS и Flutter
Поскольку я делал приложение и на Swift, и на Flutter, вот как я решил эту проблему.
iOS (Swift + Core Data + Фоновая синхронизация)
// Save note offline first
func addNoteOffline(title: String, content: String) {
let note = NoteEntity(context: context)
note.title = title
note.content = content
note.isSynced = false
saveContext()
queueForSync(note)
}
// Sync later when online
func syncNotes() {
let unsyncedNotes = fetchUnsyncedNotes()
for note in unsyncedNotes {
api.upload(note) { success in
if success {
note.isSynced = true
saveContext()
}
}
}
}
Flutter (Hive + Background Worker)
// Save task offline
void addTask(String title) {
final task = Task(title, false, DateTime.now(), false);
hiveBox.add(task.toJson());
queueForSync(task);
}
// Sync later
Future<void> syncTasks() async {
final tasks = hiveBox.values.where((t) => t['synced'] == false);
for (var task in tasks) {
final success = await api.upload(task);
if (success) {
task['synced'] = true;
hiveBox.put(task['id'], task);
}
}
}
Оба стека следовали одному и тому же правилу.
Локально в первую очередь. Синхронизация потом.
Сюрпризы
Вот чего я не ожидал.
- Производительность улучшилась. Всё работало мгновенно, поскольку пользовательский интерфейс не ждал API.
- Пользователям понравилось. Они могли использовать приложение в самолётах, поездах или зонах с плохой связью.
- Тестирование стало забавным. Я постоянно переключался в режим полёта, как сумасшедший.
- Количество ошибок увеличилось. Ошибки синхронизации — это зло: исправишь одну, и появляется ещё три.
- Моё мышление изменилось. Я перестал говорить: «Это облачное приложение». Теперь я говорю: «Это локальное приложение, которое синхронизируется, когда это возможно».
Уроки
- Создавайте offline-first, и ваш онлайн-режим автоматически станет мощнее
- Синхронизация — это не функция, а базовая архитектура
- Разрешение конфликтов так же важно, как и дизайн
- Уважайте свою локальную базу данных, она ваш лучший друг
- Регулярно тестируйте офлайн, отключите Wi-Fi и посмотрите, что сломается
Заключение
То, что начиналось как глупый эксперимент, превратилось в полную перестройку моего подхода к приложениям.
Заставив своё приложение работать офлайн, я понял его истинную сущность.
Оно стало мощнее, быстрее и удобнее для пользователя.
И я усвоил один важный урок:
Интернет — это не фича. Это роскошь. Офлайн — это выживание.
Так что, если вы разрабатываете приложение в 2025 году, попробуйте один раз. Выключите Wi-Fi, откройте приложение и посмотрите, что получится.
Вот тогда и проявятся настоящие уроки архитектуры.

