Бессерверный GraphQL, Neo4j Aura и GRANDstack
В этом посте мы рассмотрим использование GraphQL с Neo4j с использованием пространственного типа Point Neo4j GraphQL для поиска ближайших предприятий. Мы будем использовать Neo4j Aura database-as-a-service и развернем наш бессерверный GraphQL API с помощью Zeit Now и Codesandbox.
Сначала мы создадим экземпляр базы данных графа Neo4j Aura в облаке. Мы загрузим образец набора данных о предприятиях, а затем рассмотрим, как использовать тип данных Point в Cypher, а также функцию distance для поиска предприятий, расположенных рядом с другими предприятиями. Затем мы создадим сервер GraphQL Node.js, используя neo4j-graphql.js, чтобы обслуживать GraphQL API, предоставляя бизнес-данные, которые мы загрузили в Neo4j Aura. Наконец, мы развертываем наш API с помощью Zeit Now - создавая бессерверный GraphQL API в облаке, извлекая данные из нашего экземпляра Neo4j Aura.
Начиная с Aura
Сначала мы создадим экземпляр Neo4j в Neo4j Aura, базе данных Neo4j как сервисной платформе. После авторизации выбираем регион и размер базы данных. Для наших целей мы просто выберем размер памяти 1 ГБ, что даст нам кластер Neo4j, работающий в облаке ☁️☁️☁️

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

Как только наш кластер базы данных будет подготовлен, панель управления Aura предоставит нам URL-адрес Bolt, который является конечной точкой, используемой для подключения к нашему экземпляру Neo4j.

Мы создадим файл .env для хранения URL-адреса Bolt, пользователя и пароля базы данных. Позже мы загрузим эти значения в переменные среды, чтобы передать их нашему приложению GraphQL API для подключения к нашему экземпляру Neo4j Aura.
.env
NEO4J_URI=bolt+routing://522b2c8b.databases.neo4j.io NEO4J_USER=neo4j NEO4J_PASSWORD=G6OhRNPLeGQCdW0c_VtAHn1wZPubD_M2EMQ5iaEo05s
Теперь, когда мы создали наш экземпляр Neo4j Aura, давайте откроем браузер Neo4j и посмотрим, как использовать тип Point с Cypher.
Тип пространственной точки в Neo4j
Neo4j поддерживает собственный Point тип данных, который можно использовать со свойствами узлов и отношений. Каждая точка может иметь 2 или 3 измерения и поддерживает множество систем координат, но в этом посте мы сосредоточимся на географической системе координат с использованием широты и долготы.
Создание свойства точки
Функция Point используется для создания типов Point и может передавать комбинацию значений x, y, z, широты, долготы, высоты и crs в зависимости от того, какие данные мы хотим сохранить. Здесь мы создаем два бизнес-узла и сохраняем их местоположение как точку, используя широту и долготу:
CREATE (b:Business)
SET b.location = Point(
{latitude: 37.575968, longitude: -122.336041}),
b.name = "Ducky's Car Wash"
CREATE (b:Business)
SET b.location = Point(
{latitude:37.563534, longitude: -122.322269}),
b.name = "Neo4j"
См. документацию для примеров создания трехмерных точек и точек с использованием других систем координат.
Функция расстояния
После того, как мы создали узлы со свойствами типа Point, мы можем увидеть, насколько они удалены друг от друга:
Как далеко от офиса Neo4j до автомойки Ducky’s?
MATCH (neo4j:Business {name: "Neo4j"})
MATCH (ducky:Business {name: "Ducky's Car Wash"})
RETURN distance(neo4j.location, ducky.location)
-------------------------------------------------
1841.8594204207154
Или отфильтруйте компании в определенном радиусе:
Какие предприятия находятся в пределах 2 км от офиса Neo4j?
MATCH (b:Business) WHERE distance(b.location, Point({latitude:37.563534, longitude:-122.322269})) < 2000
RETURN b.name
-----------------------------------
│"Ducky's Car Wash"│
├──────────────────┤
Если мы добавим PROFILE в начале нашего запроса, мы увидим план запроса:

Глядя на план запроса, мы видим операцию сканирования этикеток. Это нормально для такого небольшого набора данных, но если бы в нашей базе данных были миллионы предприятий, это сканирование могло бы быть немного медленным. К счастью, мы можем создать индекс для свойства location, чтобы избежать этой операции сканирования.
Пространственный индекс
Чтобы создать индекс для свойства location, запустите:
CREATE INDEX ON :Business(location)
теперь, если мы снова запустим запрос с PROFILE после создания индекса, мы увидим, что поиск по индексу используется вместо операции сканирования:

Загрузить образец данных
Мы увидели, что поиск предприятий рядом с офисом Neo4j был не очень увлекательным с использованием только двух созданных нами узлов, поэтому давайте принесем еще несколько примеров данных, чтобы поиграть. Для этого загрузим Руководство по браузеру Neo4j, запустив
:play grandstack
в редакторе запросов браузера. Эта команда загрузит руководство для браузера, в которое встроен заранее подготовленный запрос, который мы можем использовать для загрузки некоторых образцов данных.

Это руководство для браузера, используемое для упражнений из книги Приложения Fullstack GraphQL с GRANDstack, но мы будем использовать его только для загрузки некоторых примеров данных. Щелкните встроенный запрос в руководстве, чтобы загрузить его в редактор запросов, затем нажмите кнопку воспроизведения, чтобы загрузить данные в наш экземпляр Neo4j Aura:

После загрузки данных мы можем запустить CALL db.schema(), чтобы увидеть визуализацию загруженных данных:

И мы видим, что у нас есть некоторые данные о компаниях и пользователях, которые их просмотрели.
Построение GraphQL API с neo4j-graphql.js и Apollo Server
Затем мы хотим представить наш бизнес-график как GraphQL API. Для этого мы будем использовать neo4j-graphql.js для создания Node.js GraphQL API. Neo4j-graphql.js позволит нам быстро создать схему GraphQL, способную извлекать данные из Neo4j, и мы будем использовать Apollo Server для обслуживания GraphQL API, обработки сетевых запросов и выполнения GraphQL.

neo4j-graphql.js
neo4j-graphql.js - это библиотека node.js, которая упрощает создание API-интерфейсов GraphQL, поддерживаемых Neo4j. Это достигается за счет использования определений типов GraphQL для автоматического создания GraphQL API, добавления запросов и изменений, фильтрации, упорядочивания, разбивки на страницы и генерации запросов Cypher из GraphQL во время запроса. Это означает, что создание GraphQL API так же просто, как написание определений типов GraphQL.

Сначала мы создадим новый проект Node.js
npm init -y
Затем установите наши зависимости. В дополнение к neo4j-graphql.js и Apollo Server мы устанавливаем драйвер JavaScript Neo4j и dotenv, утилиту для чтения учетных данных из нашего файла .env и установки их в качестве переменных среды.
npm install apollo-server neo4j-graphql-js [email protected] dotenv
neo4j-graphql.js makeAugmentedSchema
Экспорт makeAugmentedSchema из neo4j-graphql.js будет принимать наши определения типа GraphQL в качестве входных данных и возвращать исполняемый объект схемы GraphQL, который мы затем можем передать на сервер Apollo. Этот исполняемый объект схемы будет содержать всю логику выборки данных для преобразования запросов GraphQL в запросы Cypher.
Создайте index.js файл, который будет читать определения типов GraphQL из graphql.schema файла, а затем использует makeAugmentedSchema и Apollo Server для обслуживания GraphQL API. На следующем этапе мы создадим этот graphql.schema файл.
index.js
const { makeAugmentedSchema } = require("neo4j-graphql-js");
const { ApolloServer } = require("apollo-server");
const neo4j = require("neo4j-driver").v1;
const fs = require("fs");
const dotenv = require("dotenv");
// Load contents of .env as environment variables
dotenv.config();
// Load GraphQL type definitions from schema.graphql file
const typeDefs = fs.readFileSync("schema.graphql").toString("utf-8");
// Create executable GraphQL schema from GraphQL type definitions,
// using neo4j-graphql.js to autogenerate resolvers
const schema = makeAugmentedSchema({
typeDefs
});
// Create Neo4j driver instance
const driver = neo4j.driver(process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
);
// Create ApolloServer instance to serve GraphQL schema
// Inject Neo4j driver instance into the context object
// which is passed into each (autogenerated) resolver
const server = new ApolloServer({
context: { driver },
schema
});
// Start ApolloServer
server.listen().then(({ url }) => {
console.log(`GraphQL server ready at ${url}`);
});
Вывод определений типов GraphQL из существующей базы данных Neo4j
При создании API GraphQL с помощью neo4j-graphql.js у нас есть два варианта создания схемы:
- Начните с пустой базы данных Neo4j и напишите определения типа GraphQL для управления моделью базы данных, сопоставив определения типа GraphQL с моделью графа свойств, используемой Neo4j, или
- Начните с существующей базы данных Neo4j и используйте модель данных базы данных, чтобы вывести определения типа GraphQL, которые затем будут использоваться для создания GraphQL API.
Мы воспользуемся вторым подходом, выведя определения типов GraphQL из базы данных Neo4j Aura, которую мы заполнили образцами данных. Для этого мы используем экспорт inferSchema из neo4j-graphql.js:
inferSchema.js
const neo4j = require("neo4j-driver").v1;
const { inferSchema } = require("neo4j-graphql-js");
const dotenv = require("dotenv");
const fs = require("fs");
dotenv.config();
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
);
const schemaInferenceOptions = {
alwaysIncludeRelationships: false
};
inferSchema(driver, schemaInferenceOptions).then(result => {
fs.writeFile("schema.graphql", result.typeDefs, err => {
if (err) throw err;
console.log("Updated schema.graphql");
});
});
который затем заполнит schema.graphql определениями типов GraphQL
Запустим этот файл:
node inferSchema.js
и мы видим, что файл schema.graphql теперь заполнен определениями типов GraphQL, которые соответствуют модели данных нашей базы данных Neo4j:
schema.graphql
type User {
_id: Long!
name: String!
wrote: [Review] @relation(name: "WROTE", direction: "OUT")
}
type Review {
_id: Long!
date: Date!
reviewId: String!
stars: Float!
text: String
reviews: [Business] @relation(name: "REVIEWS", direction: "OUT")
users: [User] @relation(name: "WROTE", direction: "IN")
}
type Category {
_id: Long!
name: String!
businesss: [Business] @relation(name: "IN_CATEGORY", direction: "IN")
}
type Business {
_id: Long!
address: String!
city: String!
location: Point!
name: String!
state: String!
in_category: [Category] @relation(name: "IN_CATEGORY", direction: "OUT")
reviews: [Review] @relation(name: "REVIEWS", direction: "IN")
}
Запросы GraphQL с помощью GraphQL Playground
API, созданный makeAugmentedSchema, включает поля запроса и мутации для полных операций CRUD для каждого типа и взаимосвязи, определенных в определениях типа GraphQL.
Например, мы можем искать все компании и возвращать их название и местонахождение с помощью этого запроса.
{
Business {
name
location {
latitude
longitude
}
}
}

Кроме того, сгенерированный API включает нумерацию страниц, упорядочивание и сложную фильтрацию. Аргумент фильтра создается для каждого типа на основе полей этого типа. Мы можем использовать фильтр расстояния, чтобы найти предприятия в пределах некоторого радиуса от точки. Здесь мы ищем компании в радиусе 2 км:
Находите компании рядом со мной с помощью фильтра расстояния
{
Business(
filter: {
location_distance_lt: {
point: { latitude: 46.860992, longitude: -113.985122 }
distance: 2000
}
}
) {
name
address
city
state
}
}

Автоматически сгенерированный GraphQL CRUD API, который дает нам neo4j-graphql.js, очень эффективен для создания простых CRUD API, но мы также можем добавить пользовательскую логику с помощью директивы схемы @cypher. Мы рассмотрим эту функцию в одном из следующих постов, а пока вы можете увидеть некоторые ее примеры в документации.
Развертывание бессерверного GraphQL с Zeit прямо сейчас
После того, как мы построили наш GraphQL-сервер, нам нужно где-то его развернуть, и Zeit Now - отличный вариант для развертывания нашего GraphQL API с использованием бессерверного подхода. Это означает, что нам не нужно беспокоиться о инициализации экземпляров сервера, и нам выставляют счет только за использование нашего приложения.
Вам нужно будет создать учетную запись Zeit, если у вас ее еще нет, затем добавить now.json файл, в котором указано, как создавать проект и обслуживать проект, а также указать учетные данные для нашего экземпляра Neo4j Aura:
now.json
{
"version": 2,
"name": "business-search-graphql",
"builds": [{ "src": "index.js", "use": "@now/node-server" }],
"routes": [{ "src": "/", "dest": "index.js" }],
"env": {
"NEO4J_URI": "bolt+routing://32a85dd9.databases.neo4j.io",
"NEO4J_USER": "ukcompanies",
"NEO4J_PASSWORD": "ukcompanies"
}
}
Вы можете узнать больше о настройке развертываний Zeit Now в документации здесь.
Попробуйте в Codesandbox
Вы можете найти код из этого сообщения на GitHub здесь или запустить его прямо в Codesandbox здесь. Он настроен так, чтобы указывать на мой экземпляр Neo4j Aura (не беспокойтесь, это просто пользователь только для чтения), поэтому просто измените учетные данные в .env, чтобы они указывали на ваш собственный экземпляр.

Если вам это показалось интересным, подпишитесь на список рассылки GRANDstack, чтобы быть в курсе всех новостей GRANDstack:
