我的架构中有以下实体。
- 用户
- 链接帖子
- 普通帖子
如何对User
和Post
类型之间的一对多关系进行建模,其中Post
可以是两种类型LinkPost
或NormalPost
。
我会使用 GraphQL 中存在的多态类型之一对这些模式进行建模。这种方法将使你将来在查询和扩展方面具有最大的灵活性。
架构设计
对于User
来说,拥有这样的Posts
数组非常方便:
type User {
id: Int!
email: String!
posts: [Posts!]
username: String!
}
这意味着Post
必须是interface
或union
类型。这两种类型中的任何一种都允许我们利用NormalPost
和LinkedPost
仍然是一种Post
的事实。它还将允许我们在同一位置查询它们,就像上面的user.posts
一样。
接口
interface
类型在行为上与 OOP 中的interface
非常相似。它定义了比任何实现类型还必须具有的基本字段集。例如,所有Post
对象可能如下所示:
interface Post {
id: Int!
author: String!
dateCreated: String!
dateUpdated: String!
title: String!
}
任何implements
Post
接口的类型,除了特定于该类型的任何字段外,还必须实现字段id
、author
、dateCreated
、dateUpdated
和title
。因此,使用interface
,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!
}
工会
union
类型允许一起返回不需要实现相似字段的不同类型。union
类型的结构与使用接口的结构不同,因为union
类型不指定任何字段。union
架构定义中定义的唯一内容是由|
分隔的联合类型。
该规则的唯一警告是union
中定义了相同字段名称的任何类型都需要具有相同的可空性(即。因为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
和NormalPost
时如何区分user.posts
的问题。在这两种情况下,您都需要使用条件片段。
条件片段允许从interface
或union
类型查询一组特定的字段。它们看起来与常规查询片段相同,因为它们是使用查询正文中的... on <Type>
语法定义的。对于interface
与union
类型的条件片段的结构方式略有不同。
除了使用条件片段进行查询之外,将__typename
元字段添加到多态查询中往往很有用,这样查询的使用者就可以更好地识别代码中结果对象的类型。
接口
由于interface
定义了一组特定的字段,所有实现类型都具有共同的字段,因此可以像对类型进行任何其他普通查询一样查询这些字段。不同之处在于,当interface
类型具有特定于其类型的不同字段时,例如NormalPost.body
与LinkedPost.link
。选择整个Post
接口,然后选择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
之间的唯一区别。查询union
类型如下所示:
query 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
}
}
}
}
结论
这两种多态类型都有优点和缺点,由您决定哪种最适合您的用例。我在构建 GraphQL 模式时已经使用了两者,当我使用interface
或union
时的具体区别在于实现类型之间是否有公共字段。
当实现类型之间的唯一区别只是一小部分字段而其余字段在它们之间共享时,接口非常有意义。这会导致查询更小,并且可能需要更少的条件片段。
当您有一种类型是多个不同类型的掩码时,工会真的会发光,这些类型不相关但又重新组合在一起,就像在一组搜索结果中一样。根据它返回的搜索类型,可能会返回许多看起来完全不一样的不同类型。例如,在CMS上进行搜索,可以同时产生用户和帖子。在这种情况下,具有以下类型是有意义的:
union SearchResult = User | Post
.
然后可以从带有签名的查询中返回此信息
search(phrase: String!): [SearchResult!]
在这个特定问题的上下文中,我会采用interface
方法,因为它从关系和查询的角度来看最有意义。
您是否考虑过使用枚举类型使用单个Post
实体并定义帖子类型?
用户
type User {
id: ID!
posts: [Post!]!
}
发布
type Post {
id: ID!
title: String!
createdBy: User!
type: PostType!
}
以及Post
的枚举类型
后类型
enum PostType {
NORMAL
LINK
}
-----更新!-----
如果要拥有单独的实体,可以这样做:
用户
type User {
id: ID!
linkPosts: [LinkPost!]!
normalPosts: [NormalPost]
}
链接帖子类型
type LinkPost {
description: String!
url: String!
createdBy: User!
}
正常开机自检类型
type NormalPost {
title: String!
description: String!
createdBy: User!
}
让我知道这是否适合您,
问候