合约使用模式
为不同的图消费者提供特定于用例的模式
合约使得团队能够向单个统一的图贡献力量,同时向不同类型的消费者提供特定于用例的架构。图管理员定义过滤规则以生成合约架构,这是源超图的严格子集。
对于合约,您可以使用以下三种不同的过滤策略:
- 选择性排除(修剪)
- 选择性包含(默认拒绝)
- 合并排除和包含
以下部分介绍何时使用每种策略及其原因。
选择性排除(修剪)
为其合约定义一个“排除”过滤器需要最少的工作,但它也会带来最大的风险,即如果你未恰当地标记它们,可能会暴露新的模式字段。我们仅在偶然暴露敏感字段是可以接受的情况下(例如在内测API中),或如果你的组织有严格的治理和审查政策时,才推荐这种策略。
以一个使用标签subgraph的例子来说明。internal
用来表示内部专用字段。
type User {id: ID!email: String!username: String!description: StringsupportNotes: [String!]! @tag(name: "internal")supportLevel: Int!}
很可能,supportLevel
与supportNotes
具有相同的敏感性,但开发者忘记了添加标签,因此现在它在合约中暴露。
因此,Apollo在合约中公开字段是可以接受的情况下(例如,在内测或实验性API中,将Beta字段包含在生产版本中不会引起担忧的情况下)认为这种方法是最好的。
选择性包含(默认拒绝)
尽管“包含”过滤器可能比“排除”过滤器更麻烦,但我们推荐在大多数情况下使用它创建合约,以下是一些原因:
- 当与SDL进行推理时,它更直观。在哪些字段被公开,以及清晰的命名约定的基础上提供的清晰的标记,在工作时提高了与合约的体验。
- 与“排除”策略相比,它要安全得多,因为必须显式地将新的、可能敏感的架构字段添加到合约中。
为了说明为什么是这样,让我们以前面的同一类型为例,但这次使用一个“包含”模型。这次,公司选择使用一个外部
标签来表示哪些字段暴露给外部消费者:
type User {id: ID! @tag(name: "external")email: String! @tag(name: "external")username: String! @tag(name: "external")description: String @tag(name: "external")supportNotes: [String!]!supportLevel: Int!}
我们不再为两个support
字段打上标签来排除它们,而是现在明确指出哪些字段是公开的。这也更清楚地传达了标签的意图——开发者会知道id
会公开给外部机构。
如果团队想要公开一个新的字段并忘记标记它,那么它将不会包含在最终的结果合同 变体 中。
因此,在构建需要仅公开特定字段给其他受众(如外部或合作伙伴API)的项目时,我们建议采用这种方法。
合并排除和包含
这三个选项中的最后一个具有特定的使用场景,但这些场景在很大程度上取决于你正在工作的模式。这个筛选方法允许你设置一个包含标签的筛选器,然后通过删除其他标签来细化结果。
如果你需要广泛包含类型(如上述User
类型),但需要通过删除其他字段进行进一步细化,我们建议考虑这个方法。因此,这通常与排除方法相同,但它具有一些优点,允许你显式地定义应被明确包括的类型/字段,避免了排除方法中的一些缺点。
因此,请确保将其视为前面两种方法的组合。
- 因为你仍然依赖于排除过滤器来细化结果,因此将这种方法与前面讨论的排除方法非常相似。有可能在仍然使用排除过滤器进一步细化时意外包含字段。
- 使用包含过滤器确保仅公开你想要公开的类型/字段。
为了完成这些示例,让我们来看看我们之前重构后使用这种筛选方法的一个例子
type User @tag(name:"external") {id: ID!email: String!username: String!description: Stringsupport: SupportInformation! @tag(name: "internal")}type SupportInformation: @tag(name:"internal") {level: Int!supportNotes: [String!]!}
现在我们创建了一个新的类型来存放支持信息,我们用内部
类别标]标记这个类型。我们会使用这个标签来排除这个类型从最终的外部API,但普通的User
类别包含没有support
字段。如果support
字段没有也标记为内部
,它会产生一个组合错误,提供服务。
通过允许类型标记,可以创建“护栏”来防止缺少标记。如上所述,如果一个类型被标记,但发出该类型的字段没有,它会失败那个合同的组合。如果拥有服务可以确保所有类型都正确标记并且使用正确的标记来筛选,那么它仍然是一种很好的方法来完成排除方法的同时获得包含方法的一些好处。
另一方面,这也意味着必须实施强有力的治理计划。否则,您可能会遇到与讨论的排除方法相同的问题。
您还可以将此策略用于文档目的,因为在实际API中,包含和排除的关心程度远不如前者。因为合同只是一个图变体,因此可以链接用户到合同的文档,使其在此背景下也很有用。
跨子图标记
当考虑上述策略时,需要注意的是,还有一个额外的组合行为:在一个子图上应用的标签可能会影响在另一个子图中定义的字段。
例如,考虑以下这两个子图:
# Subgraph Atype Document @key(fields: "id") {id: ID!name: Stringcontent: String}# Subgraph Btype Document @key(fields: "id") @tag(name: "internal") {id: ID!reviewedBy: AdminUserapprovedBy: AdminUsercontentScore: Float}
如上所示,Document
类型在子图B中被标记为internal
,但不在子图A中。
type Document @key(fields: "id") @tag(name: "internal") {id: ID!name: Stringcontent: StringreviewedBy: AdminUserapprovedBy: AdminUsercontentScore: Float}
由于在组合之后应用了标签继承,因此在跨子图实体定义合并后,任何添加在类型级别的标签都会被继承。这意味着一个子图中对标签应用的“正确”可能对其他子图以及生成的化简图产生不利影响。