База данных для мобильного приложения: критерии выбора архитектуры
В каждом втором ТЗ на мобильное приложение есть магическая строчка: нужна база данных. Без уточнений и контекста, будто за ней не скрывается целый архитектурный пласт.
Ратмир Чеботарев·Обновлено: 10 октября 2026 г.·14 мин

Разработчики кивают, закладывают SQLite по умолчанию, а через полгода приложение начинает тормозить, терять введённые данные при плохой сети и требовать синхронизации, которую никто заранее не спроектировал.
Выбор архитектуры хранения данных не сводится к вопросу, какая БД круче. Здесь переплетаются офлайн-режим, связи между сущностями, объёмы данных на устройстве, транзакции, безопасность и стоимость развития. Универсального ответа нет. Как бы ни хотелось услышать от менеджера «поставьте Firebase и забудьте», забыть не получится: придётся определить, что именно хранится на устройстве, что считается актуальной версией и что происходит при конфликте изменений.
База данных для мобильного приложения и выбор архитектуры начинаются с поведения продукта. Приложение должно открываться без сети? Пользователь может менять одни и те же данные на нескольких устройствах? Насколько важна согласованность записей, например, при оплате или оформлении заказа? Ответы на эти вопросы часто важнее названия конкретного движка.
Локальное хранение: SQLite, Room и специфика работы с данными на устройстве
Когда говорят о локальной базе данных в мобильной разработке, чаще всего имеют в виду SQLite. Это встраиваемая реляционная СУБД, которая не требует отдельного серверного процесса и может хранить данные в приватной области приложения. На Android SQLite доступна как часть платформы. Для iOS есть свои инструменты, в том числе Core Data, а в кроссплатформенных проектах выбор зависит от стека. Общий принцип один: локальные данные находятся рядом с приложением и доступны ему без сетевого запроса.
Сильная сторона SQLite в предсказуемости. Схема, SQL-запросы, индексы и транзакции помогают работать с данными, где важны связи и целостность. Если домен состоит из пользователей, заказов, позиций заказа и статусов, реляционная модель обычно ложится естественно. Можно явно описать отношения между таблицами и поддерживать ограничения на уровне базы, а не надеяться, что каждый участок кода соблюдёт договорённость.
На Android поверх SQLite часто используют Room из Android Jetpack. Он предоставляет слой DAO, помогает описывать запросы и проверяет SQL во время сборки. Например, запрос, связанный с методом аннотированного интерфейса, можно проверить до запуска приложения. Room также упрощает работу с миграциями схемы. Это не отменяет необходимости продумывать, как приложение перейдёт со старой версии базы на новую, но делает этот процесс явнее.
Core Data и Room не стоит считать прямыми аналогами. Room работает поверх SQLite, а Core Data управляет объектным графом и предоставляет собственную модель взаимодействия с данными. При выборе для конкретной платформы важно учитывать не только возможности инструмента, но и привычные для команды подходы, требования к миграциям и то, как устроен остальной код приложения.
Отдельный вариант для локального хранения данных на мобильном устройстве — Realm. Это объектно-ориентированная база данных: приложение работает с объектами, а не с SQL-таблицами напрямую. Такой подход может упростить чтение и наблюдение за изменениями объектов. При этом Realm приносит собственный движок и особенности работы со схемой. Если в будущем команда решит перейти на другое хранилище, потребуется продумать перенос данных и заменить участки, завязанные на его API.
Локальная БД задаёт форму данных, с которой приложение будет жить на устройстве. Поэтому выбирать её стоит с учётом не только первого релиза, но и вероятных изменений продукта.
Есть и менее эффектная, но важная сторона локального хранения: жизненный цикл данных. База обычно находится в области, доступной приложению, однако переустановка, очистка данных или смена устройства могут повлиять на то, что пользователь увидит после запуска. Перенос между устройствами зависит от платформенных механизмов резервного копирования и настроек приложения. Если пользовательские данные должны переживать переустановку, одной локальной БД недостаточно: нужно определить, где находится резервная копия и когда она обновляется.
Стоит заранее решить и вопрос удаления. Если пользователь выходит из аккаунта, нужно ли очищать локальную базу? Что делать с незавершёнными изменениями? Можно ли хранить офлайн-копию после истечения сессии? Эти решения связаны и с требованиями продукта, и с безопасностью. Нельзя просто считать, что всё, что записано в приватную папку приложения, автоматически безопасно при любом сценарии.
Производительность мобильных баз данных тоже зависит от повседневных деталей. Большая таблица сама по себе не означает медленное приложение, как и выбор определённого движка не гарантирует быстрый интерфейс. На отклик влияют структура запросов, индексы, объём чтения, способ загрузки данных и то, выполняются ли операции вне главного потока. Если экран каждый раз загружает всю историю вместо нужной страницы или повторяет одни и те же запросы, смена SQLite на другую технологию проблему не устранит.
NoSQL против реляционных моделей: когда гибкость важнее жёстких связей
Спор о том, что лучше для мобильного приложения, SQL или NoSQL, решается контекстом. Реляционные базы удобны, когда у данных понятная структура, важны связи между сущностями и требуется явно поддерживать целостность. NoSQL объединяет разные модели хранения, поэтому само слово мало говорит о поведении конкретной системы. Здесь важно смотреть на то, как устроены запросы, транзакции и синхронизация именно в выбранном продукте.
Например, Cloud Firestore хранит данные в виде коллекций и документов. Firebase Realtime Database устроена иначе: это JSON-дерево, изменения которого можно передавать подписчикам в реальном времени. Обе системы относятся к облачным решениям Firebase, но Realtime Database не следует описывать как документную БД в том же смысле, что Firestore. Realm, в свою очередь, относится к объектно-ориентированным базам данных, а не к документным.
Документная модель может быть удобна для профилей с меняющимся набором полей, контента или объектов, которые приложение обычно читает целиком. Но гибкость схемы не отменяет необходимости проектировать структуру. Если один и тот же фрагмент данных копируется в несколько документов, при изменении его придётся обновлять в нескольких местах. Если приложение часто запрашивает связанные сущности по разным условиям, схема, удобная для одной операции чтения, может оказаться неудобной для остальных.
| Параметр | Реляционные решения, например SQLite и Room | Документная модель, например Cloud Firestore |
|---|---|---|
| Структура | Таблицы и схема, которую нужно менять при развитии модели | Коллекции и документы с полями, которые можно расширять |
| Связи | Внешние ключи и запросы между таблицами | Данные вкладывают друг в друга или связывают идентификаторами |
| Подход к чтению | Можно собирать результат из связанных таблиц | Часто структуру подстраивают под основные сценарии чтения |
| Транзакции и согласованность | Возможности зависят от СУБД и способа её использования | Зависят от конкретной системы и её гарантий |
| Типичные задачи | Учётные данные, заказы, связанные сущности | Профили, контент и объекты с изменяемой структурой |
У документных баз нет единого правила согласованности, которое автоматически распространялось бы на все продукты. Гарантии зависят от конкретной системы, операции и конфигурации. Поэтому выражение «документная модель с eventual consistency» слишком общее, чтобы на него опираться при проектировании. Нужно выяснить, что именно гарантирует выбранная БД при чтении и записи, как ведут себя транзакции и что увидит пользователь при одновременном изменении данных.
Это особенно важно для финансовых операций, остатков товаров и других сценариев, где две версии записи не должны незаметно разойтись. Здесь следует отдельно определить, какая система считается источником истины, где проверяются ограничения и как приложение обрабатывает отказ или повтор запроса. Выбор NoSQL сам по себе не означает ни слабой, ни сильной согласованности: ответ находится в документации и архитектуре конкретного решения.
Сравнение SQLite и Realm для Android тоже не сводится к таблице «SQL против объектов». SQLite хорошо знакома многим разработчикам, позволяет писать SQL и в связке с Room даёт инструменты для запросов и миграций. Realm предлагает объектный способ работы с данными, который может сократить часть связующего кода, но привязывает приложение к собственному API и модели. Для оценки стоит взять реальные операции из приложения: что нужно загрузить на экран, что часто меняется, какие связи есть между объектами и как данные должны переживать обновление схемы.
Частая ошибка — выбрать NoSQL из-за моды, а затем пытаться воспроизвести реляционные запросы через цепочку чтений и ссылок. В итоге команда получает дублирование данных, дополнительную логику согласования и ограничения конкретного сервиса, но не получает преимуществ, ради которых выбирала такую модель. Бывает и обратное: сложную изменяемую структуру пытаются уместить в реляционную схему, не определив, какие поля действительно стабильны.
Архитектурные паттерны: изоляция слоя данных через Clean Architecture
Выбор базы становится менее рискованным, если остальная часть приложения не знает о её конкретном движке. В Clean Architecture и близких подходах для этого используют слой данных и репозитории. Бизнес-логика обращается к описанным приложением операциям, например, получить пользователя, сохранить заказ или наблюдать за изменениями. Реализация под интерфейсом уже решает, откуда брать данные: из Room, Realm, сети или их сочетания.
Такое разделение не означает, что движок можно будет заменить одной строчкой. При смене БД всё равно потребуется перенести данные, переписать запросы, проверить поведение при ошибках и обновить тесты. Польза в другом: детали хранения не расползаются по экранным компонентам и бизнес-правилам. Изменения локализуются в слое, который отвечает за доступ к данным.
Это особенно заметно, когда появляется синхронизация. В простом приложении данные могли читаться из локального источника. Позже добавляется сервер, очередь несинхронизированных изменений и обработка конфликтов. Если вызовы Room или Firestore разбросаны по интерфейсу, новые требования затрагивают множество несвязанных участков. Репозиторий даёт место, где можно договориться о том, как складываются локальное и удалённое состояния.
Но архитектурная абстракция не должна превращаться в церемонию. Маленькому приложению с несколькими экранами и простым локальным сценарием не всегда нужны многочисленные интерфейсы, фабрики и слои. Если абстракция не упрощает тестирование или развитие, она становится ещё одной конструкцией, которую команда обязана поддерживать.
Разумная граница зависит от масштаба приложения и ожидаемых изменений. Если у продукта несколько источников данных, сложная синхронизация, развитая бизнес-логика или несколько команд, изоляция часто окупается. Если проект небольшой и данные сводятся к простой настройке или одному локальному списку, можно начать проще, сохранив понятное место для доступа к хранилищу.
Полезно также разделять модели. Объект, который удобно показывать на экране, не обязательно должен один в один совпадать со структурой таблицы или документа. Слой данных может преобразовать запись в модель приложения. Это снимает часть зависимости UI от схемы и помогает обрабатывать различия между локальными и серверными данными. Однако создавать отдельную модель для каждого поля без практической причины тоже не нужно: границы должны решать конкретную задачу.
Облачные решения и BaaS: синхронизация в реальном времени и офлайн-режим
Если данные должны переживать смену устройства, быть доступны нескольким пользователям или синхронизироваться между клиентами, одной локальной базы недостаточно. Здесь появляются облачные решения и BaaS. Firebase предлагает Realtime Database и Cloud Firestore, Supabase предоставляет платформу на базе PostgreSQL. Это разные продукты с разными моделями данных и возможностями, поэтому сравнивать их лучше по требованиям приложения, а не по общему ярлыку «облако».
Облачная vs локальная база данных — не обязательно выбор одного из двух вариантов. Часто приложению нужны оба слоя. Локальное хранилище отвечает за быстрый доступ и работу без сети, облако хранит данные, которые должны быть доступны между устройствами или пользователями. Тогда возникает главный архитектурный вопрос: кто считается источником истины и как локальные изменения добираются до сервера.
Офлайн-возможности зависят от конкретного SDK и его настроек. Некоторые сервисы умеют кэшировать данные на устройстве и отправлять локальные изменения после восстановления соединения. Это избавляет от части рутинной работы, но не решает за команду все вопросы синхронизации. Нужно понимать, какие данные доступны офлайн, как долго сохраняются локальные изменения и что происходит, если пользователь изменил запись на другом устройстве.
Сложнее всего становится при конфликте. Например, один пользователь изменил имя записи на телефоне, а затем на планшете отредактировал ту же запись в другой версии. Система должна выбрать, какое изменение принять, или предоставить способ объединить их. Правило может зависеть от типа данных: для заметки подойдёт один подход, для заказа или платёжного статуса нужен другой. Универсального разрешения конфликтов нет, и сам факт наличия синхронизации в реальном времени не отвечает на этот вопрос.
Офлайн-режим не сводится к кэшированию. Он включает правила записи, повторной отправки и разрешения конфликтов, когда связь вернётся.
BaaS снимает часть забот о серверной инфраструктуре и ускоряет запуск. Но это не значит, что подключение SDK автоматически создаёт завершённую архитектуру. Команде всё равно нужно настраивать права доступа, описывать модель данных, учитывать ограничения запросов и понимать, как рассчитывается стоимость операций. В приложении с частыми обновлениями или многочисленными подписками расходы могут зависеть от того, сколько чтений и записей выполняет реальный пользовательский сценарий. Это стоит оценивать на модели использования, а не по одному только бесплатному тарифу.
Собственный backend с API и базой вроде PostgreSQL или MongoDB даёт больше контроля над логикой и тем, как устроены запросы. Одновременно он требует разработки и поддержки серверной части, мониторинга, обновлений и обработки ошибок. BaaS обычно помогает быстрее запустить типовые сценарии, но может ограничить команду особенностями платформы. Выбор зависит от ресурсов, требований к данным и готовности поддерживать инфраструктуру.
Синхронизацию удобно проектировать как отдельный поток, а не как побочный эффект сохранения. Приложение должно понимать, какие изменения ожидают отправки, какие уже подтверждены сервером и какие завершились ошибкой. Для повторных запросов важно предусмотреть поведение, при котором повтор операции не создаст нежелательный дубль. Например, повторная отправка изменения заказа должна обрабатываться с учётом того, что сервер мог принять первую попытку, даже если клиент не получил ответ.
Полезно определить и поведение интерфейса. Пользователю нужно сообщить, что изменение сохранено локально, но ещё не синхронизировано? Что произойдёт, если отправка долго не удаётся? Можно ли продолжать работу с устаревшими данными? Эти вопросы не решаются выбором Cloud Firestore или Supabase. Они относятся к продуктовой логике, которую нужно выразить в архитектуре приложения.
Безопасность взаимодействия: почему прямой доступ к серверным БД — антипаттерн
Мобильному приложению не следует подключаться напрямую к серверной реляционной базе вроде MySQL или PostgreSQL. Для взаимодействия с ней обычно используют backend API, который проверяет пользователя, применяет бизнес-правила и решает, какие операции допустимы. Прямой доступ с клиента затрудняет контроль над запросами и создаёт лишнюю поверхность атаки.
Важна точность: сама по себе строка подключения, попавшая в APK, не гарантирует злоумышленнику доступ к базе и не означает, что он сможет получить её дамп. Для доступа нужны подходящие учётные данные, сетевой маршрут и разрешения. Но если клиент содержит секреты с избыточными правами, а серверная БД доступна извне, последствия утечки могут быть серьёзными. Секреты, которые должны оставаться только на сервере, нельзя считать защищёнными лишь потому, что они находятся внутри приложения.
Обфускация может усложнить анализ клиентского кода, но не заменяет серверную авторизацию и настройку прав. Насколько она затруднит исследование, зависит от приложения и инструментов, доступных проверяющему. Поэтому защита строится не на обещании скрыть всё внутри APK, а на минимальных привилегиях, проверке запросов на сервере и ограничении сетевого доступа к базе.
Здесь есть важное различие между прямым доступом мобильного клиента к обычной серверной БД и клиентским SDK облачного сервиса. Например, Firebase и другие BaaS предусматривают клиентское взаимодействие, но безопасность в таком случае зависит от настроенных правил доступа, аутентификации и ограничений на данные. Подключить SDK недостаточно. Если правила разрешают пользователю читать или менять больше, чем ему положено, сам факт использования облачной платформы эту проблему не исправит.
Масштабирование тоже не сводится к числу установок приложения. Количество пользователей не равно количеству одновременных соединений с базой: клиент может обращаться к backend API, а серверная часть уже управляет пулом подключений и распределением нагрузки. Сможет ли такая система выдержать конкретный пик, зависит от конфигурации API, пула, базы, кэширования и характера запросов. Поэтому опасен не сам факт того, что приложение стало популярным, а архитектура, которая не рассчитана на реальные профили нагрузки.
Взаимодействие через backend API позволяет централизовать проверку прав и бизнес-правила. Это может быть REST, gRPC или GraphQL. Существенно, чтобы сервер проверял пользователя и каждую операцию, а база данных не была открыта мобильным клиентам напрямую. Клиенту также нужны защищённое сетевое соединение и корректная работа с токенами авторизации.
К типовым проблемам относятся:
- секреты с расширенными правами, встроенные в клиентский код;
- открытые наружу порты базы, оставшиеся после разработки;
- серверные правила, которые доверяют идентификатору пользователя, присланному клиентом, без проверки авторизации;
- передача произвольных SQL-запросов с клиента;
- отсутствие защищённого соединения при передаче чувствительных данных.
У этих ситуаций нет единого исправления, но общий принцип прост: база должна принимать запросы от доверенного серверного слоя или от SDK с тщательно настроенными правилами доступа. Сервер проверяет полномочия, валидирует входящие данные и применяет ограничения, которые нельзя поручать мобильному интерфейсу. Клиент может быть удобным и хорошо написанным, но его код всё равно находится у пользователя и не является границей безопасности.
Архитектура начинается с поведения данных
Универсальной СУБД, которая выигрывает во всех мобильных сценариях, нет. SQLite с Room хорошо подходит для структурированных локальных данных на Android. Realm может быть уместен, когда команде подходит объектная модель и её особенности. Firestore и Realtime Database решают разные задачи в экосистеме Firebase, а Supabase предлагает иной набор возможностей на базе PostgreSQL. Название технологии не заменяет проектирование.
Для выбора полезно сначала описать не список желаемых функций, а поведение данных: что должно работать без сети, где хранится актуальная версия, кто и когда может менять запись, что происходит при конфликте и какие операции требуют строгой проверки на сервере. Затем становится понятнее, нужна ли локальная база, облачный сервис, собственный backend или их сочетание.
Изоляция слоя данных помогает развивать решение без распространения деталей конкретной БД по всему приложению. Но она должна соответствовать масштабу проекта. Синхронизация требует правил, а не только подписки на обновления. Безопасность опирается на серверную проверку и права доступа, а не на надежду скрыть строку подключения. Эти решения определяют архитектуру сильнее, чем выбор самого модного движка.
Если в ТЗ снова написано только «нужна база данных», его стоит вернуть с вопросами. Что пользователь должен видеть без сети? Какие данные должны пережить смену устройства? Что произойдёт, если две версии записи изменят одновременно? Ответы сэкономят команде больше времени, чем выбор между двумя библиотеками в начале проекта.