Статьи

ОТДЫХ :: Neo4p — Perl «OGM»

Это гостевой пост Марка А. Дженсена , ученого из области биоинформатики. Большое спасибо Марку за написание впечатляющей библиотеки Perl Neo4j и за то, что он нашел время для ее тщательного документирования.

Вы можете назвать REST :: Neo4p «Отображение графов объектов». Он использует REST API Neo4j в своей основе для взаимодействия с Neo4j, но то, что делает REST :: Neo4p «Perly», является объектно-ориентированным подходом. Создание объектов узла, отношения или индекса эквивалентно поиску их в базе данных графа и созданию узлов и отношений, если они отсутствуют. Обновление объектов путем установки свойств или отношений приводит к тем же действиям в базе данных и возвращает ошибки, когда эти действия запрещены Neo4j. В то же время создание объекта пытается быть как можно более ленивым, чтобы в памяти была представлена ​​только та часть базы данных, с которой вы работаете.

Идея состоит в том, что работа с базой данных осуществляется с помощью объектов Perl5 любимым способом Perl. Несмотря на пространство имен REST модулей, разработчику почти никогда не придется иметь дело с реальными вызовами REST или созданием URL-адресов. В дизайне используется удивительно полная и непротиворечивая информация с самоописанием в ответах API-интерфейса REST Neo4j, чтобы скрыть URL-адреса.

Предыстория

Я ученый в области биоинформатики, заключивший контракт с крупной исследовательской инициативой правительства США. Наши клиенты интересовались альтернативами СУБД для представления больших данных, которыми мы управляем. Как давний объектно-ориентированный Perler с небольшим количеством Java, я хотел исследовать Neo4j, но на моих собственных условиях. Мой CPAN и другие поиски предложили несколько экспериментальных подходов к работе со службой REST Neo4j, но там было мало реальных функциональных возможностей и надежности.

Я был удивлен, увидев так мало активности Neo4j в домене Perl с открытым исходным кодом, несмотря на многочисленные просьбы сообщества Neo4j о достойном интерфейсе Perl. Таким образом, я принял вызов, и я определенно надеюсь на положительные отзывы и конструктивную критику.

Основы

Загрузить и установить

REST :: Neo4p действительно семейство модулей / классов Perl. Получите и установите это так:

 $ cpan
 cpan> install REST::Neo4p

Вот и все.
CPAN утилита поставляется с каждой установкой Perl. Если вы не использовали его ранее, вам будут заданы некоторые вопросы по настройке, для большинства из которых по умолчанию будет достаточно.

Тесты. В процессе установки будет выполнен полный набор тестов. Чтобы воспользоваться этими преимуществами, введите полный URL-адрес API и порт вашего (работающего!) Механизма neo4j при появлении запроса. (По умолчанию это http://127.0.0.1:7474.)

Если какие-либо тесты не пройдены, пожалуйста , сообщите об ошибке прямо здесь .

Утилита cpan также поможет вам установить зависимости, другие модули CPAN, которые заставляют REST :: Neo4p работать. Есть только несколько. Уловка, чтобы заставить их все установить автоматически, состоит в следующем:

 $ cpan
 cpan> o conf prerequisites_policy follow
 cpan> install REST::Neo4p

Подключайтесь и манипулируйте

Я предполагаю, что вы знакомы с объектами Perl 5. Если нет, проверьте

прямо из
верблюжьей пасти.

Я пойду через простую модель из области биоинформатики, нуклеотиды в ДНК (перейдите по ссылке для хорошего введения).

Нуклеотиды — это буквы, которые пишут слова (гены), закодированные в ДНК. Есть четыре, которые являются наиболее важными, и упоминаются как A, C, G и T. Эти буквы обозначают их химические названия. ДНК может измениться или мутировать, когда одна из этих букв меняется на другую. Буквы являются узлами в нашей модели.

Мутации — это изменения в ДНК от одной буквы к другой. Эти изменения сами классифицируются и называются транзициями или трансверсиями . Детали здесь не имеют значения, за исключением того, что мутации из одной буквы в другую являются отношениями в нашей модели.

Сначала включите модули REST :: Neo4p, затем подключитесь к базе данных:

#-*- perl -*-
use REST::Neo4p;
use strict;
use warnings;
 
eval {
    REST::Neo4p->connect('http://127.0.0.1:7474');
};
ref $@ ? $@->rethrow : die $@ if $@;

Ошибки, включая ошибки связи, передаются как объекты исключений из иерархии ( полное описание см. В REST :: Neo4p :: Exceptions ). Последняя строка здесь просто проверяет, было ли выброшено исключение во время соединения и, если это так, умирает с сообщением. Более сложная обработка возможна и поощряется.

Теперь создать узлы вместе с индексами, чтобы легко с ними справиться. Новый конструктор делает создание и возвращает объекты для классов Neo4p сущностей Index, узлы и отношения.

my @node_defs =
    (
     { name => 'A', type => 'purine' },
     { name => 'C', type => 'pyrimidine' },
     { name => 'G', type => 'purine'},
     { name => 'T', type => 'pyrimidine' }
    );
my $nt_types = REST::Neo4p::Index->new('node','nt_types');
my $nt_names = REST::Neo4p::Index->new('node','nt_names');
my @nts = my ($A,$C,$G,$T) = map { REST::Neo4p::Node->new($_) } @node_defs;
 
$nt_names->add_entry($A, 'fullname' => 'adenine');
$nt_names->add_entry($C, 'fullname' => 'cytosine');
$nt_names->add_entry($G, 'fullname' => 'guanosine');
$nt_names->add_entry($T, 'fullname' => 'thymidine');
 
for ($A,$G) {
    $nt_types->add_entry($_, 'type' => 'purine');
}
 
for ($C,$T) {
    $nt_types->add_entry($_, 'type' => 'pyrimidine');
}

In general, you provide a hash reference that maps properties to values to the Node and Relationship constructors. To create an Index, the first argument is the index type (‘node’ or ‘relationship’), followed by the index name. Use the add_entry method to add an object to an index with a tag => value pair.

On to relationships. We create a relationship index to corral the mutation types, and express the mutation types as relationship objects. Using the relate_to method from node objects, we create relationships between pairs of nodes with a pretty natural syntax:

my $nt_mutation_types = REST::Neo4p::Index->new('relationship','nt_mutation_types');
 
my @all_pairs;
my @a = @nts;
while (@a) {
    my $s = shift @a;
    push @all_pairs, [$s, $_] for @a;
}
 
for my $pair ( @all_pairs ) {
    if ( $pair->[0]->get_property('type') eq
  $pair->[1]->get_property('type') ) {
 $nt_mutation_types->add_entry(
     $pair->[0]->relate_to($pair->[1],'transition'),
     'mut_type' => 'transition'
     );
 $nt_mutation_types->add_entry(
     $pair->[1]->relate_to($pair->[0],'transition'),
     'mut_type' => 'transition'
     );
    }
    else {
 $nt_mutation_types->add_entry(
     $pair->[0]->relate_to($pair->[1],'transversion'),
     'mut_type' => 'transversion'
     );
 $nt_mutation_types->add_entry(
     $pair->[1]->relate_to($pair->[0],'transversion'),
     'mut_type' => 'transversion'
     );
    }
}

The relate_to method returns the relationship object that is created. Here we use that side effect directly in the add_entry method of the index.

If you prefer, you can use a relationship constructor:

$transition = REST::Neo4p::Relationship->new($A, $G, 'transition');

Relationship properties can be added in the constructor, or after the fact:

$transition->set_property('involved_types' => 'purines');

Perl garbage collection removes objects from memory, but does not delete from the database. You must do this explicitly, using the the remove method on any entity:

for my $reln ($A->get_relationships) {
  $reln->remove;
}
$A->remove;
$nt_types->remove;
# etc.

Retrieve and query

The REST::Neo4p module itself contains a few methods for retrieving database items directly. The most useful is probably get_index_by_name. Index objects have find_entries for retrieving the items in the index.

use REST::Neo4p;
use strict;
use warnings;
 
REST::Neo4p->connect('http://127.0.0.1:7474');
my $idx = REST::Neo4p->get_index_by_name('nt_names','node');
my ($node) = $idx->find_entries(fullname => 'adenine');
my @nodes = $idx->find_entries('fullname:*');

Note that find_entries always returns an array, and it supports either an exact search or a lucene search.

REST::Neo4p also supports the CYPHER query REST API. Entities are returned as REST::Neo4p objects. Query results are always sent to disk, and results are streamed via an iterator that is meant to imitate the commonly used Perl database interface, DBI.

Here we print a table of nucleotide pairs that are involved in transversions:

my $query = REST::Neo4p::Query->new(
  'START r=relationship:nt_mutation_types(mut_type = "transversion")
   MATCH a-[r]->b
   RETURN a,b'
  );
$query->execute;
while (my $result = $query->fetch) {
   print $result->[0]->get_property('name'),'->',
         $result->[1]->get_property('name'),"\n";
}

The query is created with the CYPHER code, then executed. The fetch iterator retrieves the returned rows (as array references) one at a time until the result is exhausted. Again, the result is not held in memory, so queries returning many rows should not present a big problem.

Production-quality Features

My goal for REST::Neo4p is to go beyond the Perl experiments with Neo4j that are out there to create modules that are robust enough for production use (yes, people DO use Perl in production!). This meant a couple of things:

  • Be robust and feature-rich enough that people will want to try it.
  • Be responsive enough to bugs that people will see it maintained.
  • Incorporate unit and integration tests into the user installation.
  • Have complete documentation with tested examples.
  • Create bindings to as many of the Neo4j REST API functions as is possible for a guy with a real job.
  • Be concerned with performance by being sensitive to memory use, and taking advantage of streaming and batch capabilities.
  • Capture both Perl and Neo4j errors in a catchable way.

There isn’t space here to discuss these points in detail, but here are some highlights and links:

  • REST::Neo4p::Agent is the class where the REST calls get done and the endpoints are captured. It subclasses the widely-used LWP::UserAgent and can be used completely independently of the object handling modules, if you want to roll your own Neo4j REST interface.
  • The batch API can be used very simply by including the REST::Neo4p::Batch mixin. Visit the link for detailed examples.
  • When paths are returned by CYPHER queries, they are rolled up into their own simple container object REST::Neo4p::Path that collects the nodes and relationships with some convenience methods.
  • You can choose to have REST::Neo4p create property accessors automatically, allowing the following:
$node->set_property( name => 'Fred' );
print "Hi, I'm ".$node->name;
  • REST::Neo4p::Index allows index configuration (e.g., fulltext lucene) as provided by the Neo4p REST API.

I hope Perlers will give REST::Neo4p a try and find it useful. Again, I appeciate the time you take to report bugs.