Статьи

Пути, сущности и типы в данных Spring для Neo4j

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

(Мальчик, я когда-нибудь хотел, чтобы это было так.)



Странно и замечательно!


[Предоставлено: FOX]

Нет, мне удалось найти время, чтобы написать кое-что о том, что я изучал в отношении SDN: пути с несколькими типами сущностей.

Итак, без лишних слов, давайте начнем.



Да вполне.


[Предоставлено samuelrunge.com и Монти Пайтон)

отказ

Хотя темы, которые я собираюсь затронуть, вполне могут найти решения, которые мне еще предстоит раскрыть, я представляю свои выводы и вопросы в надежде, что не только кто-то найдет обсуждение интересным / полезным, но и что это может также помочь привести меня к лучшим решениям.

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

Получение гетерогенных путей

Если вы помните несколько постов назад, я пытался написать веб-приложение, основанное на концепции простого механизма рекомендаций и розничной торговли программным обеспечением (в стиле Бэббиджа из прошлого века). Реализация осуществлялась с использованием стека Java на основе Spring с Neo4j в качестве хранилища данных.

Я дошел до того, что смог загрузить данные в Neo4j с помощью метода, мало чем отличающегося от метода Cineasts (который можно найти в  справочнике Spring Data Neo4j ). Краткий обзор модели предметной области приведен ниже.



Модель предметной области, о которой идет речь.

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

Еще одна более простая задача — понять, как связаны две сущности, то есть «как мне добраться из точки А в точку Б?». В качестве примера можно привести один такой вопрос: «Как игра А связана с игрой Б, даже если разница в датах публикации велика?» И да, этот тип вопроса чрезвычайно похож на вопрос «Шесть степеней разделения».



Могу поспорить, он геймер.


[Курести: Википедия]

Некоторый Основной Кодекс

Вы также можете вспомнить, что я реализовал  класс GameRepository со следующей подписью:

public interface GameRepository extends GraphRepository<Game>, RelationshipOperationsRepository<Game>

Благодаря вышеприведенному репозиторию, расширяющему  интерфейсы GraphRepository  и RelationshipOperationsRepository , нам предоставляется множество интересных (и удобных) методов из коробки (совет: убедитесь, что вы знакомы с парадигмой «соглашение о конфигурации», так как есть немного магии, которая продолжается с этими методами OOTB).

Как и следовало ожидать, мы также можем добавить дополнительные методы в интерфейс. Одним из примеров метода, который вы можете добавить, может быть пользовательский запрос Cypher, который возвращает все  узлы Game с определенным свойством (реализация этого выходит за рамки этого поста, но на самом деле это довольно просто; если люди хотят его видеть Скинь мне записку!)

Однако сегодня мы стремимся ответить на вопрос «Шесть степеней разделения» (минус предел степеней разделения), то есть «как связаны узел A и узел B»?

Итак, давайте сделаем это (очень просто) выстрел:

@Query("START n=node(1), x=node(97) MATCH p = shortestPath( n-[*]-x ) RETURN p")
Iterable<EntityPath<Game, Game> > getPath();

Учитывая, что узлы с идентификаторами 1 и 97 являются «игровыми» узлами, приведенный выше запрос Cypher по существу определяет, как эти два узла связаны между собой.

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

Быстро перебирая возвращаемый тип, SDN позволяет нам возвращать обратно EntityPath с  заданным типом начального узла и типом конечного узла, который в данном случае является  типом Game . EntityPath  способен хранить узлы и их отношения как часть пути Cypher. Iterable  часть возвращаемого типа необходимо , если вы не хотите использовать EndResult  вместо Iterable .

Затем мы можем получить доступ к отдельным путям с помощью  возвращаемого типа Iterable .

(ПРИМЕЧАНИЕ. В настоящее время  существует ошибка  с SDN, которая выдает исключение при вызове .nodes () или .nodeEntities () одного пути  . Эта ошибка существовала с SDN 2.1.RC4.)

Пройдя по возвращенному пути

Есть причина, по которой мое объяснение используемого кода останавливается там, где оно происходит. Те из вас, у кого есть острый взгляд и / или знакомы с OOP / OOD, обнаружат потенциально большой камень преткновения: как вы перебираете путь узлов и / или отношений с потенциально совершенно несопоставимыми, несвязанными типами?   Учитывая , что это является  SDN и направлено легко интегрировать с Java и POJOs, проблема становится очевидной.

Как мы решаем это?

Решение 1

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

Кажется, слишком хорошо, чтобы быть правдой? Вы правы, потому что это так. Хотя могут существовать некоторые сценарии, в которых может работать это решение, в графической базе данных часто бывает так, что узлы / отношения представляют собой совершенно не связанные понятия / типы, например, « Игра  и клиент» , или « Игра и разработчик» . Такое разделение подразумевает, что существуют вероятные методы и атрибуты, специфичные для данного типа, которые не будут иметь бизнеса в совместно используемом суперклассе или интерфейсе.

Решение 2

Используйте некоторую форму отражения.

Вот оно: слово «р». Отражение, как правило, довольно дорого, и поэтому немедленно препятствует его привлекательности.

Отражение «бедняка» может заключаться в реализации ряда блоков «если / еще» для проверки типов и выполнения некоторого соответствующего приведения. Я думаю, что мы можем видеть, что это могло и станет очень уродливым и трудным поддерживать.

А как насчет полностью автоматизированного отражения? Что ж, мы тоже столкнемся с этой загадкой: в типичной операции присваивания у нас есть левая сторона (LHS) и правая сторона (RHS), например, TheClass theClassInstance = new TheClass (); , RHS задания довольно прост с отражением. Поскольку SDN сохраняет  атрибут / свойство __type__ в заданном узле / отношении, мы можем извлечь его и использовать для приведения (поскольку обычно это полное имя типа). Это может выглядеть примерно так:

// n = Node object in question
String theType = (String)n.getProperty("__type__", "");
// we could then make the RHS of the assignment something like: (LHS) = template.convert(n, class<theType>;

Но как насчет LHS? Без блока «if / else», как мы можем рассматривать возвращенную строку theType  как первоклассного гражданина, который объявил бы тип для LHS? Насколько я знаю, нет никакого способа сделать это (конечно, могут быть способы, которых я просто не видел, но у меня есть ощущение, что они будут такими же дорогими, как и остальные задания). Ява является языком со строгой типизацией, и поэтому я уверен, что большинство из нас ожидают такого результата. Итак, мы видим, что это «решение» на самом деле не одно из них.

Решение 3

Какой — то образом изменить запрос для работы с @MapResult -annotated интерфейса для борьбы с результатами ( примечание: По крайней мере , по состоянию на SDN 2.3.1 @MapResult  устарел в пользу @QueryResult . Я не сделал слишком много с @ QueryResult,  поэтому ваш пробег может отличаться ). Это, очевидно, требует большего знания заблаговременно о типах путей, узлов и отношений, которые вы планируете возвращать, что может ограничить виды гетерогенных запросов, которые вы можете выполнять.

Так что еще можно сделать?

Недавно я посетил  GraphConnect 2013  в Нью-Йорке, где у меня была возможность встретиться с Майклом Хангером (имя, которое почти не требует введения в базу данных графиков / Spring Data Continum). У нас был отличный разговор о самой теме этого поста.

Его общее понимание Spring Data и его цели, достоинств и недоброжелателей было очень полезным, особенно с концептуальной точки зрения.

Пункт номер один, который нужно отбросить — и, возможно, это совершенно очевидно, но стоит повторить, — это следующее: Spring Data — не волшебная пуля . Принимая во внимание различия в понятиях (т. Е. Строго типизированный, объектно-ориентированный язык и хранилище данных, не основанное на графах), существуют определенные ограничения.

Сильной стороной Spring Data является простота интеграции . Типичным вариантом использования SDN, вероятно, будет тот, где требуется возвращать относительно небольшое количество узлов / отношений. SDN не обязательно должен «исследовать» графики.

Для того, чтобы по-настоящему устранить такие различия, мне кажется, что было бы более целесообразно либо создать слой SDN поверх другого уровня абстракции, либо даже перейти непосредственно к API Neo4j.

Возможно, еще лучшим подходом было бы использование доменно-специфического языка (DSL), такого как Groovy или JRuby; что-то гораздо более свободно набранное, гибкое и все еще способное интегрироваться в стек Java.

(Бесстыдный плагин:  посмотрите Pacer , мощный движок обхода графов на основе JRuby.)

Резюме / Заключение

В этом посте мы увидели, что исследование подграфов и путей с SDN не так просто, как хотелось бы; однако ясно, что SDN не был создан для реализации таких функций (по крайней мере, пока).

Spring Data для сильной стороны Neo4j — простота интеграции. Как следует из этой и других публикаций, легко и просто внедрить SDN в существующие / унаследованные Java-приложения и быстро настроить приложения на Java, которые больше полагаются на «конечные результаты», чем на «исследование» как таковое.

Хорошо, ребята; как всегда, я определенно приветствую комментарии, отзывы и вопросы. Если вы можете придумать лучший способ приблизиться к этому типу проблемного пространства, или даже если у меня здесь что-то не так, пожалуйста, дайте мне знать, и я обязательно воспользуюсь такой обратной связью.

Спасибо за чтение, и мы увидимся в следующем посте!