- PVSM.RU - https://www.pvsm.ru -
В Meteor любая работа с даными связана с двусторонней реактивностью. На данный момент 100% реактивностью обладают встроенная в Meteor MongoDB и Redis (оба драйвера разработаны в стенах Meteor), частично реактивность реализована для MySQL и MSSQL (сторонними разработчиками).
Для вышеуказанных баз данных реактивность реализована посредством observer'ов, которые сообщают где, как, когда и какие данные изменились, для того чтобы драйвер, обслуживающий связь [данные <-> представление], знал какие данные и у каких клиентов обновить. Neo4j лишен каких-либо watcher'ов и observer'ов, но это нас не остановило. Как мы вышли из данной ситуации и зачем нам нужен Neo4j читайте под катом.
Под node.js имеется официальный и одноименный npm-пакет от производителей Neo4j, который изобилует функционалом, но в первых же двух строках его документации мягко намекается, что стабильно работает только подключение к базе GraphDatabase и метод query.
Считаю, что под каждую задачу должен быть свой инструмент, созданный и предназначенный для выполнения данной задачи на высшем уровне. Всем известно, что один гвоздь можно, а иногда и нужно (если потраченное время на поиск или средства на покупку молотка неоправданно завышены) забить тапком. Но для 100 и тем более 100000 гвоздей будет целесообразно обзавестись молотком, а лучше гвоздеметом. В нашем случае нам необходимо хранить и получать данные отношений между записями. Сами данные в денормализованном виде мы продолжаем хранить в MongoDB, а вот отношения этих данных мы храним в Neo4j.
Изначально предполагалось, что создав глобальную переменную, держащую в себе объект типа GraphDatabase, функционала, поставляемого в npm-пакете, будет достаточно для поставленных задач: на тот момент мы писали/считывали данные в/из базы (без реактивности). Так родился neo4jdriver — пакет, содержащий в себе глобально доступный класс Neo4j, при инициализации которого создается соединение с базой, запущенной локально или удаленно. При инициализации можно передать единственный параметр url.
Позже появилась потребность в:
Тут-то и началось самое интересное.
Вторым на свет вышел пакет neo4jreactivity, основанный на принципе псевдо-реактивности, реализуемой через прослойку в виде MongoDB. Проще говоря, на любой запрос в Neo4j, мы возвращаем — MongoCursor, который в свою очередь является источником реактивных данных или, как это принято называть в Meteor комьюнити: REACTIVE DATA SOURCE.
Изначально все казалось просто:
На момент релиза одной из первых версий драйвера, на Клиенте Вы могли запустить абсолютно любой запрос, т.е. могли изменить, получить или стереть все данные хранимые в Neo4j. Данная проблема была решена посредством введения методов neo4j.methods({}) и neo4j.call(methodName, opts, callback), которые работают по принципу стандартного Meteor.methods({}), пример:
if(Meteor.isServer){
neo4j.methods({
‘GetUser’: function(){
return ‘MATCH (n:User {_id: {userId}}) RETURN n’
}
});
};
if(Meteor.isClient){
neo4j.call(‘GetUser’, {userId: 123}, function(error, data){
if(!error){
Session.set('theUser', data.get());
}
});
}
Второе, что мы сделали — это property neo4j.allowClientQuery, которая принимает значение true и false, и по умолчанию имеет значение false. Это позволит разработчикам на время разработки и тестирования приложения работать в консоли браузера, отправлять данные и проверять полученные.
Если же вы по какой-то причине решите оставить возможность исполнения запросов к Neo4j с Клиента, то предусмотрен следующий функционал, позволяющий ограничить тип запросов к Neo4j. Вам доступны два метода: neo4j.set.allow и neo4j.set.deny. Оба метода принимают единственный параметр — массив строк (array of strings). Дополнительно Вам доступны массивы: neo4j.rules.allow, neo4j.rules.deny и neo4j.rules.write, которые содержат текущие правила, а последний содержит массив с операторами записи, что позволяет сделать вот такой шорткат:
if(Meteor.isClient){
neo4j.set.deny(neo4j.rules.write);
}
И запретить все запросы на запись со стороны Клиента. Все методы, описанные в параграфе выше, — изоморфны. Хак со стороны Клиента не пройдет, так как данные дополнительно проверяются на целостность на стороне Сервера.
Позже было обнаруженно, что запросы на изменение данных не инициировали обновление данных на Клиентах. Реактивность просто не работала до момента, пока один из Клиентов не обратится к измененным данным и не инициирует обновление в MongoDB, и как следствие — на всех Клиентах. Это произошло по причине того, что у нас не было observer'а, который бы следил за измененными данными и инициировал запуск всех запросов, связанных с данными, которые изменились.
Возвращаемся к нашему идеальному пакету под названием neo4jdriver, стираем весь проект и пишем заново:
Первым делом нам необходимо научиться отделять данные запроса от конструкции запроса, для этого был введен параметр sensitivities. Этот параметр содержит данные, которые могут быть изменены. Теперь запись в коллекции Neo4jCacheCollection выглядит следующим образом:
uid // Unique hashed ID of the query
data // Parsed data returned from Graph
query // Original Cypher query string
sensitivities // Sensitive data, which contains a map of parameters, and hardcoded data into query
opts // Original map of parameters for the Cypher query
type // Type of query ('READ'|'WRITE')
created // Creation time
У нас получилось обеспечить обновление данных при их изменении — на всех Клиентах, очень простым способом.
Третьей проблемой были данные, которые приходили из Neo4j. Помимо запрошенных нами полей, мы получаем еще кучу пустых объектов, которые нам возвращает npm-пакет. Пустые объекты много весят и не содержат информации, хранить их нам ни к чему. Для отделения полезных и запрошенных данных был написан метод parseReturn, который парсил запрос в БД (Cypher query) и понимал, какие данные были запрошенны и какие поля хотел получить разработчик. После чего, для каждой запрошенной информации создавался объект, содержащий массив нодов с их данными и метаданными. В случае, если запрашиваются отношения нодов, каждый нод содержит в себе объект relations, содержащий в себе данные в виде следующих параметров:
Обновлять данные в MongoDB и следить за их изменениями в Neo4j мы научились, а вот вложенные объекты в возвращаемых данных сами по себе обновляться не станут. На помощь нам пришел функционал предлагаемый пакетом reactive-var. Для этого данные при получении из коллекции Neo4jCache назначаются и возвращаются через ReactiveVar, а их получение помещено вовнутрь коллбека Tracker.autorun(func), после чего достаточно вызвать метод get()для активации реактивности на Клиенте.
Пример:
/* Запрос */
allUsers = neo4j.query('MATCH (n:User) RETURN n');
/* Пример кода на клиенте */
if(Meteor.isClient && allUsers.get()){
var users = allUsers.get().a;
...
}
/* Пример кода на сервере */
if(Meteor.isServer && allUsers){
var users = allUsers.a;
...
}
На этом этапе мы создали тестовое приложение и опубликовали его на GitHub. Спустя неделю комьюнити разработчиков помогло нам «допилить» драйвер и исправить незначительные баги. Буду рад вопросам и предложениям по усовершенствованию и дальнейшему развитию проекта. Желающим получить ссылки — в личку или google. Спасибо за внимание.
P.S. На данный момент компания Neo4j принимает активное участие в разработке проекта и признала данный драйвер для Meteor официальным.
Автор: drdimitru
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/80230
Ссылки в тексте:
[1] Источник: http://habrahabr.ru/post/248465/
Нажмите здесь для печати.