模式基础#

GraphQL 服务器使用 模式 (schema)来描述数据的形状。模式定义了类型(types)的层次结构,其中的字段是从数据存储中填充的。模式还精确地指定了哪些查询和变更可以供客户端执行。

导航

本指南描述了模式的基本构建块,以及如何使用 Strawberry 创建模式。

模式定义语言(SDL)#

有两种方法可以为 GraphQL 服务器创建模式。一个称为“模式优先”,另一个称为“代码优先”。Strawberry 只支持代码优先模式。在深入研究代码之前,先解释一下 SDL(Schema definition language)是什么。

模式首先使用 GraphQL 的模式定义语言工作,该语言包含在 GraphQL 规范中。

下面是使用 SDL 定义的模式示例:

type Book {
  title: String!
  author: Author!
}

type Author {
  name: String!
  books: [Book!]!
}

模式定义了所有类型和它们之间的关系。通过这种方式,使客户开发人员能够准确地看到哪些数据可用,并请求该数据的特定子集。

备注

! 符号指定字段是非空的。

注意,模式没有指定如何获取数据。这将在稍后定义解析器时出现。

代码优化方法#

如前所述,Strawberry 使用代码优先的方法。之前的模式在 Strawberry 中是这样的

import strawberry

@strawberry.type
class Book:
    title: str
    author: "Author"

@strawberry.type
class Author:
    name: str
    books: list["Book"]

正如你所看到的,由于 python 的类型提示特性,代码几乎与模式一一对应。注意,这里也没有指定如何获取数据,这将在解析器一节中解释。

受支持类型#

GraphQL 支持几种不同的类型:

标量类型#

标量(Scalar)类型类似于 Python 的基本类型。下面是 GraphQL 中默认标量类型的列表:

  • Int, a signed 32-bit integer, maps to python’s int

  • Float, a signed double-precision floating-point value, maps to python’s float

  • String, maps to python’s str

  • Boolean, true or false, maps to python’s bool

  • ID, a unique identifier that usually used to refetch an object or as the key for a cache. Serialized as string and available as strawberry.ID(“value”)

  • UUID, a UUID value serialized as a string

备注

Strawberry 还包括对 date, time 和 datetime 对象的支持,它们没有正式包含在 GraphQL 规范中,但大多数服务器通常都需要它们。它们被序列化为 ISO-8601。

这些原语适用于大多数用例,但您也可以指定自己的标量类型

Object 类型#

在 GraphQL 模式中定义的大多数类型都是对象类型。对象类型包含字段集合,每个字段可以是标量类型,也可以是另一个对象类型。对象类型可以相互引用,就像前面的模式中那样:

import strawberry


@strawberry.type
class Book:
    title: str
    author: "Author"

@strawberry.type
class Author:
    name: str
    books: list[Book]

向字段提供数据#

在上面的模式中,Bookauthor 字段,Authorbooks 字段,但是不知道如何映射数据来实现所承诺的模式的结构。

为了实现这一点,引入了 解析器 的概念,它通过函数向字段提供一些数据。继续本例中的 booksauthors,可以定义解析器为字段提供值:

def get_author_for_book(root) -> "Author":
    return Author(name="Michael Crichton")


@strawberry.type
class Book:
    title: str
    author: "Author" = strawberry.field(resolver=get_author_for_book)


def get_books_for_author(root):
    return [Book(title="Jurassic Park")]


@strawberry.type
class Author:
    name: str
    books: list[Book] = strawberry.field(resolver=get_books_for_author)


def get_authors(root) -> list[Author]:
    return [Author(name="Michael Crichton")]


@strawberry.type
class Query:
    authors: list[Author] = strawberry.field(resolver=get_authors)
    books: list[Book] = strawberry.field(resolver=get_books_for_author)

这些函数为 strawberry.field 提供了在请求时将数据呈现给 GraphQL 查询的能力,并且是所有 GraphQL APIs 的主干。

这个例子很简单,因为解析的数据完全是静态的。然而,当构建更复杂的 API 时,可以编写这些解析器来映射数据库中的数据,例如使用 SQLAlchemy 进行 SQL 查询,以及其他 API,例如使用 aiohttp 进行 HTTP 请求。有关编写解析器的不同方法的更多信息和详细信息,请参阅解析器部分

Query 类型#

Query 类型准确地定义了哪些 GraphQL 查询(即读操作)客户端可以对您的数据执行。它类似于对象类型,但它的名称总是 Query

Query 类型的每个字段定义了受支持的不同查询的名称和返回类型。示例模式的 Query 类型可能类似于以下内容:

@strawberry.type
class Query:
    books: list[Book]
    authors: list[Author]

这个 Query 类型定义了两个可用的查询:图书和作者。每个查询返回对应类型的列表。

对于基于 REST 的 API,书籍和作者可能会由不同的端点返回(例如,/api/books /api/authors)。GraphQL 的灵活性使客户端可以通过一个请求查询两个资源。

结构化查询#

当客户端构建针对数据流图执行的查询时,这些查询与您在模式中定义的对象类型的形状匹配。基于到目前为止的示例模式,客户端可以执行以下查询,它请求所有书名列表和所有作者名称列表:

query {
  books {
    title
  }

  authors {
    name
  }
}

然后,服务器会以与查询结构匹配的结果响应查询,如下所示:

{
  "data": {
    "books": [{ "title": "Jurassic Park" }],
    "authors": [{ "name": "Michael Crichton" }]
  }
}

尽管在某些情况下获取这两个单独的列表可能很有用,但客户端可能更喜欢获取单个图书列表,其中每本书的作者都包含在结果中。因为我们的模式的 Book 类型有 author 类型的字段,客户端可以这样组织查询:

query {
  books {
    title
    author {
      name
    }
  }
}

再一次,服务器将响应与查询结构匹配的结果:

{
  "data": {
    "books": [
      { "title": "Jurassic Park", "author": { "name": "Michael Crichton" } }
    ]
  }
}

Mutation 类型#

Mutation 类型在结构和用途上与 Query 类型相似。Query 类型定义了数据支持的读操作,而 Mutation 类型定义了支持的写操作。

Mutation 类型的每个字段都定义了不同变更的签名和返回类型。示例模式的 Mutation 类型可能类似于以下内容:

@strawberry.type
class Mutation:
    @strawberry.field
    def add_book(self, title: str, author: str) -> Book:
        ...

这个 Mutation 类型定义了可用的 mutation addBook。mutation 接受两个参数(titleauthor)并返回新创建的 Book 对象。如您所料,这个 Book 对象符合在模式中定义的结构。

备注

Strawberry 自动将字段名称从 snake 的大小写转换为 camel 的大小写。

结构化 mutation#

与查询一样,变更与模式类型定义的结构相匹配。下面的变更创建了新的 Book,并请求创建对象的某些字段作为返回值:

mutation {
  addBook(title: "Fox in Socks", author: "Dr. Seuss") {
    title
    author {
      name
    }
  }
}

和查询一样,服务器会用与变更结构匹配的结果来响应这个变更,如下所示:

{
  "data": {
    "addBook": {
      "title": "Fox in Socks",
      "author": {
        "name": "Dr. Seuss"
      }
    }
  }
}

小技巧

需要添加如下语句,使之生效:

schema = strawberry.Schema(query=Query, mutation=Mutation)

Input 类型#

Input 类型是特殊的对象类型,允许您将对象作为参数传递给查询和变更(与仅传递标量类型相反)。输入类型有助于保持操作签名的干净。

考虑之前添加一本书的变更:

@strawberry.type
class Mutation:
    @strawberry.field
    def add_book(self, title: str, author: str) -> Book:
        ...

这种变更可以接受包含所有这些字段的单一输入类型,而不是接受两个参数。如果决定在将来接受额外的参数,比如发表日期,这就非常方便了。

Input 类型的定义类似于对象类型的定义,但它使用 strawberry.input 关键字:

@strawberry.input
class AddBookInput:
    title: str
    author: str


@strawberry.type
class Mutation:
    @strawberry.field
    def add_book(self, book: AddBookInput) -> Book:
        ...

这不仅方便了在模式中传递 AddBookInput 类型,还为使用 GraphQL 工具自动公开的描述注释字段提供了基础:

@strawberry.input
class AddBookInput:
    title: str = strawberry.field(description="The title of the book")
    author: str = strawberry.field(description="The name of the author")

当多个操作需要完全相同的信息集时,输入类型有时很有用,但应该谨慎地重用它们。操作最终可能会在必需的参数集中出现分歧。

参见

如果您想了解更多关于模式设计的知识,请确保遵循 Apollo 提供的文档