65.9K
CodeProject 正在变化。 阅读更多。
Home

GraphQL 如何使图数据库与全栈开发人员相关

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2022年6月2日

CPOL

8分钟阅读

viewsIcon

4501

由 Neo4j(原生图数据库)支持的 GraphQL API 可以轻松地建模、查询和解析复杂的关系。这对于新闻编辑室来说可能是一大福音,因为在那里通常没有什么事情是容易的。

由 Neo4j(原生图数据库)支持的 GraphQL API 可以轻松地建模、查询和解析复杂的关系。这对于新闻编辑室来说可能是一大福音,因为在那里通常没有什么事情是容易的。

编者注:对于世界上许多在线新闻出版商来说,知名商业内容管理系统 (CMS) 是不够的。一些新闻机构已经合作开发了自己的插件来支持 WordPress 等系统,以解决功能不足的问题。另一些机构则没有时间或资源进行合作,最终只能改变自己的运营模式以适应 CMS,在这个过程中改变了它们自己的故事框架。还有一些机构会投入巨资设计和构建自己的一体化系统,但后来才发现持续投入巨额资金用于维护、功能添加和安全仍难以承受。在接下来的演示中,Neo4j 的 William Lyon 通过演示现代图数据库如何利用关系建立新闻文章之间的上下文关联来直接解决这些问题。这样的进步有可能彻底改变现有的 CMS 平台以及自建的新闻编辑室管理系统,赋予它们所有传统 CMS 通常缺乏的通用性、适应性和性能

GraphQL 已成为一种高效且强大的技术,用于构建位于客户端和数据库之间的 API 层。在本文中,我们将探讨如何使用 Neo4j 图数据库和 Neo4j GraphQL 库,为新闻文章构建一个具有搜索、个性化推荐和数据丰富功能的 GraphQL API。读完本文,我希望您能认识到图形化思维的强大之处,特别是将图数据库与 GraphQL 配对使用,并能获得一些关于如何将这项技术应用到您自己的领域中的想法。

GraphQL 如何让图数据库与全栈开发人员息息相关

从根本上说,GraphQL 是一个用于 API 的查询语言,以及一个用于针对数据层执行这些请求的运行时。GraphQL 使用严格的类型系统来定义 API 中可用的数据、它们如何连接以及 API 支持的操作类型。与返回每个资源固定属性集的 REST API 不同,GraphQL 允许客户端自由地只请求所需的数据,从而避免过度获取或获取不足,确保返回的数据正是所需。GraphQL 通常因其内省功能而被认为具有自文档性。这允许客户端查看 API 模式,从而生成依赖于 GraphQL 类型系统的文档或开发工具。

GraphQL 与数据层无关 - 我们可以使用任何数据库或数据层构建 GraphQL API,包括封装其他 API。由于 GraphQL 将应用程序数据视为一个图,因此 Neo4j 作为图数据库非常适合 GraphQL 后端。通过这种方式,我们可以将图形化思维应用到整个全栈应用程序中,从数据库到前端数据获取的 GraphQL 查询。

GraphQL API 通常如何构建,以及常见的挑战

要了解 GraphQL API 应用程序是如何构建的,有两个重要的 GraphQL 特定概念需要理解:*类型定义*和*解析器函数*。

GraphQL 类型定义定义了 API 中可用的数据以及数据的连接方式。这些类型定义通常使用 GraphQL Schema Definition Language (SDL) 定义。但是,也可以以编程方式定义类型定义。在这里,我们看到一个简单的会议应用程序的类型定义,该应用程序处理在特定房间中进行的、具有相关主题的会议。

type Session {
  sessionId: ID!
  title: String!
  description: String!
  time: String
  room: Room
  theme: [Theme!]!
  recommended: [Session]
}

type Room {
  name: String
  building: String
  sessions: [Session!]!
}

type Theme {
  name: String!
  description: String
  sessions: [Sessions!]!
}

GraphQL 解析器函数是负责实际完成 GraphQL 操作的函数。在查询的上下文中,这意味着从数据层获取数据。让我们看一个示例,了解我们会议 GraphQL 类型定义的解析器函数可能是什么样子。

const resolvers = {
  Query: {
    Session: (object, params, context, info) => {
      return context.db.sessionsBySearch(params.searchString);
    }
  },
  Session: {
    room: (object, params, context, info) => {
      return context.db.roomForSession(object.sessionId);
    },
    theme: (object, params, context, info) => {
      return context.db.themeForSession(object.sessionId);
    },
    recommended: (object, params, context, info) => {
      return context.db.recommendedSession(object.sessionId)
    }
  }
}

在这里,我们在每个解析器函数中使用一个虚构的数据访问层来调用数据库,并使用传递到解析器函数的数据来执行搜索或按 ID 查找会议。由于这些解析器以嵌套方式调用,我们通常会在单个 GraphQL 操作中发出多个数据库请求。这可能会导致性能问题,因为每次访问数据层都会增加开销。这就是所谓的 N+1 查询问题,是构建 GraphQL API 时常见的一个问题。

另一个常见问题是需要在 GraphQL 解析器中编写大量样板数据获取代码。幸运的是,有一些数据库集成可以用于构建 GraphQL API,以解决这些问题,并为开发人员提供其他增强功能。

Neo4j GraphQL 库

Neo4j GraphQL 库是一个 Node.js 包,可以更轻松地构建由 Neo4j 图数据库支持的 GraphQL API。Neo4j GraphQL 库有四个主要目标:

  1. 支持 GraphQL 优先开发
  2. 自动生成 GraphQL API 操作
  3. 从 GraphQL 操作生成数据库查询
  4. 用 Cypher 的力量扩展 GraphQL

让我们以使用《纽约时报》的数据构建新闻文章 GraphQL API 为背景,详细介绍这些要点。

支持 GraphQL 优先开发

使用 Neo4j GraphQL 库,GraphQL 类型定义将驱动数据库数据模型。这意味着我们不需要为 API 和数据库维护两个独立的模式。取而代之的是,数据模型由 GraphQL 类型定义来定义。以下是我们的新闻 API 的 GraphQL 类型定义。

type Article {
  abstract: String
  published: Date
  title: String
  url: String!
  authors: [Author!]! @relationship(type: "BYLINE", direction: OUT)
  topics: [Topic!]! @relationship(type: "HAS_TOPIC", direction: OUT)
  people: [Person!]! @relationship(type: "ABOUT_PERSON", direction: OUT)
  organizations: [Organization!]! @relationship(type: "ABOUT_ORGANIZATION", direction: OUT)
  geos: [Geo!]! @relationship(type: "ABOUT_GEO", direction: OUT)
}

type Author {
  name: String!
  articles: [Article!]! @relationship(type: "BYLINE", direction: IN)
}

type Topic {
  name: String!
  articles: [Article!]! @relationship(type: "HAS_TOPIC", direction: IN)
}

type Person {
  name: String!
  articles: [Article!]!
    @relationship(type: "ABOUT_PERSON", direction: IN)
}

type Organization {
  name: String!
  articles: [Article!]! @relationship(type: "ABOUT_ORGANIZATION", direction: IN)
}

type Geo {
  name: String!
  location: Point
  articles: [Article!]! @relationship(type: "ABOUT_GEO", direction: IN)
}

请注意类型定义中 @relationship schema directive 的使用。Schema directive 是 GraphQL 内置的扩展机制,并且被 Neo4j GraphQL 库广泛用于配置生成的 GraphQL API。在这种情况下,我们使用 @relationship 指令来指定关系的类型和方向。

这些 GraphQL 类型定义映射到 Neo4j 中的以下图形数据模型。使用 Neo4j GraphQL 库,我们现在同时定义了 GraphQL API 和数据库的数据模型。

自动生成 GraphQL API 操作

创建 GraphQL 类型定义后,我们将它们传递给 Neo4j GraphQL 库以创建可执行的 GraphQL 模式,然后可以使用 Apollo Server 等 GraphQL 服务器来提供该模式。我们还需要使用数据库连接字符串创建一个数据库驱动程序实例。我已将凭据存储为环境变量,并使用了 Neo4j AuraDB 免费套餐来创建云 Neo4j 实例。

const { Neo4jGraphQL } = require("@neo4j/graphql");
const { ApolloServer } = require("apollo-server");
const neo4j = require("neo4j-driver");


// Create Neo4j driver instance
const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
);

// Our GraphQL type definitions from above
const typeDefs = …

// Create executable GraphQL schema from GraphQL type definitions,
// using @neo4j/graphql to autogenerate resolvers
const neoSchema = new Neo4jGraphQL({
  typeDefs,
  driver,
});

// Create a new Apollo Server instance using our Neo4j GraphQL schema
neoSchema.getSchema().then((schema) => {
  const server = new ApolloServer({
    schema,
  });
  server.listen().then(({ url }) => {
    console.log(`GraphQL server ready at ${url}`);
  });
});

借助 Neo4j GraphQL 库,GraphQL 类型定义为生成的 API 提供了起点,该 API 包括 Query 和 Mutation 类型,以及模式中定义的每个类型的入口点,用于排序、分页和复杂过滤的参数,以及对 DateTime 和 Point 等原生数据类型的支持。

例如,以下 GraphQL 代码查询距离旧金山 10 公里范围内、关于地理区域的 10 篇最新文章。

{
  articles(
    where: {
      geos_SOME: {
        location_LT: {
          point: { latitude: 37.7749, longitude: -122.4194 }
          distance: 10000
        }
      }
    }
    options: { sort: { published: DESC }, limit: 10 }
  ) {
    title
    url
    topics {
      name
    }
  }
}

请注意,我们不需要编写任何解析器函数来定义我们的数据获取。所有这些都由 Neo4j GraphQL 库为我们处理,大大减少了启动 GraphQL API 所需的代码量。

从 GraphQL 操作生成数据库查询

在查询时,Neo4j GraphQL 库会检查传入的 GraphQL 操作,并为该 GraphQL 操作(查询或修改)生成单个数据库查询。这通过一次数据库往返解决了 N+1 查询问题。此外,Neo4j 等图数据库针对许多 GraphQL 操作中通常指定的嵌套遍历类型进行了优化。

要了解有关 Neo4j GraphQL 库等数据库集成如何在后台工作的更多信息,请查看此 GraphQL Summit 的演示文稿,其中提供了更详细的介绍。

用 Cypher 的力量扩展 GraphQL

Cypher 是 Neo4j 等图数据库使用的强大图形查询语言。与 GraphQL(一种 API 查询语言而不是数据库查询语言)不同,Cypher 支持复杂的图形操作,如模式匹配和可变长度路径操作。

Neo4j GraphQL 库允许我们在 GraphQL 类型定义中使用 Cypher 定义自定义逻辑。为此,我们在 GraphQL 类型定义中使用 @cypher GraphQL schema directive。这意味着我们只需使用 GraphQL 类型定义即可为我们的 GraphQL API 添加自定义逻辑!

让我们通过为我们的 API 添加文章推荐功能来了解其工作原理。如果用户正在阅读一篇文章,让我们通过查找具有共同主题、或关于同一地理区域或人物的文章来找到他们可能感兴趣的其他类似文章。我们将为 Article 类型添加一个 similar 字段,并添加一个包含此逻辑的 @cypher 指令。

extend type Article {
  similar(first: Int = 3): [Article]
    @cypher(
      statement: """
      MATCH (this)-[:HAS_TOPIC|:ABOUT_GEO|:ABOUT_PERSON]->(t)
      MATCH (t)<-[:HAS_TOPIC|:ABOUT_GEO|:ABOUT_PERSON]-(rec:Article)
      RETURN rec ORDER BY COUNT(*) DESC LIMIT $first
      """
    )
}

虽然 Cypher 是专门为图形设计的,但它是一种非常强大的查询语言,支持调用其他 API 等功能。我们可以在 GraphQL API 中利用此功能从其他来源获取数据。在这里,我们通过调用 Google Knowledge Graph API 来查找有关文章中提到的人物更详细信息,从而补充我们的数据, essentially acting as a data federation mechanism.

extend type Person {
  description: String
    @cypher(
      statement: """
      WITH this, apoc.static.get('gcpkey') AS gcpkey,
        'https://kgsearch.googleapis.com/v1/entities:search?query=' AS baseURL
      CALL apoc.load.json(baseURL + apoc.text.urlencode(this.name) + '&limit=1&key=' + gcpkey)
      YIELD value
      RETURN value.itemListElement[0].result.detailedDescription.articleBody
      """
    )
}

Neo4j GraphQL 库是开源的,并且还有许多我们今天未介绍的功能,例如强大的授权模型、对Relay 式分页的支持、Subscriptions,以及更多功能!

资源

您可以在此 Codesandbox 中亲眼看到此示例的工作原理,或在GitHub 上找到所有代码。也请务必查看Neo4j GraphQL 登陆页面文档

© . All rights reserved.