A7 Data Server: управление данными онлайн
Привет, Хабр!
Мы пилотируем публикации с расшифровками докладов, прозвучавших на PiterJS.
Так как это первый опыт, будем рады услышать конструктивную критику и предложения по улучшению.
Смотрите видео и слайды, а за расшифровкой — добро пожаловать под кат.
Доклад и расшифровку подготовил Андрей Логинов, технический директор в «A7 Systems».
Начнем
Fast Big Data Server — cервер для больших быстрых данных. Изначально A7 DS предназначен для Digital Twin, управления ролями и паттернами данных. Но это не всё, что он умеет.
Что внутри.
Давайте посмотрим:
- Объектная база данных
- Темпоральная (хронологическая) база данных
- Виртуальная машина JavaScript (если быть честным, то js-like языка)
- Система уровня доступа
- Cервер приложения
Объектная база данных помимо типизации и наследования идеревьевмеет несколько особенностей:
- поддержка деревьев
- поддержка графов
- монтирование и ссылки
- Spaces (пространства)
- поддержка биндинга (реактивность)
Space
Самое необычное здесь — это Space (пространство).
Space — это экземпляр типовой рабочей области.
Space включает:
- data (данные)
- shared-data (общие данные для нескольких пространств. Например, погода или курс валюты)
- roles (owner, users, groups)
То есть, Space является достаточно изолированным от сервера A7 DS и других Spaces пространством.
Как пользоваться
Теперь вопрос: как этим пользоваться. Это наверное самый главный вопрос.
Создадим небольшое мобильное приложение на ECMAScript.
Нам потребуется:
- Минимальное знание С++. (возможно в рамках школьной программы)
- Знание ECMAScript и QML (Qt)
- Android NDK (просто чтобы скомпилировать это)
По моему ощущению, лучшие примеры всегда связаны с деньгами, поэтому попробуем создать онлайн семейный кошелёк)).
Создадим объекты в объектной базе данных. Точнее мы создадим типы. Для описания объектов удобнее использовать редактор, но мы “не такие”, и создадим описание объектов в JSON.
Сначала создадим объект деньги, в котором у нас есть есть наличные, кредитка, и итоговая сумма:
{ "name": "Money", "fields": [ {"name": "card","fieldtype": "value","datatype": "double", "def": 0}, {"name": "cash","fieldtype": "value","datatype": "double","def": 0}, {"name": "sum","fieldtype": "formula","datatype": "double", "def": "card+credit"} ] }
Поля card и cash это простые значения (по умолчанию равные 0), можно записать немного короче:
{"name": "card","value": 0.0}, {"name": "cash","value": 0.0}
Поле sum это формула (привет реактивность!), тоже можно записать немного короче:
{"name": "sum","formula":"card+credit"}
Теперь мы создадим пару из юноши и девушки.
{ "name": "Pair", "fields": [ {"name": "boyfriend","fieldtype": "value","datatype": "Money", "def": "Money"}, {"name": "girlfriend","fieldtype": "value","datatype": "Money","def": "Money"}, {"name": "sum","fieldtype": "formula","datatype": "double", "def": "boyfriend.sum+girlfriend.sum"} ] }
Поле sum (снова привет реактивность!), стало включать ссылки на подобъекты:
{"name": "sum","formula":"boyfriend.sum+girlfriend.sum"}
Теперь, при каждом изменении любой цифры мы автоматически будем получать пересчет текущего баланса.
Но нам полезно добавить немного истории.
{"name": "history","fieldtype": "list", "list":{"datatype": "History"}}
В короткой записи
{"name": "history", "list":{"datatype": "History"}}
Ну и сам объект истории. Кто, что, и сколько изменил.
{ "name": "History", "fields": [ {"name": "who","fieldtype": "value","datatype": "string", "def": “”}, {"name": "which","fieldtype": "value","datatype": "string","def": “”}, {"name": "delta","fieldtype": "value","datatype": "double","def": 0} ] }
Добавим триггеров к Pair:
"functions": [{"functiontype": "before", "arguments": [boyfriend.cash], "code": "..." } ]
И сам код триггера:
{ var historyItem= history.add(new History()); historyItem.who=”boyfriend”; historyItem.which=”cash”; history.delta=value-boyfriend.cash; return true; }
По аналогии добавим триггеры для boyfriend.card
, girlfriend.card
, girlfriend.cash
.
Поскольку мы хотим сделать наше приложение большому количеству пар, то создаем типовое пространство SpacePair
, и делаем его корневым элементом Pair
.
Добавляем двух пользователей по умолчанию
Girl
Boy
Собственно все, генератор пространств для контроля кошельков готов.
Добавим несколько пространств. При добавлении пространства автоматически создается область данных (и сами данные со значениями по умолчанию). Также создаются предустановленные пользователи и группы (для пространства).
Каждое пространство имеет своих пользователей и свои группы.
Начинаем делать клиент:
Добавим в проект библиотеки
android { debug{ LIBS+= ../A7DS/Libs/android/libA17EDboClientBaseBind.a LIBS+= ../A7DS/Libs/android/libA17ClientLibBind.a } release{ LIBS+= ../A7DS/Libs/android/libA17EDboClientBaseBin.a LIBS+= ../A7DS/Libs/android/libA17ClientLibBin.a } }
Немного поправим файл main.cpp
#include <QApplication> #include <QQmlApplicationEngine> #include <QVariant> #include <QQmlEngine> // Добавим ссылку на *.h файлы #include "../A7DS/A17EBase/A17EDboClientBaseBin/a17edboclientbasebin.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; // Создаем клиент для A7DS A17EDboClientBaseBin*client=new A17EDboClientBaseBin(engine,&app); // Инициализируем клиент для A7DS client->init(engine); // Дальше стандартный код engine.load(QUrl(QLatin1String(QString("qrc:/main.qml").toLatin1()))); return app.exec(); }
На этом C++ часть закончена, и мы можем переходить к QML.
Сначала создадим пару компонентов.
Добавим компонент для отображения данных
MyLabelView.qml
import QtQuick 2.7 import Astra.Dbo 17.0 Item {id: viewItem property alias field: field property string label: "что и кто" width: parent.width height: 100 DboField{ id: field } Text { id: labelItem text: viewItem.label anchors.left: parent.left anchors.right: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom } Text { id: valueItem text: field.value anchors.right: parent.right anchors.left: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom } }
MyLabelEdit.qml
import QtQuick 2.7 import Astra.Dbo 17.0 Item {id: viewItem property alias field: field property string label: "что и кто" width: parent.width height: 100 DboField{ id: field } Text { id: labelItem text: viewItem.label anchors.left: parent.left anchors.right: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom } TextInput { id: valueItem text: field.value anchors.right: parent.right anchors.left: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom onEditingFinished:{ field.value=text; } } }
Теперь соберем главное окно
MyLabelEdit.qml
import QtQuick 2.7 import Astra.Dbo 17.0 import QtQuick.Controls 1.5 {id:appWindow visible: true width: 640 height: 480 property var component; property var sprite; ApplicationWindow {id: viewItem property alias field: field property string label: "что и кто" property string host: "127.0.0.1" //// адрес A7 DS property int port: 8989 // порт A7 DS property string isBoy: (dboconnection.login=="Boy") property var myselfMoney: (isBoy)?boyfriend:girlfriend property var myfriendMoney: (!isBoy)?boyfriend:girlfriend /* Данные, которые начнут автоматическую синхронизацию с сервером после соединения */ DboObject{id:boyfriend parentObject: rootData parentFieldName: "boyfriend" } DboObject{id:girlfriend parentObject: rootData parentFieldName: "girlfriend" } DboModel{id:history parentObject: rootData parentFieldName: "history" } /* То, что мы отображаем, если нет соединения с сервером A7 DS */ Column{ z: 10 visible: (! dboconnection.isConnect) anchors.fill: parent TextInput{id:login width: parent.width height: 100 } TextInput{id:password width: parent.width height: 100 } Button{id:btn width: parent.width height: 100 text: ”Подключиться” onClicked: dboconnection.connectToDbo( login.text, password..text, viewItem.host, viewItem.port); } } SwipeView{ anchors.fill: parent currentIndex: 1 /// Здесь таблица отражающая историю изменений Page{ ListView{ model: history delegate: Text{ text: model.who+” ”+model.which+” ”+model.delta } } } /// Здесь список дающий текущую картину Page{ Column{ anchors.fill: parent MyLabelEdit{id:myCash; label: “мои наличные” field.name: “cash”; field.parentObject: myselfMoney } MyLabelEdit{id:myCard; label: “моя карта” field.name: “card”; field.parentObject: myselfMoney } MyLabelView{id:mySum; label: “все мои деньги” field.name: “sum”; field.parentObject: myselfMoney } MyLabelView{id:myfriendCash; label: “наличные друга” field.name: “cash”; field.parentObject: myfriendMoney } MyLabelView{id:myfriendCard; label: “карта друга” field.name: “card”; field.parentObject: myfriendMoney } MyLabelView{id:myfriendSum; label: “все деньги друга” field.name: “sum”; field.parentObject: myfriendMoney } MyLabelView{id:mypairSum; label: “все наши деньги” field.name: “sum”; field.parentObject: mypairMoney } } } } } Text { id: labelItem text: viewItem.label anchors.left: parent.left anchors.right: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom } TextInput { id: valueItem text: field.value anchors.right: parent.right anchors.left: parent.horizontalCenter anchors.top: parent.top anchors.bottom: parent.bottom onEditingFinished:{ field.value=text; } } }
Эмм. “А как же обещанные Digital Twin, и прочие ништяки?” – спросит внимательный читатель.
“Биндинг это конечно хорошо, но где монтирование и графы?” – добавит он.
Это справедливые вопросы, и на ответы на них будут дан в следующих статьях ;).

