假设我正在构建一个提供自然灾害事件时间线服务的 GraphQL API。
现在有两种不同类型的事件:
所有事件都有一个 ID 和发生日期。我计划使用游标进行分页查询来获取事件。
我可以想到两种不同的方法来建模我的领域。
1. 接口
interface Event {
id: ID!
occurred: String! # ISO timestamp
}
type Earthquake implements Event {
epicenter: String!
magnitude: Int!
}
type Hurricane implements Event {
force: Int!
}
Run Code Online (Sandbox Code Playgroud)
2. 联盟
type Earthquake {
epicenter: String!
magnitude: Int!
}
type Hurricane {
force: Int!
}
type EventPayload =
| Earthquake
| Hurricane
type Event {
id: ID!
occurred: String! # ISO timestamp
payload: EventPayload!
}
Run Code Online (Sandbox Code Playgroud)
两种方法之间的权衡是什么?
我相信:
它们有不同的用途,并且可以一起使用:
interface I {
id: ID!
}
type A implements I {
id: ID!
a: Int!
}
type B implements I {
id: ID!
b: Int!
}
type C implements I {
id: ID!
c: Int!
}
union Foo = A | C
type Query {
foo: Foo!
}
Run Code Online (Sandbox Code Playgroud)
该模式声明A、B、 和C有一些共同的字段,以便客户端更容易请求它们,并且查询foo只能产生A或C。
你能foo: I!代替写一下吗?虽然这可以无缝工作,但我相信这会导致糟糕的开发体验。如果您说foo提供了一个I对象,那么您的客户应该准备好接收任何实现类型,包括B,并且会花时间编写和维护永远不会被调用的代码。如果您知道foo只能产生A和C,请明确告诉他们。
foo如果要产生A、B、 或 ,同样如此C。碰巧它正是实现的类型列表I,所以在这种情况下,你可以写吗foo: I!?不!别被这个愚弄了。为什么?因为这个列表可以通过联合/模式拼接来扩展!我相信这是某些 GraphQL 框架很少使用的功能,但其采用率正在增长。如果您从未使用过它,请尝试一下,它将让您了解微服务间通信和其他媒体流行语的新想法。简而言之,想象一下您正在制作一个公共 API,甚至在组织内有点公开。其他人可以通过提供额外的东西来“增强”你的 API。这可能包括实现您的接口的新类型。所以我们回到上一段。
到目前为止,看起来我赞成你的第一个代码。
然而,这可能是特定于这种情况的,在我看来,您对事件的定义混合了有关其发生的数据和有关物理指标的数据。您的第二个代码将它们分为两种类型层次结构。我喜欢。感觉对架构更友好。您的架构更加开放。想象一下,您的 API 是关于事件历史记录的,有人通过预测对其进行了增强:您的 APIEventPayload可以重复使用!
此外,请注意您的第一个示例不完整。实现接口的类型必须实现(即重复)该接口的每个字段,就像我在上面的代码中编写的那样。随着字段数量和实现类型数量的增加,这变得更难维护。
所以,第二种解决方案也有一些优点。但这样做的话,我之前提出的有关返回类型具体化的废话很难实现,因为要具体化的有效负载被嵌入到另一种类型中,并且 GraphQL 中不存在泛型之类的东西。
这是协调所有这些的提议:
interface HasForce {
force: Int!
}
type Earthquake {
epicenter: String!
magnitude: Int!
}
type Hurricane implements HasForce {
force: Int!
}
type Tsunami implements HasForce {
force: Int!
}
interface Event {
data: EventData!
}
type EventData {
id: ID!
occurred: String!
}
union HistoryMeteorologicalPhenomenon = Earthquake | Hurricane
type HistoryEvent implements Event {
data: EventData!
meteorologicalPhenomenon: HistoryMeteorologicalPhenomenon!
}
type Query {
historyEvents: [HistoryEvent!]!
}
Run Code Online (Sandbox Code Playgroud)
它看起来比你的两个建议更复杂一些,但它满足了我的需求。此外,很少从这个高度来看待模式:更常见的是,我们知道入口点并从那里向下挖掘。例如,我打开 的文档historyEvents,看到它产生两种现象,很好,我不知道存在其他联合类型和事件类型。
如果您要编写大量此类联合+事件对,则可以使用代码生成它们,其中一个函数调用将声明一对。不太容易出错,实施起来更有趣,并且中等文章的潜力更大。
请注意,GraphQL 结构独立于您的存储结构。可以有多个 GraphQL 对象提供来自同一个“在此处插入您的语言”对象的数据,例如由数据库驱动程序生成的数据。可能有一点我没有进行基准测试的开销,但提供一个更干净的 API 对我来说比这更重要。基本思想是解析器函数只需使用相同的源进行解析,以便使用相同的源对象调用与另一个类型相关的解析器函数。
| 归档时间: |
|
| 查看次数: |
1175 次 |
| 最近记录: |