在 graphql 中执行多个突变时的正确错误处理
Proper error handling when performing multiple mutations in graphql
鉴于以下 GraphQL 突变:
type Mutation {
updateUser(id: ID!, newEmail: String!): User
updatePost(id: ID!, newTitle: String!): Post
}
Apollo 文档指出完全可以在一个请求中执行多个变更,比如说
mutation($userId: ID!, $newEmail: String!, $postId: ID!, $newTitle: String!) {
updateUser(id: $userId, newEmail: $newEmail) {
id
email
}
updatePost(id: $postId, newTitle: $newTitle) {
id
title
}
}
1.真的有人这样做吗? 如果你不明确地这样做,批处理会导致这种突变合并吗?
2。如果你在突变中执行 运行 多项操作,你将如何正确处理错误?
我见过很多人建议在服务器上抛出错误,这样服务器就会像这样响应:
{
errors: [
{
statusCode: 422,
error: 'Unprocessable Entity'
path: [
'updateUser'
],
message: {
message: 'Validation failed',
fields: {
newEmail: 'The new email is not a valid email address.'
}
},
},
{
statusCode: 422,
error: 'Unprocessable Entity'
path: [
'updatePost'
],
message: {
message: 'Validation failed',
fields: {
newTitle: 'The given title is too short.'
}
},
}
],
data: {
updateUser: null,
updatePost: null,
}
}
但是我怎么知道哪个错误属于哪个突变?我们不能假设 errors
数组中的第一个错误属于第一个突变,因为如果 updateUser
成功,数组将只包含一个条目。然后我是否必须遍历所有错误并检查路径是否与我的突变名称匹配? :D
另一种方法是将错误包含在专用响应类型中,比如 UpdateUserResponse
和 UpdatePostResponse
。这种方法使我能够正确解决错误。
type UpdateUserResponse {
error: Error
user: User
}
type UpdatePostResponse {
error: Error
post: Post
}
但我感觉这会使我的架构膨胀很多。
简而言之,是的,如果您包含多个顶级突变字段,请利用错误上的 path
属性 来确定哪个突变失败。请注意,如果错误发生在图表的更深处(在某些子字段而不是根级字段上),路径将反映该字段。也就是说,解析 title
字段时发生的执行错误将导致 path
为 updatePost.title
。
作为 data
的一部分返回错误是一个同样有效的选项。这种方法还有其他好处:
- 像这样发送的错误可能包含额外的元数据("code"属性、关于可能产生错误的特定输入字段的信息等)。虽然可以通过
errors
数组发送相同的信息,但将其作为架构的一部分意味着客户端将了解这些错误对象的结构。这对于使用类型化语言编写的客户端尤其重要,因为客户端代码通常是从模式中生成的。
- 以这种方式返回客户端错误可以让您清楚地区分应该对用户可见的用户错误(错误的凭据、用户已经存在等)和客户端或服务器代码实际出错的地方(在这种情况下,我们充其量只能显示一些通用消息)。
- 像这样创建一个 "payload" 对象可以让您在以后添加额外的字段而不会破坏您的架构。
第三种选择是以类似的方式使用联合:
type Mutation {
updateUser(id: ID!, newEmail: String!): UpdateUserPayload!
}
union UpdateUserPayload = User | Error
这使客户端能够使用片段和 __typename
字段来区分成功和失败的突变:
mutation($userId: ID!, $newEmail: String!) {
updateUser(id: $userId, newEmail: $newEmail) {
__typename
... on User {
id
email
}
... on Error {
message
code
}
}
}
您甚至可以为每种错误创建特定类型,从而允许您忽略任何类型的 "code" 字段:
union UpdateUserPayload = User | EmailExistsError | EmailInvalidError
这里没有正确或错误的答案。虽然每种方法都有优点,但您最终会选择哪种方法取决于偏好。
鉴于以下 GraphQL 突变:
type Mutation {
updateUser(id: ID!, newEmail: String!): User
updatePost(id: ID!, newTitle: String!): Post
}
Apollo 文档指出完全可以在一个请求中执行多个变更,比如说
mutation($userId: ID!, $newEmail: String!, $postId: ID!, $newTitle: String!) {
updateUser(id: $userId, newEmail: $newEmail) {
id
email
}
updatePost(id: $postId, newTitle: $newTitle) {
id
title
}
}
1.真的有人这样做吗? 如果你不明确地这样做,批处理会导致这种突变合并吗?
2。如果你在突变中执行 运行 多项操作,你将如何正确处理错误?
我见过很多人建议在服务器上抛出错误,这样服务器就会像这样响应:
{
errors: [
{
statusCode: 422,
error: 'Unprocessable Entity'
path: [
'updateUser'
],
message: {
message: 'Validation failed',
fields: {
newEmail: 'The new email is not a valid email address.'
}
},
},
{
statusCode: 422,
error: 'Unprocessable Entity'
path: [
'updatePost'
],
message: {
message: 'Validation failed',
fields: {
newTitle: 'The given title is too short.'
}
},
}
],
data: {
updateUser: null,
updatePost: null,
}
}
但是我怎么知道哪个错误属于哪个突变?我们不能假设 errors
数组中的第一个错误属于第一个突变,因为如果 updateUser
成功,数组将只包含一个条目。然后我是否必须遍历所有错误并检查路径是否与我的突变名称匹配? :D
另一种方法是将错误包含在专用响应类型中,比如 UpdateUserResponse
和 UpdatePostResponse
。这种方法使我能够正确解决错误。
type UpdateUserResponse {
error: Error
user: User
}
type UpdatePostResponse {
error: Error
post: Post
}
但我感觉这会使我的架构膨胀很多。
简而言之,是的,如果您包含多个顶级突变字段,请利用错误上的 path
属性 来确定哪个突变失败。请注意,如果错误发生在图表的更深处(在某些子字段而不是根级字段上),路径将反映该字段。也就是说,解析 title
字段时发生的执行错误将导致 path
为 updatePost.title
。
作为 data
的一部分返回错误是一个同样有效的选项。这种方法还有其他好处:
- 像这样发送的错误可能包含额外的元数据("code"属性、关于可能产生错误的特定输入字段的信息等)。虽然可以通过
errors
数组发送相同的信息,但将其作为架构的一部分意味着客户端将了解这些错误对象的结构。这对于使用类型化语言编写的客户端尤其重要,因为客户端代码通常是从模式中生成的。 - 以这种方式返回客户端错误可以让您清楚地区分应该对用户可见的用户错误(错误的凭据、用户已经存在等)和客户端或服务器代码实际出错的地方(在这种情况下,我们充其量只能显示一些通用消息)。
- 像这样创建一个 "payload" 对象可以让您在以后添加额外的字段而不会破坏您的架构。
第三种选择是以类似的方式使用联合:
type Mutation {
updateUser(id: ID!, newEmail: String!): UpdateUserPayload!
}
union UpdateUserPayload = User | Error
这使客户端能够使用片段和 __typename
字段来区分成功和失败的突变:
mutation($userId: ID!, $newEmail: String!) {
updateUser(id: $userId, newEmail: $newEmail) {
__typename
... on User {
id
email
}
... on Error {
message
code
}
}
}
您甚至可以为每种错误创建特定类型,从而允许您忽略任何类型的 "code" 字段:
union UpdateUserPayload = User | EmailExistsError | EmailInvalidError
这里没有正确或错误的答案。虽然每种方法都有优点,但您最终会选择哪种方法取决于偏好。