一对多关系的GraphQL中的多态性

时间:2019-01-11 13:18:40

标签: graphql

我的架构中包含以下实体。

  • 用户
  • 链接发布
  • 普通帖子

如何为UserPost类型之间的一对多关系建模,其中Post可以是LinkPostNormalPost的两种类型。

2 个答案:

答案 0 :(得分:1)

我将利用GraphQL中存在的一种多态类型对这些模式进行建模。这种方法将为您将来的查询和扩展提供最大的灵活性。

模式设计

User这样拥有Posts的数组非常方便:

type User {
  id: Int!
  email: String!
  posts: [Posts!]
  username: String!
}

这意味着Post必须是interfaceunion类型。这两种类型中的任何一种都使我们能够利用NormalPostLinkedPost仍然都是Post的事实。就像上面user.posts一样,它也使我们可以在同一位置查询它们。

界面

在OOP中,interface类型的行为与interface类型非常相似。它定义了任何实现类型都必须具有的基本字段集。例如,所有Post对象可能看起来像这样:

interface Post {
  id: Int!
  author: String!
  dateCreated: String!
  dateUpdated: String!
  title: String!
}

任何类型的implements Post接口都必须同时具有字段idauthordateCreateddateUpdated和{{ 1}}以及除该类型特定的任何字段之外的其他实现。因此,使用titleinterfaceNormalPost可能如下所示:

LinkedPost
type NormalPost implements Post {
  id: Int!
  author: String!
  body: String!
  dateCreated: String!
  dateUpdated: String!
  title: String!
}

联盟

union类型允许将不需要实现相似字段的不同类型一起返回。 type LinkedPost implements Post { id: Int! author: String! dateCreated: String! dateUpdated: String! link: String! title: String! } 类型的结构与使用接口的结构不同,因为union类型不指定任何字段。在union模式定义中定义的唯一内容是由union分隔的联合类型。

该规则的唯一警告是|中定义了相同字段名称的任何类型都必须具有相同的可空性(即,由于union是不可空的({{ 1}}),那么NormalPost.title也必须是不可为空的。

title: String!
LinkedPost.title
union Post = NormalPost | LinkedPost

查询

上面的内容介绍了在从type NormalPost implements Post { id: Int! author: String! body: String! dateCreated: String! dateUpdated: String! title: String! } 中查询type LinkedPost implements Post { id: Int! author: String! dateCreated: String! dateUpdated: String! link: String! title: String! } LinkedPost时如何区分它们的问题。在这两种情况下,您都需要使用条件Fragment

条件片段允许从NormalPostuser.posts类型中查询一组特定的字段。它们看起来与常规查询片段相同,因为它们是在查询正文中使用interface语法定义的。对于union... on <Type>类型,条件片段的结构方式略有不同。

除了使用条件片段进行查询外,向多态查询中添加interface Meta Field往往很有用,这样查询的使用者可以更好地识别代码中结果对象的类型

界面

由于union定义了一组特定的字段,所有实现类型都有相同的字段,因此可以像查询任何其他普通类型一样查询这些字段。区别在于__typename类型具有特定于其类型的不同字段,例如interfaceinterface。选择整个NormalPost.body接口,然后选择LinkedPost.linkPost的查询如下所示:

NormalPost.body

联盟

由于LinkedPost.link在其类型之间未定义任何公共字段,因此所有要选择的字段必须存在于每个条件片段中。这是查询query getUsersNormalAndLinkedPosts { user(id: 123) { id name username posts { __typename id author dateCreated dateUpdated title ... on NormalPost { body } ... on LinkedPost { link } } } } union之间的唯一区别。查询interface类型如下:

union

结论

这两种多态类型都有优点和缺点,由您决定哪种类型最适合您的用例。在构建GraphQL模式时,我都利用了两者,在使用unionquery getUsersNormalAndLinkedPosts { user(id: 123) { id name username posts { __typename ... on NormalPost { id author body dateCreated dateUpdated title } ... on LinkedPost { id author dateCreated dateUpdated link title } } } } 时,我的具体区别是实现类型之间是否存在公共字段。

当实现类型之间的唯一区别只是一小部分字段而其余部分在它们之间共享时,接口具有很大的意义。这样可以减少查询量,并可能减少所需的条件片段。

当您的类型是互不相关但又重新组合在一起的多种不同类型的掩码(例如在一组搜索结果中)时,联合会真正发光。根据搜索类型的不同,返回的结果可能会返回许多看起来完全不同的不同类型。例如,在CMS上进行的搜索可能同时产生用户和帖子。在这种情况下,最好使用以下类型:

interface

然后可以从带有签名的查询中返回

union

在这个特定问题的背景下,我将使用union SearchResult = User | Post方法,因为从关系和查询的角度来看,这是最有意义的。

答案 1 :(得分:-1)

您是否考虑过拥有单个Post实体并使用枚举类型定义帖子类型?

用户

type User {
  id: ID!
  posts: [Post!]!
}

发布

type Post {
  id: ID!
  title: String!
  createdBy: User!
  type: PostType!
}

Post

的枚举类型

PostType

enum PostType {
  NORMAL
  LINK
}

-----更新! -----

如果您要分离实体,则可以这样做:

用户

type User {
  id: ID!
  linkPosts: [LinkPost!]!
  normalPosts: [NormalPost]
}

LinkPost 类型

type LinkPost {
  description: String!
  url: String!
  createdBy: User!
}

NormalPost 类型

type NormalPost {
  title: String!
  description: String!
  createdBy: User!
}

让我知道这是否适合您

致谢