Статьи

Использование OrientDB с JRuby

ruby_orient_sun_logo

Это мой второй пост в серии про OrientDB и Ruby. В первом посте ничего не было сказано о Rubyish, и это исправит эта статья.

Когда я нашел OrientDB, я нашел страницу Language Bindings и был воодушевлен тем, что в этой таблице было 3 записи Ruby: OrientDB-JRuby, OrientDB Client и OrientDB4R . В то время ни один из них не был обновлен в последнее время.

С тех пор OrientDB4R был обновлен до OrientDB 1.5, но он использует только REST API. Мы хотим решение JRuby по соображениям производительности, поэтому REST API на самом деле не вариант.

OrientDB Client использует OrientDB 1.0.0.rc2 и не обновлялся в течение года.

OrientDB-JRuby (Адриан Мадрид) сидел на OrientDB 1.3.0 (OrientDB в настоящее время собирается выпустить 1.6.0) и не обновлялся в течение нескольких месяцев. Это простая оболочка JRuby для Java API OrientDB. Если вы никогда не использовали JRuby для обёртывания библиотеки Java (я этого не делал), то это так же просто, как require jar и BOOM !, вы можете вызывать методы в Javaland из Ruby. В github wiki есть отличное представление о том, насколько это просто.

Обновление OrientDB-JRuby до 1.5.0 (вчера мы фактически переместили его на 1.5.1 :)) было так же просто, как заменить jar-файлы 1.3.0 на более поздние версии вместе с некоторыми зависимостями Java API. Давайте ненадолго поговорим о некоторых из этих зависимостей.

Тинкерпоп, чертежи, трубы и Гремлин

Вы, наверное, думаете, что я только что назвал телепузиков, верно? Нет. Tinkerpop — это набор библиотек Java, который позиционируется как «Программное обеспечение с открытым исходным кодом в графическом пространстве». Он состоит из:

  • Чертежи — интерфейс модели «граф свойств». Это как JDBC, но для графовых баз данных. Blueprints поддерживает другие библиотеки Tinkerpop.
  • Pipes — Pipes обеспечивает конвейерный подход для запросов к базе данных графа. Каналы предоставляют объекты, используемые для преобразования и фильтрации запросов и обходов графа. По сути, он используется Gremlin для предоставления мощного способа запроса графа.
  • Гремлин — Гремлин — это «язык обхода графов». Думайте о Гремлин как о интерфейсе DSL для обхода графа. Большинство графовых запросов — это обходы («начните с этой вершины и обойдите отношения, основанные на каком-то фильтре»), а Гремлин — это попытка стандартизировать язык.

В стеке Tinkerpop есть другие библиотеки, но мы их не используем, поэтому я не буду их обсуждать. Также есть реализации Tinkerpop для Neo4j , Dex и других баз данных Graph.

В рамках моего обновления гема OrientDB-JRuby мне также пришлось заменить различные банки Tinkerpop (OrientDB-JRuby уже использовал банки Blueprints и Pipes) и добавить банки Gremlin. Добавление Gremlin немедленно дало банку OrientDB-JRuby возможность создавать мощные обходы запросов, что мы увидим чуть позже.

Наконец, стоит отметить, что OrientDB имеет свой собственный Java API, отдельный от реализации Tinkerpop. Gem OrientDB-JRuby также оборачивает этот API и предоставляет некоторые функциональные биты, которые недоступны в реализациях OrientDB Tinkerpop. В максимально возможной степени я буду придерживаться API-интерфейсов OrientDB Tinkerpop, но если мне придется углубиться в базовый API-интерфейс, я позабочусь, чтобы вы это знали.

Подготовьте свою демонстрационную зону

Для этой демонстрации вам нужно установить гем orientdb из репозитория github. Я бы порекомендовал создать каталог orientdb-sitepoint . Кроме того, этот драгоценный камень требует JRuby, поэтому используйте RVM или rbenv и установите JRuby 1.7.4. Убедитесь, что вы находитесь в направлении orientdb-sitepoint, и создайте Gemfile со следующим:

 source "https://rubygems.org" gem 'orientdb', github: 'aemadrid/orientdb-jruby' 

Быстрая bundle install повлечет за собой сбой OrientDB-JRuby.

Вам нужно будет запустить сервер OrientDB, поэтому обратитесь к моей первой статье, чтобы узнать, как его настроить.

OrientDB-JRuby Source

Как я уже сказал, OrientDB-JRuby просто оборачивает ядро ​​OrientDB и API Tinkerpop. Если вы просматриваете исходный код на Github, вы увидите некоторые удобные методы, которые он предоставляет, а также некоторые константы Ruby, которые он создает на основе пространств имен Java. Например, просмотр файла constants.rb демонстрирует константы Ruby, предоставляемые гемом.

 module OrientDB CORE = com.orientechnologies.orient.core CLIENT = com.orientechnologies.orient.client ClusterType = CORE.storage.OStorage::CLUSTER_TYPE DocumentDatabase = CORE.db.document.ODatabaseDocumentTx DocumentDatabasePool = CORE.db.document.ODatabaseDocumentPool DocumentDatabasePooled = CORE.db.document.ODatabaseDocumentTxPooled GraphDatabase = CORE.db.graph.OGraphDatabase OTraverse = CORE.command.traverse.OTraverse Document = CORE.record.impl.ODocument IndexType = CORE.metadata.schema.OClass::INDEX_TYPE OClassImpl = CORE.metadata.schema.OClassImpl LocalStorage = CORE.storage.impl.local.OStorageLocal LocalCluster = CORE.storage.impl.local.OClusterLocal PropertyImpl = CORE.metadata.schema.OPropertyImpl RecordList = CORE.db.record.ORecordTrackedList RecordSet = CORE.db.record.ORecordTrackedSet Schema = CORE.metadata.schema.OSchema SchemaProxy = CORE.metadata.schema.OSchemaProxy SchemaType = CORE.metadata.schema.OType SQLCommand = CORE.sql.OCommandSQL SQLSynchQuery = CORE.sql.query.OSQLSynchQuery User = CORE.metadata.security.OUser RemoteStorage = CLIENT.remote.OStorageRemote #Blueprints BLUEPRINTS = com.tinkerpop.blueprints #Gremlin Gremlin = com.tinkerpop.gremlin.java OrientGraph = BLUEPRINTS.impls.orient.OrientGraph Conclusion = com.tinkerpop.blueprints.TransactionalGraph::Conclusion ... end 

Поскольку мы используем gem для доступа к Java API, вы увидите, как мы используем эти константы.

Создать базу данных

Мы создадим базу данных для этой демонстрации под названием «sitepoint-ruby-demo». Итак, запустите orientdb-console , который является скриптом, поставляемым гемом . Это запустит сеанс IRB с уже востребованным OrientDB.

Мы собираемся использовать «удаленный» протокол для доступа к нашему серверу OrientDB, что означает, что мы должны использовать объект OServerAdmin для создания базы данных. Это часть базового Java API OrientDB.

 jruby-1.7.4 :001 > sa = OrientDB::CLIENT::remote::OServerAdmin.new("remote:localhost") => #<Java::ComOrientechnologiesOrientClientRemote::OServerAdmin:0x4473e7ae> jruby-1.7.4 :001 > sa.connect("user", "pass") # REPLACE user, pass with your OrientDB user and pass => #<Java::ComOrientechnologiesOrientClientRemote::OServerAdmin:0x4473e7ae> jruby-1.7.4 :001 > sa.create_database("sitepoint-ruby-demo", "graph", "local") => #<Java::ComOrientechnologiesOrientClientRemote::OServerAdmin:0x4473e7ae> 

Теперь у нас должна быть база данных для демонстрации сегодня. Чтобы убедиться, что база данных была создана, перейдите в веб-приложение OrientDB Studio и убедитесь, что sitepoint-ruby-demo находится в раскрывающемся списке.

db_exists

(Примечание: вам, вероятно, придется сменить пользователя и передать)

Теперь подключитесь к этой базе данных:

 jruby-1.7.4 :001 > g = OrientDB::OrientGraph.new("remote:localhost/sitepoint-ruby-demo", "user", "pass") => #<Java::ComTinkerpopBlueprintsImplsOrient::OrientGraph:0x25f1ce01> 

OrientDB :: OrientGraph является подключением к базе данных API Blueprints.

Создание вершин и ребер

Вспоминая, как работает OrientDB, каждая графовая база данных имеет таблицы V и E, которые представляют суперклассы для Vertex и Edge соответственно. Хотя вы можете хранить записи в этих таблицах, часто имеет смысл использовать подкласс V или E для представления объектов домена.

 person_type = g.create_vertex_type("Person") => #<OrientDB::OClassImpl:Person super=V> 

Вы можете видеть, что он автоматически подклассов V для этого типа вершины. Добавить свойства просто, с одной OrientGraph объект OrientGraph по умолчанию всегда имеет открытую транзакцию. OrientDB не позволит вам добавлять свойства в середине открытой транзакции, поэтому мы должны перейти к «сырому» соединению и остановить транзакцию.

 g.raw_graph.transaction.close => nil person_type.add("Name", :string) => #<OrientDB::OClassImpl:Person super=V Name=STRING> jruby-1.7.4 :022 > name_prop = name_prop.get_property("Name") => #<OrientDB::Property:Name type=string indexed=false mandatory=false not_null=false> jruby-1.7.4 :023 > name_prop.set_mandatory(true) => #<OrientDB::Property:Name type=string indexed=false mandatory=true not_null=false> jruby-1.7.4 :024 > name_prop.set_not_null(true) => #<OrientDB::Property:Name type=string indexed=false mandatory=true not_null=true> 

У нашей персоны теперь есть имя. Мы добавим возраст и пол свойства.

 jruby-1.7.4 :025 > person_type.add("Age", :int) => #<OrientDB::OClassImpl:Person super=V Name=STRING Age=INTEGER> jruby-1.7.4 :026 > person_type.add("Gender", :string) => #<OrientDB::OClassImpl:Person super=V Name=STRING Age=INTEGER Gender=STRING> 

Теперь нам нужны мама и папа.

 jruby-1.7.4 :030 > dad = g.add_vertex("class:Person", {name: 'Bob', age: 40, gender: 'M'}) => #<Java::ComTinkerpopBlueprintsImplsOrient::OrientVertex:0x617e9189> jruby-1.7.4 :016 > dad.id.to_s => "#11:-2" 

Мы добавили временную (то есть, не сохраненную) вершину для папы. Помните, что OrientDB имеет несколько уникальную схему идентификаторов, которая состоит из «#», за которым следуют идентификатор кластера и идентификатор записи, разделенные двоеточием. Идентификатор папы указывает, что идентификатор кластера по умолчанию для подкласса Person равен 11 и что он еще не сохранен, потому что его идентификатор записи отрицателен (-2).

 jruby-1.7.4 :020 > g.commit => nil jruby-1.7.4 :021 > dad.id.to_s => "#11:0" 

Передача графика сохраняется, папа, давая ему правильный идентификатор. Теперь очередь мамы

 jruby-1.7.4 :030 > mom = g.add_vertex("Person", {name: 'Mary', age: 40, gender: 'F'}) => #<Java::ComTinkerpopBlueprintsImplsOrient::OrientVertex:0x617e9189> jruby-1.7.4 :020 > g.commit => nil 

Дорогой читатель, в качестве упражнения для вас создайте детей по следующей таблице:

  • Брат: Бобби, 10 лет
  • Сестра: Джейн, 12 лет

Иметь отношения

С неповрежденными членами нашей маленькой семьи пришло время определить, как они относятся друг к другу. Мы могли бы следовать тому же маршруту «создания типа» для создания типов ребер, определения свойств ребер и т. Д. Но это было бы скучно и не показывало бы, как OrientDB будет создавать типы на лету.

 jruby-1.7.4 :026 > marriage = dad.add_edge("Family", mom, "Family", nil, {relation_name: 'spouse'}) Sep 01, 2013 11:49:52 AM com.orientechnologies.common.log.OLogManager log WARNING: Committing the active transaction to Committing the active transaction to create the new type 'Family' as subclass of 'E'. The transaction will be reopen right after that. To avoid this behavior create the classes outside the transaction. To avoid this behavior do it outside the transaction => #<Java::ComTinkerpopBlueprintsImplsOrient::OrientEdge:0x477e4626> jruby-1.7.4 :026 > g.commit => nil jruby-1.7.4 :027 > marriage.id.to_s => "#12:0" 

Мама и папа теперь женаты. ( бросает рис )

Я уверен, что вы заметили, что WARNING когда мы добавили край. То есть OrientDB кричит вам, что он меняет схему, что он будет делать только один раз для типа ребра «Семейство».

Кроме того, более проницательный из вас может спросить, почему мы только добавили преимущество папе. Значит ли это, что папа женат на маме, а мама не замужем за папой? Ну, это хороший вопрос, и его ответ сводится к тому, как вы и ваш код хотите к нему относиться. OrientDB (и большинство графовых баз данных) являются «Направленными графами свойств», что означает, что КАЖДЫЙ край имеет начало, конец и (как результат) направление. Вы не можете иметь ребро, у которого нет направления.

Это приводит нас к выбору. Мы можем либо добавить ребро Семейства с помощью ‘spouse’ для значения свойства relation_name ИЛИ игнорировать направление отношения в нашем коде. Я собираюсь проигнорировать это здесь и предположить, что любые ребра семейства OUT или IN двунаправлены. Итак, если папа женат на маме, то мама замужем за папой.

После этого у вас теперь есть больше домашней работы: добавьте маму и папу в качестве «родителя» к Бобби и Джейн, затем сделайте Бобби и смените «родного брата». Я буду ждать. Когда вы закончите, записи в классе Family в студии OrientDB должны выглядеть следующим образом:

семья

С этим наша семья полна.

Запросы

В начале этой статьи, если вы помните это, я упоминал Gremlin и Pipes. Несмотря на то, что OrientDB имеет язык запросов, который по своей природе является SQL-кодом, я собираюсь сосредоточиться на использовании инструментов запросов Tinkerpop. Я чувствую, что именно туда движется OrientDB, так что это имеет смысл.

В реальной жизни вы захотите протестировать оба типа запросов по причинам производительности. Базовый язык запросов иногда быстрее.

В Gremlin Wiki есть несколько хороших примеров того, как запросы Gremlin выглядят с использованием API. Прочитайте эту страницу, а затем вернитесь сюда, чтобы запросить нашу семью.

Для запуска запроса Gremlin вам потребуется GremlinPipeline

 gp = OrientDB::Gremlin::GremlinPipeline.new(g) => #<Java::ComTinkerpopGremlinJava::GremlinPipeline:0x5554ca01> 

Помните, это трубопровод . Он является цепным и (до тех пор, пока вы не выполните итерацию / оценку) имеет состояние через цепочку. Каждый канал добавляется в конец конвейера, поэтому, если вы хотите начать все сначала, вам нужен новый конвейер.

Получить супругов

Как только у нас будет наш GremlinPipeline , мы должны дать ему отправную точку. Если мы хотим получить всех супругов, то наша отправная точка — это все вершины в классе Person . Далее мы хотим пересечь ребра Family для каждой вершины и оставить только те, у которых есть «супруга» для relation_name . Наконец, поскольку мы игнорируем направление Family, возьмем обе вершины для каждого подходящего ребра.

 jruby-1.7.4 :078 > gp = OrientDB::Gremlin::GremlinPipeline.new(g) => #<Java::ComTinkerpopGremlinJava::GremlinPipeline:0x4e790cea> jruby-1.7.4 :079 > spouses = gp.v.has("@class", "Person").outE("Family").has("relation_name", "spouse").bothV.iterate => [#<Java::ComTinkerpopBlueprintsImplsOrient::OrientVertex:0x42d0a46b>, #<Java::ComTinkerpopBlueprintsImplsOrient::OrientVertex:0x2c4c9e88>] jruby-1.7.4 :080 > spouses.map{|v| v.get_property("name")} => ["Bob", "Mary"] 

Давайте разберемся с этим.

  • gp — наш трубопровод
  • v устанавливает нашу отправную точку для всех вершин.
  • has("@class", "Person") добавляет PropertyFilterPipe который проверяет каждый системный атрибут вершин @class на Person .
  • outE("Family") добавляет OutEdgePipe который пересекает любой край Семейства, выходящий из любой вершины, еще OutEdgePipe в конвейере.
  • has еще один PropertyFilterPipe который фильтрует края для relation_name == 'spouse' .
  • bothV добавляет BothVertices которая собирает обе вершины с любого ребра, все еще BothVertices в конвейере. Помните, что наши отношения двунаправлены.
  • iterate оценивает конвейер в этой точке. Вызов to_a в этот момент сделает то же самое.

После этого мы просто перебираем результаты и берем свойство name чтобы увидеть, кто в браке.

Глядя на Pipes, Javadoc показывает, сколько Pipes существует. Вещи могут стать очень мощными (и сложными) в спешке.
Привыкание к запросам с Gremlin требует немного времени и настойчивости. Это очень простой пример. В моей следующей статье я покажу еще несколько связанных запросов.

Домашнее задание: выяснить трубопровод, чтобы получить всех братьев и сестер.

Заверните

Это ваш вихревой тур по использованию камня OrientDB-JRuby. Очевидно, вы можете сделать гораздо больше, но это хорошее начало.

Для моей следующей статьи о OrientDB и Ruby я планирую рассказать о другом геме под названием Oriented, который добавляет еще один уровень абстракции к тому, что предлагает OrientDB-JRuby. До этого начинайте применять концепции графа к своей работе. Могу поспорить, вы найдете, что он подходит гораздо больше доменов, чем вы думаете.