界面与GraphQL模式设计中的联合
假设我正在建立一个为自然灾害事件的时间表的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!
}
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!
}
两种方法之间的权衡是什么?
Suppose I am building a GraphQL API that serves a timeline of natural disaster events.
There are two different kinds of event right now:
- Hurricane
- Earthquake
All events have an ID and a date they occurred. I plan to have a paginated query for fetching events using cursors.
I can think of 2 different approaches to modelling my domain.
1. Interface
interface Event {
id: ID!
occurred: String! # ISO timestamp
}
type Earthquake implements Event {
epicenter: String!
magnitude: Int!
}
type Hurricane implements Event {
force: Int!
}
2. Union
type Earthquake {
epicenter: String!
magnitude: Int!
}
type Hurricane {
force: Int!
}
type EventPayload =
| Earthquake
| Hurricane
type Event {
id: ID!
occurred: String! # ISO timestamp
payload: EventPayload!
}
What are the trade-offs between the two approaches?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我相信:
它们具有不同的目的,可以一起使用:
此架构声明
a
,b
和c
有一些共同的字段,因此客户端更容易请求它们,并且查询foo
只能产生a
或c
。您可以编写
foo:i!
吗?尽管这将无缝起作用,但我相信这会带来不良的发展经历。如果您说的是foo
提供i
对象,则您的客户应为接收任何实施类型做好准备,包括b
和将花时间编写和维护永远不会被调用的代码。如果您知道foo
只能产生a
和c
,请明确告诉他们。如果
foo
是要产生a
,b
或c
,则相同。碰巧是实现i
的类型列表,因此在这种情况下,您可以编写foo:i!
吗?不!不要被那个愚弄。为什么?因为此列表是可以通过联邦 /模式缝制的扩展< / strong>!我相信这是某些GraphQl框架的很少使用的功能,但其采用量的增长。如果您从未使用过它,请尝试,它将为您的新想法带来微观服务间交流和其他中等流行语的新想法。简而言之,想象一下您在组织内部进行公共API,甚至在组织内部进行某种公共活动。其他人可以通过提供额外的东西来“增强”您的API。这可能包括实现界面的新类型。因此,我们回到了上一段。到目前为止,看来我赞成您的第一个代码。
但是,这可能是针对这种情况的,在我看来,您对事件的定义既将有关其发生的数据和物理指标的数据混合在一起。您的第二个代码将它们分为两个类型的层次结构。我喜欢那个。感觉更友好。您的模式更加开放。想象一下,您的API是关于事件历史记录的,并且有人通过预测来增强它:您的
eventpayload
可以重复使用!此外,请注意您的第一个示例不完整。实现接口必须 emparte 的类型,即重复,该接口的每个字段,就像我在上面的代码中所写一样。随着场的数量和实施类型的增加,这变得更加难以维持。
因此,第二个解决方案也具有一些优势。但是这样做,我早些时候对返回类型进行特定的blah-blah很难实现,因为有效载荷(要具体的)嵌入了另一种类型中,并且GraphQl中没有仿制药。
这是一个调和所有这些的建议:
看起来这两个建议看起来更复杂,但它可以满足我的需求。另外,很少看这个高度的架构:更多的是,我们经常知道入口点并从那里挖掘。例如,我在
historyEvents
上打开文档,请参阅它产生两种现象,很好,我不知道存在其他联合类型和事件类型。如果您要编写很多这些 union + event 对,则可以用代码生成它们,从而使一个函数调用会声明一对。较少的错误,实施更有趣,并且具有更多的中等文章潜力。
请注意,GraphQL结构独立于您的存储结构。可以让多个GraphQl对象从相同的 insert-your语言中提供数据,例如,您的DB驱动程序产生的对象。我可能没有一个小的开销,但我没有标准,但是提供更干净的API对我来说胜过这一点。基本想法是,解析器函数只需要从相同的源解决,因此与同一源对象相关的解析器函数将被调用。
I believe that:
They serve different purposes, and they can be used together:
This schema declares that
A
,B
, andC
have some fields in common, so that it's easier for the client to request them, and that queryingfoo
can only yieldA
orC
.Could you write
foo: I!
instead? While this would work seamlessly, I believe this leads to a bad development experience. If you're saying thatfoo
provides anI
object, your clients should be prepared for receiving any of the implementing types, includingB
, and would spend time to write and maintain a code that will never be called. If you know thatfoo
can only yieldA
andC
, please tell them explicitly.The same holds if
foo
were to yieldA
,B
, orC
. It happens that it's exactly the list of types that implementI
, so in this case, could you writefoo: I!
? No! Don't be fooled by that. Why? Because this list is expandable through federation / schema stitching! I believe it's a seldom used feature of some GraphQL frameworks, but whose adoption grows. If you've never used it, please try, it will open your mind to new ideas of inter-micro-service-communication and other Medium buzzwords. In short, imagine you're making a public API, or even somewhat-public within an organization. Someone else could "augment" your API by providing extra stuff. This may include new types implementing your interface. And so we're back to the previous paragraph.So far, it looks like I'm in favor of your first code.
However, and this may be specific to this scenario, it seems to me that your definition of event mixes both data about its occurrence and about physics metrics. Your second code splits them into two type hierarchy. I like that. It feels more architecture-friendly. Your schema is more open. Imagine your API is about event history, and someone enhance it with forecasts: your
EventPayload
can be reused!Besides, note that your first example is incomplete. Types implementing an interface must implement, i.e. repeat, every single field of this interface, like I wrote in the above code. This becomes harder to maintain as the number of fields and the number of implementing types grow.
So, the second solution also has some advantages. But doing so, the blah-blah I made earlier about being specific with returned types is hard to implement, because the payload, which is the one to be specific about, is embedded into another type, and there's no such thing as generics in GraphQL.
Here's a proposal to reconcile all of that:
It looks a bit more complex that both of your proposals, but it fulfills my needs. Also, it's rare to look at a schema from this height: more often, we know the entry point and dig down from there. For instance, I open the documentation at
historyEvents
, see that it yields phenomena of two kinds, fine, I'm not aware that other union types and event types exist.If you were to write a lot of these union + event pairs, you could generate them with code instead, whereby one function call would declare a pair. Less error-prone, funnier to implement, and with more potential of Medium articles.
Note that the GraphQL structure is independent of your storage structure. It's possible to have multiple GraphQL objects providing data from the same insert-your-language-here object, e.g. yielded by your DB driver. There may be a tiny overhead that I haven't benchmarked, but providing a cleaner API outweighs that to me. The basic idea is that resolver functions just have to resolve with the same source, so that the resolver functions related to another type will be called with the same source object.