MobiDB Database Designer – приложение для создания реляционных баз данных. Предназначено как для бизнеса, так и для повседневного использования, поддерживает синхронизацию и много пользователей. Позволяет хранить различные типы данных. Подходит для планирования, управления проектами, формирования счет-фактур, организации доставки, инвентаризации, ведения пациентов в больнице и т.п.
Идея проекта
Идея проекта заключалась в создании дизайнера базы данных с таблицами и соответствующих форм просмотра/ввода на тач-устройствах. При этом хотелось, чтобы было удобно и просто создавать базу данных. В итоге решили дизайнить базу на основе формы. Т.е. накидывалась форма ввода данных, на ее основе создавалась база данных и карточки для ввода этих данных. Удаление/добавление контрола приводило к удалению/добавлению колонки в базе данных.
Выбор технологии
Проект изначально предусматривалось делать для нескольких мобильных платформ (для начала android, позже планировали сделать windows store приложение). Писать нативный код означало писать код для всех платформ с нуля. Использовать кроссплатформенные решения (чтобы UI тоже был общим), не хотелось. Хотелось, чтобы приложение было “родным” на каждой платформе. Имея опыт .NET разработки и изучив возможные кроссплатформенные решения, мы выбрали Xamarin. Можно было написать общий код и разный UI, чего мы и хотели. Данная статья не будет касаться специфики Xamarin, а посвящена архитектурным вопросам.
Разрабатывали проект последовательно, сначала модель данных, затем слой данных, после этого UI часть. Описание UI части не входит в рамки данной статьи.
Базу нужно было где-то хранить, клиент должен был работать offline, поддерживаться всеми основными платформами, поэтому ничего лучшего, чем SQLite не нашлось.
Модель
Наша база данных подразумевала наличие высокоуровневых типов данных. Т.е. не числа и строки, а к примеру валюта, почта, рейтинг, ссылка, координаты и т.п. В общем случае это не проблема: обычно для работы с базой данных делают классы модели и мапят её на БД. В java есть hibernate, spring persistence; в .net Entity framework, nhibernate. Однако вспомним, что мы говорим про редактор базы данных, и модель может меняться пользователем. Т.е. про мапинги и адаптеры таблиц (как в .net dataset) следует забыть. Возникла проблема, как организовать хранение данных? В нашей идеологии аналог маппингов представляет схема данных, поддерживающая динамическую модель.
Далее, в SQLite очень ограниченный набор типов:
- TEXT
- NUMERIC
- INTEGER
- REAL
- BLOB
Нужно было каким-то образом хранить наши данные в этом ограниченном наборе. Заметим, что поле валюта будет иметь один и тот же знак валюты, рейтинг будет иметь одинаковый шаг и максимум для всех рядов.
Если хранить описание типа вместе со значением, то получится избыточность данных, так как эти данные будут дублироваться для всех строк. Нужно выносить эту информацию в отдельную сущность. Также не все наши типы соответсвуют типам SQLite. Например, поле Location это два числа широта и долгота. Можно было, конечно, хранить их в разных колонках, но это нелогично, так как это неделимая информация, не представляющая смысла в отдельности. Поэтому также было необходимо выполнять конвертацию из типов данных SQLite в данные модели. Данную сущность назвали MetaType, а данные модели MetaValue.
В обязанности MetaType входит преобразование типов данных SQLite в типы модели, а также хранение настроек, характерных для всей колонки.
Пример диаграммы классов RatingMetaType:
Пример реализации TimeMetaType
public class TimeMetaType : TextualMetaType
{
public override string ConvertToString(MetaValue value)
{
var timeValue = value as MetaValue<DateTime>;
if (timeValue == null)
{
throw new ArgumentException("MetaValue<long> was expected");
}
return string.Format("{0:t}", timeValue.Value);
}
public override ElementaryType GetElementaryType()
{
return ElementaryType.String;
}
public override MetaValue LoadFromElementaryType(object value)
{
var dateValue = DateTime.Parse((string)value);
return new MetaValue<DateTime>(this, new DateTime(1, 1, 1, dateValue.Hour, dateValue.Minute, dateValue.Second));
}
public override object SaveToElementaryType(MetaValue value)
{
var concreteValue = value as MetaValue<DateTime>;
if (concreteValue == null)
{
throw new ArgumentException("MetaValue<DateTime> was expected");
}
return concreteValue.Value.ToString("HH:mm:ss");
}
public override bool TryParse(string stringValue, out MetaValue value)
{
DateTime result;
value = null;
if (!DateTime.TryParse(stringValue, out result))
{
return false;
}
value = new MetaValue<DateTime>(this, result);
return true;
}
}
Каждой базе данных в нашем приложении соответствует SQLite база данных. Данные хранятся в обычных таблицах SQLite. Здесь мы вспомним про нашу динамическую модель, которую тоже нужно сохранять в базе данных. Мы назвали эту сущность Template (шаблон). Шаблон умеет сохраняться в бинарные данные/загружаться из бинарных данных. Шаблон описывает набор табличных схем, схема описывает коллекцию колонок, а у колонки в свою очередь есть метатип.
Преобразование шаблона в byte[] сделано через сериализацию и пометку классов/свойств аттрибутами. Мы используем наш собственный xml-сериализатор. Естественно можно было использовать встроенные в .NET сериализаторы.
Разобрались с метаданными, перейдем к самим данным. Таблица – хранит коллекцию рядов, ряд хранит значения. Значения хранятся в виде MetaValue. Из MetaValue можно узнать метатип и определить, что это за метатип. У таблицы есть схема таблицы. Именно эта связь превращает наши сырые данные в более высокоуровневые данные. В связке они и образуют модель. Читая метаданные о колонках, можно перебирать значения в DataRow.
С данными и метаданными мы разобрались, а теперь вспомним, что все это предполагалось генерировать на основе формы. В схеме также хранится форма редактирования с контроллами:
Заметим, что есть еще контрол формы вложенная таблица, для обеспечения жизнедеятельности в Table и в DataRow есть ParentId.
Что касается связи с родительской таблицей, то этот метатип задается двумя значениями: именем таблицы и именем колонки. RelationMetaType умеет возвращать описание связанной записи и связанное значение. Связанная запись определяется RowId. Таким образом из RelationMetaType можно узнать имя таблицы, взять ее из шаблона, вытащить ряд из связанной таблицы по RowId и получить доступ ко всем значениям связанной записи.
Слой данных
На момент старта проекта не было нормальной поддерки PCL, поэтому использовали Mono.android библиотеку. Позднее слой данных переписали, поскольку появилась SQLite PCL:
sqlitepcl.codeplex.com
В SQLite PCL достаточно специфичное API. Подробнее можно посмотреть здесь marcominerva.wordpress.com/2014/02/13/the-new-portable-class-library-for-sqlite
Для бизнес логики мы сделали специальную абстракцию IDatabaseManager. В нашем случае изолюрующий слой данных и возвращающий/принимающий сущности модели:
Ввиду большого объема и нехитрой логики реализации слоя данных не буду приводить реализацию SqliteDatabaseManager на основе SQLite PCL.
В результате у нас получилось довольно мощное приложение, где пользователь может самостоятельно создавать сложную структуру данных в соответствии со своими задачами.Оценить реализацию вы можете, загрузив приложение из Google Play:
https://play.google.com/store/apps/details?id=com.perpetuumsoft.mobidb.lite
или из Windows Store:
https://www.microsoft.com/store/apps/9nblggh1jp5v
Комментарии (9)
gcnew
13.01.2016 15:00Что касается конкурентов, то на ios есть TapForms, на android есть memento, в windows store сильных конкурентов нет. Есть еще FileMaker, у него раньше было приложение Bento. Самое сложное в данном типе приложений — сделать его простым и мощным, что невозможно, так как одно другому противоречит. Этим все приложения и страдают: одни слабые но удобные, другие мощные и неудобные (для гиков).
Terranz
14.01.2016 11:54я так и не понял, а что в результате то выдаёт программа?
gcnew
14.01.2016 12:03Позволяет создавать/вести базы данных. При этом не требуется знание SQL и т.п. База создается через WYSWYG designer с drag and drop.
Terranz
14.01.2016 12:04я не об этом) что это программа для проектирования БД я понял
а вот что на выходе получается? SQL файл со структурой, или файл sqlite базы данных, или что?
мы же не проектируем базу данных ради проектирования базы данных, её же надо куда-то подключить, где-то использоватьgcnew
14.01.2016 12:07Хранится все это в SQLite базе. Базе соответствует 1 файл. В файле есть данные, а также системная таблица с записью о метаданных.
gcnew
14.01.2016 12:21Вообще в полученную базу лучше руками не залазить. Для работы с ней мы предлагаем 2 клиента, синхронизацию между клиентами, импорт/экспорт, печать.
Terranz
14.01.2016 12:33а, так это библиотека просто?
gcnew
14.01.2016 12:46Я бы не сказал, что это библиотека. Как оно внутри хранится — это особенности реализации. Это скорее СУБД поверх другой СУБД. Мы бы могли с тем же успехом использовать свой формат для хранения наших данных (например xml) или сервис Parse.com с оффлайн работой. Причины не залазить в саму базу следующие:
- названия таблиц — UniqueName, название колонок — UniqueName. Нормальные названия лежат в Blob поле системной таблице в зазипованом xml.
- Для синхронизации ведется специальный журнал изменений, поэтому если поменять поле, а в журнал не записать, то нарушится целостность данных. На двух синхронизированных клиентах могут получиться разное состояние данных.
gentee
Каких конкурентов знаете?
У меня есть на github похожий по сути проект (создание и работа с таблицами для «чайников»), только он устанавливается на web-сервере — нужны только PHP и MySQL, ну и бесплатный.