中文

学习可扩展的 GraphQL schema 设计模式,以构建能够满足全球多元化用户需求的稳健且可维护的 API。掌握 schema stitching、federation 和模块化。

GraphQL Schema 设计:面向全球 API 的可扩展模式

GraphQL 已成为传统 REST API 的强大替代品,它为客户端提供了精确请求所需数据的灵活性。然而,随着您的 GraphQL API 的复杂性和范围不断增长——尤其是在服务具有不同数据需求的全球用户时——精心的 schema 设计对于可维护性、可扩展性和性能变得至关重要。本文探讨了几种可扩展的 GraphQL schema 设计模式,以帮助您构建能够应对全球应用需求的稳健 API。

可扩展 Schema 设计的重要性

一个设计良好的 GraphQL schema 是一个成功的 API 的基础。它决定了客户端如何与您的数据和服务进行交互。糟糕的 schema 设计可能导致一系列问题,包括:

对于全球性应用,这些问题会被放大。不同地区可能有不同的数据要求、法规限制和性能期望。一个可扩展的 schema 设计使您能够有效地应对这些挑战。

可扩展 Schema 设计的关键原则

在深入探讨具体模式之前,让我们概述一些应该指导您进行 schema 设计的关键原则:

可扩展 Schema 设计模式

以下是几种可用于构建稳健 GraphQL API 的可扩展 schema 设计模式:

1. Schema Stitching

Schema stitching 允许您将多个 GraphQL API 合并成一个统一的 schema。当您有不同的团队或服务负责数据的不同部分时,这尤其有用。这就像拥有几个迷你 API,并通过一个“网关” API 将它们连接在一起。

工作原理:

  1. 每个团队或服务都公开其自己的 GraphQL API 及其 schema。
  2. 一个中央网关服务使用 schema stitching 工具(如 Apollo Federation 或 GraphQL Mesh)将这些 schema 合并成一个单一、统一的 schema。
  3. 客户端与网关服务交互,网关服务将请求路由到适当的底层 API。

示例:

想象一个电子商务平台,它有针对产品、用户和订单的独立 API。每个 API 都有自己的 schema:

  
    # 产品 API
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # 用户 API
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # 订单 API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
    }

    type Query {
      order(id: ID!): Order
    }
  

网关服务可以缝合这些 schema,以创建一个统一的 schema:

  
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Order {
      id: ID!
      user: User! @relation(field: "userId")
      product: Product! @relation(field: "productId")
      quantity: Int!
    }

    type Query {
      product(id: ID!): Product
      user(id: ID!): User
      order(id: ID!): Order
    }
  

请注意,Order 类型现在包含了对 UserProduct 的引用,尽管这些类型是在不同的 API 中定义的。这是通过 schema stitching 指令(在本例中为 @relation)实现的。

优点:

注意事项:

2. Schema Federation

Schema federation 是 schema stitching 的演进,旨在解决其某些局限性。它提供了一种更具声明性和标准化的方法来组合 GraphQL schema。

工作原理:

  1. 每个服务公开一个 GraphQL API,并使用 federation 指令(例如,@key, @extends, @external)来注解其 schema。
  2. 一个中央网关服务(使用 Apollo Federation)使用这些指令来构建一个 supergraph——整个联合 schema 的表示。
  3. 网关服务使用 supergraph 将请求路由到相应的底层服务并解决依赖关系。

示例:

使用相同的电子商务示例,联合 schema 可能如下所示:

  
    # 产品 API
    type Product @key(fields: "id") {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # 用户 API
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # 订单 API
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
      user: User! @requires(fields: "userId")
      product: Product! @requires(fields: "productId")
    }

    extend type Query {
      order(id: ID!): Order
    }
  

请注意 federation 指令的使用:

优点:

注意事项:

3. 模块化 Schema 设计

模块化 schema 设计涉及将一个庞大、单一的 schema 分解为更小、更易于管理的模块。这使得理解、修改和重用 API 的各个部分变得更加容易,即使不使用联合 schema 也是如此。

工作原理:

  1. 在您的 schema 中识别逻辑边界(例如,用户、产品、订单)。
  2. 为每个边界创建独立的模块,定义与该边界相关的类型、查询和变更。
  3. 使用导入/导出机制(取决于您的 GraphQL 服务器实现)将模块组合成一个单一、统一的 schema。

示例 (使用 JavaScript/Node.js):

为每个模块创建单独的文件:

  
    // users.graphql
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    // products.graphql
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }
  

然后,在您的主 schema 文件中将它们组合起来:

  
    // schema.js
    const { makeExecutableSchema } = require('graphql-tools');
    const { typeDefs: userTypeDefs, resolvers: userResolvers } = require('./users');
    const { typeDefs: productTypeDefs, resolvers: productResolvers } = require('./products');

    const typeDefs = [
      userTypeDefs,
      productTypeDefs,
      ""
    ];

    const resolvers = {
      Query: {
        ...userResolvers.Query,
        ...productResolvers.Query,
      }
    };

    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    module.exports = schema;
  

优点:

注意事项:

4. 接口 (Interface) 与联合类型 (Union Types)

接口和联合类型允许您定义可由多个具体类型实现的抽象类型。这对于表示多态数据——即根据上下文可以呈现不同形式的数据——非常有用。

工作原理:

示例:

  
    interface Node {
      id: ID!
    }

    type User implements Node {
      id: ID!
      name: String!
      email: String!
    }

    type Product implements Node {
      id: ID!
      name: String!
      price: Float!
    }

    union SearchResult = User | Product

    type Query {
      node(id: ID!): Node
      search(query: String!): [SearchResult!]!
    }
  

在此示例中,UserProduct 都实现了 Node 接口,该接口定义了一个公共的 id 字段。SearchResult 联合类型表示一个可以是 UserProduct 的搜索结果。客户端可以查询 search 字段,然后使用 __typename 字段来确定他们收到的结果类型。

优点:

注意事项:

5. Connection 模式

Connection 模式是在 GraphQL API 中实现分页的标准方式。它提供了一种一致且高效的方式来分块检索大量数据列表。

工作原理:

示例:

  
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type UserEdge {
      node: User!
      cursor: String!
    }

    type UserConnection {
      edges: [UserEdge!]!
      pageInfo: PageInfo!
    }

    type PageInfo {
      hasNextPage: Boolean!
      hasPreviousPage: Boolean!
      startCursor: String
      endCursor: String
    }

    type Query {
      users(first: Int, after: String, last: Int, before: String): UserConnection!
    }
  

优点:

注意事项:

全球化考量

在为全球用户设计 GraphQL schema 时,请考虑以下额外因素:

例如,考虑一个产品描述字段:


type Product {
 id: ID!
 name: String!
 description(language: String = "en"): String!
}

这允许客户端请求特定语言的描述。如果未指定语言,则默认为英语 (`en`)。

结论

可扩展的 schema 设计对于构建能够应对全球应用需求的稳健且可维护的 GraphQL API 至关重要。通过遵循本文中概述的原则并使用适当的设计模式,您可以创建易于理解、修改和扩展的 API,同时提供卓越的性能和可扩展性。请记住对您的 schema 进行模块化、组合和抽象,并考虑全球用户的特定需求。

通过采用这些模式,您可以释放 GraphQL 的全部潜力,并构建能够为您的应用程序提供长久动力的 API。