Статьи

Создание DSL для запросов графа Cypher

Моим первым заданием в Neo4j было создание Java DSL для языка запросов Cypher, который используется для графического доступа к данным из базы данных Neo4j.

Во-первых, почему DSL? Существует масса причин, по которым лучше использовать DSL вместо строк. С практической точки зрения DSL и приличная IDE значительно упростят создание запросов, так как вы можете использовать завершение кода для построения запроса. Нет необходимости обращаться к руководствам и шпаргалкам, если вы забыли синтаксис. Во-вторых, я считаю полезным создавать итеративные запросы в многоуровневой архитектуре, в которой модель домена может создавать базовый запрос, который описывает некоторую концепцию, например «все сообщения в моем почтовом ящике», а затем прикладной уровень может принять это и улучшить с помощью фильтрация, например, «все сообщения в моем почтовом ящике, отправленные из XYZ», а затем, наконец, пользовательский интерфейс может добавить порядок и выполнить подкачку. Делать что-то подобное было бы крайне сложно без DSL.

После краткого прочтения книги Мартина Фаулера о DSL, чтобы убедиться, что я не пропустил ни одной из основных полезных моделей, я приступил к работе. Вместе с Майклом Хангером и Андресом Тейлором DSL был быстро повторен, и я покажу вам несколько примеров ниже.

Вот пример запроса Cypher:

START n=node(3,1)
WHERE (n.age<30 and n.name="Tobias") or not(n.name="Tobias")
RETURN n

Это можно выразить с помощью Cypher DSL следующим образом:

start( node( "n", 3, 1 ) ).
where( prop( "n.age" ).lt( 30 ).and( prop( "n.name" ).eq( "Tobias" )).
       or(not(prop("n.name").eq("Tobias" )))).
returns( nodes( "n" ) )

Очевидно, что здесь происходит целая куча импорта статических методов, чтобы разрешить такой синтаксис в Java. Каждое предложение, например «start», принимает одно или несколько выражений и возвращает свободный DSL, который помогает вам узнать, какие возможные предложения будут следующими. Если вы используете функции автозавершения кода, таким образом, становится очень легко создавать эти запросы без необходимости знать синтаксис. Вместо этого все ваши мозговые циклы могут быть потрачены на выяснение того, как построить предложения MATCH и WHERE, что обычно является сложной задачей.

Для предложения WHERE у вас есть возможность использовать инфиксную или префиксную нотацию для выражений. Другими словами, эти два выражения одинаковы:

prop( "n.age" ).lt( 30 )
lt("n.age",30)

Самым уникальным предложением языка запросов Cypher является предложение MATCH, которое позволяет выполнять сопоставление с образцом в графе. Вот пример того, как это выглядит:

START a=node(3),c=node(2)
MATCH p=(a)-->(b)-->(c)
RETURN nodes(p)

Учитывая два узла, выясните все способы, чтобы добраться от a до c за один прыжок. Это можно выразить с помощью DSL следующим образом:

start( node( "a", 3 ), node( "c", 2 ) ).
match( path( "p" ).from( "a" ).out().to( "b" ).link().out().to( "c" ) ).
returns( nodesOf( "p" ) )

Как видите, писать запрос с использованием DSL, а не просто строки становится немного длиннее, но я надеюсь, что он достаточно хорошо «читает», чтобы его было не сложно проанализировать в вашей голове. В случае сомнений вы всегда можете добавить .toString () к результату DSL, чтобы увидеть, как выглядит сгенерированный запрос.

Одним из приемов, которые Майкл Хангер показал мне, был трюк с блоком инициализации экземпляра Java (это полный рот!). По сути, когда вы создаете экземпляр Java-объекта, можно добавить блок инициализатора, что-то вроде статического блока в классах, которые выполняются как часть фазы инициализации объекта. Для DSL это можно использовать, добавляя термины как защищенные методы. Вот как бы вы использовали Cypher DSL с этим стилем:

assertEquals( "START john=node(0) RETURN john", new CypherQuery()
{{
   starts( node( "john", 0 ) ).returns( nodes( "john" ) );
}}.toString() );

В этом случае нет статического импорта вообще. Вместо этого «старты», «узлы» и «узлы» являются защищенными методами в классе CypherQuery, что делает их доступными для завершения кода в блоке инициализации. Таким образом, этот стиль позволяет избежать статического импорта, но очень затрудняет выполнение итеративного построения запроса, упомянутого ранее. Если вы строите свои запросы за один шаг, это может быть полезно, однако.

Для более полного набора примеров, пожалуйста, посмотрите тестовое руководство Cypher в GitHub здесь .

Итак, что вы делаете со сборщиком DSL после создания запроса? В первой версии главное, что вы можете сделать, это вызвать .toString (), чтобы получить запрос в виде строки, а затем использовать его для вызова Cypher. Однако это заставляет Cypher анализировать запрос, что может быть дорогостоящим. Если у вас есть запросы, которые повторяются часто, вы можете использовать параметризованные запросы, так что вам придется выполнять этот анализ только один раз. В долгосрочной перспективе мы работаем над тем, чтобы механизм Cypher мог напрямую выполнять запросы Cypher DSL, таким образом полностью пропуская этап синтаксического анализа.

Если вы используете Neo4j и Cypher, попробуйте этот DSL! Вы можете найти его в репозиториях Maven здесь . Если у вас есть отзывы о том, как его улучшить, сообщите мне, желательно через списки рассылки Neo4j.

Cypher DSL также интегрирован с библиотекой QueryDSL, что обеспечивает еще большую статичность при наборе текста. В следующем посте я покажу, как это работает и как его настроить.

Источник: 
https://rickardoberg.wordpress.com/2011/11/14/creating-a-dsl-for-cypher-graph-queries/