跳到主要内容

模式生成器

在本节中,我们将延续 GraphQL 示例,讲解如何从 ent/schema 生成类型安全的 GraphQL 模式。

配置 Ent

ent/entc.go 文件中,添加高亮行(扩展选项):

ent/entc.go
func main() {
ex, err := entgql.NewExtension(
entgql.WithWhereInputs(true),
entgql.WithConfigPath("../gqlgen.yml"),
entgql.WithSchemaGenerator(),
entgql.WithSchemaPath("../ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
entc.TemplateDir("./template"),
}
if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

WithSchemaGenerator 选项用于启用 GraphQL 模式生成功能。

Todo 模式添加注解

entgql.RelayConnection() 注解用于为 Todo 类型生成 Relay <T>Edge<T>ConnectionPageInfo 类型。

entgql.QueryField() 注解用于在 Query 类型中生成 todos 字段。

ent/schema/todo.go
// Edges of the Todo.
func (Todo) Edges() []ent.Edge {
return []ent.Edge{
edge.To("parent", Todo.Type).
Unique().
From("children").
}
}

// Annotations of the Todo.
func (Todo) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.RelayConnection(),
entgql.QueryField(),
}
}

entgql.RelayConnection() 注解也可用于边字段,以生成 first、last、after、before... 等参数,并将字段类型改为 <T>Connection!。例如,将 children 字段从 children: [Todo!]! 改为 children(first: Int, last: Int, after: Cursor, before: Cursor): TodoConnection!。您可以在边字段上添加 entgql.RelayConnection() 注解:

ent/schema/todo.go
// Edges of the Todo.
func (Todo) Edges() []ent.Edge {
return []ent.Edge{
edge.To("parent", Todo.Type).
Unique().
From("children").
Annotations(entgql.RelayConnection()),
}
}

清理手写模式

请从 todo.graphql 中移除以下类型,以避免与 EntGQL 在 ent.graphql 文件中生成的类型发生冲突。

todo.graphql
-interface Node {
- id: ID!
-}

"""将 Time GraphQL 标量映射到 Go time.Time 结构体。"""
scalar Time

-"""
-定义 Relay Cursor 类型:
-https://relay.dev/graphql/connections.htm#sec-Cursor
-"""
-scalar Cursor

-"""
-定义枚举类型,稍后将其映射到 Ent 枚举(Go 类型)。
-https://graphql.org/learn/schema/#enumeration-types
-"""
-enum Status {
- IN_PROGRESS
- COMPLETED
-}
-
-type PageInfo {
- hasNextPage: Boolean!
- hasPreviousPage: Boolean!
- startCursor: Cursor
- endCursor: Cursor
-}

-type TodoConnection {
- totalCount: Int!
- pageInfo: PageInfo!
- edges: [TodoEdge]
-}

-type TodoEdge {
- node: Todo
- cursor: Cursor!
-}

-"""以下枚举与 ent/schema 中的 entgql 注解匹配。"""
-enum TodoOrderField {
- CREATED_AT
- PRIORITY
- STATUS
- TEXT
-}

-enum OrderDirection {
- ASC
- DESC
-}

input TodoOrder {
direction: OrderDirection!
field: TodoOrderField
}

-"""
-定义对象类型,稍后将其映射到生成的 Ent 模型。
-https://graphql.org/learn/schema/#object-types-and-fields
-"""
-type Todo implements Node {
- id: ID!
- createdAt: Time
- status: Status!
- priority: Int!
- text: String!
- parent: Todo
- children: [Todo!]
-}

"""
为以下变更定义输入类型。
https://graphql.org/learn/schema/#input-types
请注意,此类型映射到 mutation_input.go 中
生成的输入类型。
"""
input CreateTodoInput {
status: Status! = IN_PROGRESS
priority: Int
text: String
parentID: ID
ChildIDs: [ID!]
}

"""
为以下变更定义输入类型。
https://graphql.org/learn/schema/#input-types
请注意,此类型映射到 mutation_input.go 中
生成的输入类型。
"""
input UpdateTodoInput {
status: Status
priority: Int
text: String
parentID: ID
clearParent: Boolean
addChildIDs: [ID!]
removeChildIDs: [ID!]
}

"""
定义用于创建待办事项的变更。
https://graphql.org/learn/queries/#mutations
"""
type Mutation {
createTodo(input: CreateTodoInput!): Todo!
updateTodo(id: ID!, input: UpdateTodoInput!): Todo!
updateTodos(ids: [ID!]!, input: UpdateTodoInput!): [Todo!]!
}

-"""定义用于获取所有待办事项的查询,并支持 Node 接口。"""
-type Query {
- todos(after: Cursor, first: Int, before: Cursor, last: Int, orderBy: TodoOrder, where: TodoWhereInput): TodoConnection
- node(id: ID!): Node
- nodes(ids: [ID!]!): [Node]!
-}

确保 Ent 和 GQLGen 的执行顺序

我们还需要对 generate.go 文件进行一些更改,以确保 Ent 和 GQLGen 的执行顺序。这样做的原因是确保 GQLGen 能够识别 Ent 创建的对象并正确执行代码生成器。

首先,删除 ent/generate.go 文件。然后,更新 ent/entc.go 文件中的路径,因为 Ent 代码生成将从项目根目录运行。

ent/entc.go
func main() {
ex, err := entgql.NewExtension(
entgql.WithWhereInputs(true),
- entgql.WithConfigPath("../gqlgen.yml"),
+ entgql.WithConfigPath("./gqlgen.yml"),
entgql.WithSchemaGenerator(),
- entgql.WithSchemaPath("../ent.graphql"),
+ entgql.WithSchemaPath("./ent.graphql"),
)
if err != nil {
log.Fatalf("creating entgql extension: %v", err)
}
opts := []entc.Option{
entc.Extensions(ex),
- entc.TemplateDir("./template"),
+ entc.TemplateDir("./ent/template"),
}
- if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
+ if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}

更新 generate.go 以包含 ent 代码生成。

generate.go
package todo

//go:generate go run -mod=mod ./ent/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen

更改 generate.go 文件后,我们可以按如下方式执行代码生成:

go generate ./...

您将看到 ent.graphql 文件将使用 EntGQL 模式生成器的新内容进行更新。

扩展由 Ent 生成的类型

您可能会注意到,生成的类型将包含 Query 类型对象及其已定义的一些字段:

type Query {
"""通过 ID 获取对象。"""
node(
"""对象的 ID。"""
id: ID!
): Node
"""通过 ID 列表查找节点。"""
nodes(
"""节点 ID 列表。"""
ids: [ID!]!
): [Node]!
todos(
"""返回列表中位于指定游标之后的元素。"""
after: Cursor

"""返回列表中的前 _n_ 个元素。"""
first: Int

"""返回列表中位于指定游标之前的元素。"""
before: Cursor

"""返回列表中的最后 _n_ 个元素。"""
last: Int

"""用于从连接中返回的待办事项的排序选项。"""
orderBy: TodoOrder

"""用于从连接中返回的待办事项的筛选选项。"""
where: TodoWhereInput
): TodoConnection!
}

要向 Query 类型添加新字段,您可以执行以下操作:

todo.graphql
extend type Query {
"""返回字面字符串 'pong'。"""
ping: String!
}

您可以扩展由 Ent 生成的任何类型。要跳过类型中的某个字段,可以在该字段或边上使用 entgql.Skip()


干得漂亮!如您所见,在采用了模式生成器功能后,我们不再需要手动编写 GQL 模式了。有疑问吗?需要入门帮助?欢迎加入我们的 Discord 服务器Slack 频道