模式基础#
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]
向字段提供数据#
在上面的模式中,Book
有 author
字段,Author
有 books
字段,但是不知道如何映射数据来实现所承诺的模式的结构。
为了实现这一点,引入了 解析器 的概念,它通过函数向字段提供一些数据。继续本例中的 books
和 authors
,可以定义解析器为字段提供值:
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 接受两个参数(title
和 author
)并返回新创建的 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 提供的文档。