突变输入
在本部分我们继续 GraphQL 示例 来解释如何使用 Go 语言模板 为 GraphQL 突变扩展 Ent 代码生成器并生成 输入类型 对象, 该对象可以直接应用到 Ent 突变中。
克隆代码(可选)
本教程代码可以在 github.com/a8m/ent-graphql-example 找到, 并在每一步都打了标签(使用 Git)。 如果你想要跳过基础安装并使用 GraphQL 最初的版本,你可以像如下克隆代码仓库:
git clone git@github.com:a8m/ent-graphql-example.git
cd ent-graphql-example
go run ./cmd/todo/
突变类型
Ent 支持生成图表类型。图表类型可以在 GraphQL 突变中作为输入接收,并由 Ent 处理并验证。
让我们告知 Ent 我们的 GraphQL Todo 类型支持创建和更新操作:
ent/schema/todo.go
func (Todo) Annotations() []schema.Annotation {
return []schema.Annotation{
entgql.QueryField(),
entgql.Mutations(entgql.MutationCreate(), entgql.MutationUpdate()),
}
}
然后运行代码生成:
go generate .
你会注意到 Ent 生成了两个类型: ent.CreateTodoInput 和 ent.UpdateTodoInput。
突变
生成完突变输入后,我们可以在 GraphQL 突变中接入他们:
todo.graphql
type Mutation {
createTodo(input: CreateTodoInput!): Todo!
updateTodo(id: ID!, input: UpdateTodoInput!): Todo!
}
运行代码生成会生成实际的突变,剩下的事情就是将解析器绑定到 Ent。
go generate .
todo.resolvers.go
// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
return r.client.Todo.Create().SetInput(input).Save(ctx)
}
// UpdateTodo is the resolver for the updateTodo field.
func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) {
return r.client.Todo.UpdateOneID(id).SetInput(input).Save(ctx)
}
测试 CreateTodo 解析器
让我们通过执行 createTodo 突变两次来创建两个待办事项条目。
突变
mutation CreateTodo {
createTodo(input: {text: "Create GraphQL Example", status: IN_PROGRESS, priority: 2}) {
id
text
createdAt
priority
parent {
id
}
}
}
输出
{
"data": {
"createTodo": {
"id": "1",
"text": "Create GraphQL Example",
"createdAt": "2021-04-19T10:49:52+03:00",
"priority": 2,
"parent": null
}
}
}
突变
mutation CreateTodo {
createTodo(input: {text: "Create Tracing Example", status: IN_PROGRESS, priority: 2}) {
id
text
createdAt
priority
parent {
id
}
}
}
输出
{
"data": {
"createTodo": {
"id": "2",
"text": "Create Tracing Example",
"createdAt": "2021-04-19T10:50:01+03:00",
"priority": 2,
"parent": null
}
}
}
测试 UpdateTodo 解析器
剩下的事情就是测试 UpdateTodo 解析器了。让我们使用它更新第二个待办事项条目的 parent 为 1。
mutation UpdateTodo {
updateTodo(id: 2, input: {parentID: 1}) {
id
text
createdAt
priority
parent {
id
text
}
}
}
输出
{
"data": {
"updateTodo": {
"id": "2",
"text": "Create Tracing Example",
"createdAt": "2021-04-19T10:50:01+03:00",
"priority": 1,
"parent": {
"id": "1",
"text": "Create GraphQL Example"
}
}
}
}
使用突变创建边
若在同一突变中创建节点的边,你可以扩展包括边字段的 GQL 突变输入:
extended.graphql
extend input CreateTodoInput {
createChildren: [CreateTodoInput!]
}
再次运行代码生成:
go generate .
GQLGen 会生成 createChildren 字段的解析器并允许你在自己的解析器中使用它:
extended.resolvers.go
// CreateChildren is the resolver for the createChildren field.
func (r *createTodoInputResolver) CreateChildren(ctx context.Context, obj *ent.CreateTodoInput, data []*ent.CreateTodoInput) error {
panic(fmt.Errorf("not implemented: CreateChildren - createChildren"))
}
现在我们需要实现创建子条目的逻辑:
extended.resolvers.go
// CreateChildren is the resolver for the createChildren field.
func (r *createTodoInputResolver) CreateChildren(ctx context.Context, obj *ent.CreateTodoInput, data []*ent.CreateTodoInput) error {
// NOTE: We need to use the Ent client from the context.
// To ensure we create all of the children in the same transaction.
// See: Transactional Mutations for more information.
c := ent.FromContext(ctx)
builders := make([]*ent.TodoCreate, len(data))
for i := range data {
builders[i] = c.Todo.Create().SetInput(*data[i])
}
todos, err := c.Todo.CreateBulk(builders...).Save(ctx)
if err != nil {
return err
}
ids := make([]int, len(todos))
for i := range todos {
ids[i] = todos[i].ID
}
obj.ChildIDs = append(obj.ChildIDs, ids...)
return nil
}
修改下面的行为使用事务化客户端:
todo.resolvers.go
// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
return ent.FromContext(ctx).Todo.Create().SetInput(input).Save(ctx)
}
// UpdateTodo is the resolver for the updateTodo field.
func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) {
return ent.FromContext(ctx).Todo.UpdateOneID(id).SetInput(input).Save(ctx)
}
测试带有子条目的突变:
Mutation
mutation {
createTodo(input: {
text: "parent", status:IN_PROGRESS,
createChildren: [
{ text: "children1", status: IN_PROGRESS },
{ text: "children2", status: COMPLETED }
]
}) {
id
text
children {
id
text
status
}
}
}
Output
{
"data": {
"createTodo": {
"id": "3",
"text": "parent",
"children": [
{
"id": "1",
"text": "children1",
"status": "IN_PROGRESS"
},
{
"id": "2",
"text": "children2",
"status": "COMPLETED"
}
]
}
}
}
如果你启用了调试客户端,你可以看到子条目在相同的事务中被创建了:
2022/12/14 00:27:41 driver.Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312): started
2022/12/14 00:27:41 Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312).Query: query=INSERT INTO `todos` (`created_at`, `priority`, `status`, `text`) VALUES (?, ?, ?, ?), (?, ?, ?, ?) RETURNING `id` args=[2022-12-14 00:27:41.046344 +0700 +07 m=+5.283557793 0 IN_PROGRESS children1 2022-12-14 00:27:41.046345 +0700 +07 m=+5.283558626 0 COMPLETED children2]
2022/12/14 00:27:41 Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312).Query: query=INSERT INTO `todos` (`text`, `created_at`, `status`, `priority`) VALUES (?, ?, ?, ?) RETURNING `id` args=[parent 2022-12-14 00:27:41.047455 +0700 +07 m=+5.284669251 IN_PROGRESS 0]
2022/12/14 00:27:41 Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312).Exec: query=UPDATE `todos` SET `todo_parent` = ? WHERE `id` IN (?, ?) AND `todo_parent` IS NULL args=[3 1 2]
2022/12/14 00:27:41 Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312).Query: query=SELECT DISTINCT `todos`.`id`, `todos`.`text`, `todos`.`created_at`, `todos`.`status`, `todos`.`priority` FROM `todos` WHERE `todo_parent` = ? args=[3]
2022/12/14 00:27:41 Tx(7e04b00b-7941-41c5-9aee-41c8c2d85312): committed