Использование паттерна data access object в клиентском приложении

Рассказывает Стенли Винтергрин, наш подписчик 

Всем привет! В этой статье я поделюсь своим опытом по работе с базой данных в приложении для Android. Это будет один из вариантов реализации паттерна data access object на языке Java.

Постановка задачи

Заказчик, который занимается предстраховыми осмотрами автомобилей, хочет автоматизировать рабочий процесс. Задача приложения — собирать и передавать на обработку данные об автомобиле.

Проектирование показало, что у приложения будет 3 модуля:

  1. Авторизация.
  2. Проведение осмотра.
  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 и точнее настраивать реализацию работы с БД (без изменений в других модулях) или полностью заменить одну реализацию на другую.