Определение пользовательских полей запроса с помощью директивы схемы Cypher GraphQL

Ранее мы видели, как легко создать GraphQL API на основе Neo4j с использованием neo4j-graphql.js, части GRANDstack. В этом посте мы увидим, как раскрыть функциональность полнотекстового поиска Neo4j в нашем GraphQL API, определив настраиваемое поле запроса с помощью директивы схемы @cypher GraphQL.

Полнотекстовые индексы в Neo4j

Полнотекстовые индексы, представленные в Neo4j 3.5, очень полезны для сопоставления на основе ввода данных пользователем. Представьте себе форму в веб-приложении для поиска по бизнесу. Пользователь хочет искать компании по названию, но, возможно, это не лучший вариант написания ...

Обратите внимание, что ранее мы могли использовать полнотекстовые индексы в Neo4j с помощью явных индексов и через APOC. И, конечно, мы всегда можем использовать регулярные выражения, но они не поддерживаются индексом.

Создание полнотекстового индекса

Сначала нам нужно создать полнотекстовый индекс в базе данных. В API для этого используются процедуры, а именно db.index.fulltext.createNodeIndex()

Итак, давайте создадим полнотекстовый индекс, используя свойство name на Business ярлыках:

CALL db.index.fulltext.createNodeIndex("businessNameIndex", ["Business"],["name"])

Обратите внимание, что мы можем объединить несколько меток и свойств узлов в один индекс. Это было бы полезно, если бы, например, у нас было свойство description на наших бизнес-узлах, которое мы также хотели бы найти.

Мы также можем создать полнотекстовый индекс для отношений, используя db.index.fulltext.createRelationshipIndex()

Запрос полнотекстового индекса

Как и при создании полнотекстового индекса, мы также запрашиваем его с помощью процедуры:

CALL db.index.fulltext.queryNodes("businessNameIndex", "pizza")

Мы можем использовать синтаксис запроса Lucene в нашем запросе, например, для использования нечеткого соответствия:

CALL db.index.fulltext.queryNodes("businessNameIndex", "pizzza~")

и, конечно же, мы можем использовать результаты полнотекстового запроса в более сложном запросе Cypher. Здесь мы гарантируем, что любые совпадения Business из нашего полнотекстового индекса действительно связаны с категорией «Рестораны»:

CALL db.index.fulltext.queryNodes("businessNameIndex", "pizzza~")
YIELD node
MATCH (node)-[:IN_CATEGORY]->(:Category {name: "Restaurants"})
RETURN node

Создание нашего GraphQL API с neo4j-graphql.js

Теперь перейдем к созданию нашего сервиса GraphQL. Мы будем использовать neo4j-graphql.js, чтобы быстро развернуть GraphQL API.

npm install --save neo4j-graphql-js

Первым шагом в создании GraphQL API является создание наших определений типов. Давайте определим несколько простых типов для предприятий и категорий.

type Business {
  id: ID!
  name: String
  address: String
  categories: [Category] @relation(
    name: "IN_CATEGORY", direction: "OUT")
}
type Category {
  name: ID!
  businesses: [Business] @relation(
    name: "IN_CATEGORY", direction: "IN")
}

Затем мы передаем эти определения типов в `makeAugmentedSchema` из neo4j-graphql.js, и мы запускаем и работаем с полным API GraphQL поверх Neo4j:

import { ApolloServer } from "apollo-server";
import { makeAugmentedSchema } from "neo4j-graphql-js";
const schema = makeAugmentedSchema({
  typeDefs
});
const server = new ApolloServer({
  schema
});
server.listen(process.env.GRAPHQL_LISTEN_PORT, "0.0.0.0")
      .then(({ url }) => {
        console.log(`GraphQL API ready at ${url}`);
});

Затем мы можем изучить автоматически сгенерированный GraphQL API на GraphQL Playground. Мы видим, что доступны 2 поля запроса: Business и Category. Аргументы для этих полей позволяют нам искать точные совпадения со свойством названия компании (или адресом, или идентификатором), но как мы можем искать нечеткое совпадения? Например, мы хотим рассмотреть сравнение без учета регистра и небольшие орфографические ошибки. Выше мы видели, как запрашивать эти нечеткие совпадения с помощью Cypher, но как мы можем внести это в наш GraphQL API?

Вот здесь-то и появляется создание настраиваемого поля запроса с использованием директивы схемы @cypher GraphQL. Аннотируя поле в наших определениях типа GraphQL с помощью запроса Cypher, мы можем сопоставить этот запрос Cypher с полем GraphQL!

Добавление настраиваемого поля запроса в нашу схему GraphQL

Мы добавим поле fuzzyBusinessByName к типу запроса в наших определениях типов GraphQL, аннотируя это поле запросом Cypher с помощью директивы схемы @cypher GraphQL:

type Query {
  fuzzyBusinessByName(searchString: String): [Business] @cypher(
    statement: """
      CALL db.index.fulltext.queryNodes(
        'businessName', $searchString+'~') 
      YIELD node RETURN node
    """
  )
}

Если вы не знакомы с директивами схемы GraphQL, они являются встроенным механизмом расширения GraphQL и позволяют нам определять некоторое настраиваемое поведение на стороне сервера. Интеграции neo4j-graphql используют специальную директиву @cypher для сопоставления любого поля в определении типа GraphQL с запросом Cypher, эффективно создавая вычисляемое поле. Мы можем использовать @cypher директивы схемы для полей обычного типа, а также для запросов и мутаций. Подробнее о директиве схемы @cypher GraphQL здесь.

Теперь мы готовы запросить наш GraphQL API, поэтому мы вернемся к GraphQL Playground и сделаем запрос, используя наше новое поле нечеткого поискового запроса:

Ознакомьтесь с кодом в Стартовом проекте GRANDstack, чтобы узнать, как подключить этот наш пользовательский интерфейс.

Этот пример специфичен для использования полнотекстовых индексов в настраиваемых полях запроса GraphQL с использованием директивы схемы @cypher, но вы можете применить эту технику практически к любому запросу Cypher и расширениям для Neo4j, таким как плагин Neo4j алгоритмы графа.

Ресурсы