Использование паттерна data access object в клиентском приложении
Рассказывает Стенли Винтергрин, наш подписчик
Всем привет! В этой статье я поделюсь своим опытом по работе с базой данных в приложении для Android. Это будет один из вариантов реализации паттерна data access object на языке Java.
Постановка задачи
Заказчик, который занимается предстраховыми осмотрами автомобилей, хочет автоматизировать рабочий процесс. Задача приложения — собирать и передавать на обработку данные об автомобиле.
Проектирование показало, что у приложения будет 3 модуля:
- Авторизация.
- Проведение осмотра.
- Синхронизация.
Сущности
Есть сущности, которыми мы будем оперировать в коде, опираясь на техзадание этого проекта и API сервера: пользователи, шаблоны и осмотры. Пользователи получают от сервера шаблон сценария и проводят осмотр. Составленный на основе полученной информации готовый осмотр отправляется на сервер.
Доступ к данным
Выделим для этих сущностей 3 вида доступа. В данном случае это будут следующие интерфейсы:
- UsersAccess — для пользователей;
- ScenariosAccess — для шаблонов сценариев;
- InspectionsAccess — для осмотров.
Далее создадим data access object, который предоставляет доступы. Этот интерфейс будет выглядеть так:
Теперь у нас есть все необходимые виды доступа, но пока они не запрашивают и не изменяют данные.
Модели данных
Создадим модели, которые будут описывать необходимые операции с сущностями. Начнём с пользователей. Что в данном проекте нужно с ними делать? В техзадании сказано, что одним устройством могут пользоваться разные люди, поэтому нужно хранить список пользователей. Также известно, что пользователи ничего не будут знать друг о друге. В таком случае можно создать модель:
Очевидно, что у нас также должны быть модели ScenariosModels и InspectionsModels. Они будут оперировать несколькими сущностями. Шаблон будет состоять из типов этапов осмотра, а типы этапов из типов элементов. Нужно организовать возможность добавлять данные в базу, получать шаблон по уникальному ключу и строить список типов этапов по ссылке на шаблон и список типов элементов по ссылке на тип этапа:
Готовый осмотр будет состоять из элементов, а элементы из данных. Нужно реализовать возможность получения осмотра по ключу, построение списка черновиков осмотров, списка готовых осмотров, а также возможность удалить осмотр. Таким образом, для всех подсущностей осмотра создадим модели:
Теперь передадим доступам их модели:
Теперь наш data access object готов.
Использование data access object
У нас есть три модуля. Каждый из них получит доступ только к той области базы данных, которая необходима для выполнения его задач.
При авторизации будет происходить работа только с пользователями. Поэтому модуль авторизации получит интерфейс UsersAccess. Поскольку необходимы операции только с одной сущностью, можно оставить этой модели доступ только к таблице Users.
При прохождении осмотра нужно опираться на шаблон и на его основе составлять объект осмотра. Придется определять, как отображать этапы осмотра и его элементы, т. е. в этом случае необходим полный доступ ко всем сущностям шаблона и сущностям осмотра. Этому модулю можно передать ScenariosAccess вместе с InspectionsAccess.
Во время синхронизации необходимо получать информацию о готовых осмотрах для передачи на сервер со всеми элементами и прикреплёнными данными. В этот модуль стоит передать только InspectionsAccess.
Для сравнения посмотрите на код, который используется для получения данных, если не использовать DAO:
Обратите внимание, что класс неявно получает зависимость от конкретной реализации SQLite. Более того, он получает доступ ко всем частям базы данных, хотя не должен иметь возможности читать, изменять и удалять данные вне его круга задач. А вот как выглядит реализация аналогичной задачи с применением DAO:
Теперь класс явно зависит только от модели Users
и от модели Inspections
. Стоить заметить, что SQLite нигде не упоминается.
Тестирование
У нас уже описана логика по работе с базой данных. Мы используем её в модулях, хотя реализации работы с данными пока нет. Однако, мы уже можем написать тесты для всех доступов и моделей. Итак, DAO должен успешно передавать доступы (не null
). Точно так же для доступов: они не производят никаких действий, а только передают данные. А вот для моделей можно написать тесты. Например, модель Users изначально ничего не возвращает по ключу, но после добавления объекта станет возвращать новый объект со всеми полями в таком же виде, в котором он был добавлен в базу. По такому же принципу нужно покрыть тестами все модели.
Реализация
Теперь осталось выбрать, с помощью чего реализовать обработку данных для нашего data access object – например, это может быть SQLite. Удобство DAO в том, что мы сможем изменить реализацию логики работы приложения, не затрагивая при этом блок по работе с базой данных. Например, в проекте MyM1y (система учёта финансовых операций) с самого начала для хранения данных был выбран SQLite. В процессе работы над проектом было решено заменить реализацию на более легковесную. Была выбрана другая библиотека — Boxes, и реализация работы с базой данных в проекте была полностью заменена. Это можно наблюдать в коммите painless jump from sqlite to custom nosql orm. Класс stan.mym1y.clean.db.SQLite.java был полностью вырезан со всеми зависимостями и заменён на stan.mym1y.clean.boxes.Boxes.java. Обратите внимание, что это было сделано без изменения модулей по взаимодействию с базой данных.
Заключение
Используя DAO, можно удобно разделять уровни доступа при работе с базой данных, чётко видеть эти уровни доступа и легко оперировать ими, не привязываясь к конкретной реализации хранения данных. Это позволяет применять такой подход вместе с TDD и точнее настраивать реализацию работы с БД (без изменений в других модулях) или полностью заменить одну реализацию на другую.