使用关注点分离进行命名空间划分
将根级操作字段组织到命名空间中
大部分GraphQL API将其功能作为根级字段提供,形成一个扁平结构。例如,GitHub GraphQL API大约有200个这样的根级字段!即使有像Apollo Explorer这样的工具,理解和导航较大的“扁平”图也可以是困难的。查询
和突变
类型,结果是一个扁平的结构。例如,GitHub GraphQL API有大约200个这样的根级字段!即使有了像Apollo Explorer这样的工具,导航和理解较大的“扁平”图表也可能很困难。
ⓘ 注意
务必阅读下面的注意事项部分。虽然命名空间模式在查询方面效果良好,但突变可能会有副作用,这可能不符合GraphQL规范。
为了提高我们的图逻辑组织能力,我们可以为根级操作字段定义操作的命名空间。这些是对象类型,它们进一步定义查询和突变字段,所有这些都与特定的关注点相关。
例如,我们可以在UsersMutations
命名空间对象中定义与User
对象相关的所有突变字段
type UsersMutations {create(profile: UserProfileInput!): User!block(id: ID!): User!}
然后,我们可以在Comment
突变中定义类似的命名空间:
type CommentsMutations {create(comment: CommentInput!): Comment!delete(id: ID!): Comment!}
现在,我们的User
和Comment
类型都有与它们相关联的create
突变字段,这是有效的,因为每个都在单独的命名空间类型内部定义。
最后,我们可以将命名空间类型添加到Mutation
类型的根级别字段的返回值中:
type Mutation {users: UsersMutations!comments: CommentsMutations!}
我们可以使用相同的模式来处理涉及User
和Comment
类型的查询:
type UsersQueries {all: [User!]!}type CommentsQueries {byUser(user: ID!): [Comment!]!}# Add a single root-level namespace-type which wraps other queriestype Query {users: UsersQueries!comments: CommentsQueries!}
现在,所有与操作相关的客户端
mutation CreateNewUser($userProfile: UserProfileInput!) {users {create(profile: $userProfile) {idfirstNamelastName}}}query FetchAllUsers {users {all {idfirstNamelastName}}}
ⓘ 注意
你不需要在UsersQueries
类型的字段名中重复user文本,因为我们已经知道所有这些字段都适用于User
对象。
序列突变命名空间
与其他所有 GraphQL 操作的 字段 不同,在 Mutation 类型的根级别字段必须进行串行解析而不是并行解析。这可以帮助防止两个 突变 字段同时与同一数据进行交互,从而可能导致竞态条件。
mutation DoTwoThings {one {success}# The `two` field is not resolved until after `one` is resolved.# It is not resolved at all if resolving `one` results in an error.two {success}}
使用命名空间后,您实际修改数据的 突变 字段 已不再是根级别字段(而是您的命名空间对象)。因此,突变字段将并行解析。在许多系统中,这不会引起问题(例如,您可能在突变 解析器 中使用其他机制以确保事务一致性,如语言串联器)。
mutation DoTwoNestedThings($createInput: CreateReviewInput!$deleteInput: DeleteReviewInput!) {reviews {create(input: $createInput) {success}# Is resolved in parallel with `create`delete(input: $deleteInput) {success}}}
如果要在特定操作中保证串行执行,可以使用客户端 别称 创建两个可以串行解析的根 字段:
mutation DoTwoNestedThingsInSerial($createInput: CreateReviewInput!$deleteInput: DeleteReviewInput!) {a: reviews {create(input: $createInput) {success}}# Is resolved serially after `a` is resolvedb: reviews {delete(input: $deleteInput) {success}}}
注意事项
如 GraphQL 技术指导委员会的成员指出,尽管上述方法在 GraphQL 服务器 上执行,但它不满足 GraphQL 规范中对以下要求的规定:
除了顶级突变字段外,字段的解析必须始终是无副作用的且幂等的。
因此,建议将任何 GraphQL 突变定义在根级别,以确保它们串行执行,并符合规范的要求。
目前,GraphQL 规范提供的唯一解决方案是将相关的 突变 分组 是使用字段命名约定和仔细排序这些字段 - 目前没有符合规范的方法来处理在根突变类型上有大量字段的情况。然而,有一些有趣的建议来解决这个问题的方案,我们鼓励社区成员审阅并反馈,特别是关于 GraphQL 命名空间提案 和 串行字段提案。