Skip to content

Commit 1d14e32

Browse files
tnineslingshorgi
andauthored
Document new demand control directives (#3111)
<!-- ROUTER-457 --> This pull request documents the directives added in #3074. These will be released in the next federation version (v2.9), which is targeted for the end of the month. Both directives are inspired by the [IBM cost specification](https://ibm.github.io/graphql-specs/cost-spec.html#sec-The-Cost-Directive). So, most of the documentation parrots their specification. --------- Co-authored-by: Edward Huang <edward.huang@apollographql.com>
1 parent dc797f9 commit 1d14e32

3 files changed

Lines changed: 298 additions & 0 deletions

File tree

composition-js/src/__tests__/compose.demandControl.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,20 +218,33 @@ const subgraphWithUnimportedCost = {
218218
somethingWithCost: Int @federation__cost(weight: 20)
219219
}
220220
221+
scalar ExpensiveInt @federation__cost(weight: 30)
222+
223+
type ExpensiveObject @federation__cost(weight: 40) {
224+
id: ID
225+
}
226+
221227
type Query {
222228
fieldWithCost: Int @federation__cost(weight: 5)
223229
argWithCost(arg: Int @federation__cost(weight: 10)): Int
224230
enumWithCost: AorB
225231
inputWithCost(someInput: InputTypeWithCost): Int
232+
scalarWithCost: ExpensiveInt
233+
objectWithCost: ExpensiveObject
226234
}
227235
`),
228236
};
229237

230238
const subgraphWithUnimportedListSize = {
231239
name: 'subgraphWithListSize',
232240
typeDefs: asFed2SubgraphDocument(gql`
241+
type HasInts {
242+
ints: [Int!]
243+
}
244+
233245
type Query {
234246
fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false)
247+
fieldWithDynamicListSize(first: Int!): HasInts @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true)
235248
}
236249
`),
237250
};

docs/source/federated-schemas/federated-directives.mdx

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,3 +982,214 @@ The selection syntax for `@fromContext` used in its `ContextFieldValue` is simil
982982
When the same contextual value is set in multiple places, the `ContextFieldValue` must resolve all types from each place into a single value that matches the parameter type.
983983

984984
For examples using `@context` and `@fromContext`, see [Using contexts to share data along type hierarchies](../entities/use-contexts).
985+
986+
## Customizing demand controls
987+
988+
<MinVersion version="2.9">
989+
990+
### `@cost`
991+
992+
</MinVersion>
993+
994+
<EnterpriseFeature />
995+
996+
```graphql
997+
directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR
998+
```
999+
1000+
The `@cost` directive defines a custom weight for a schema location. For GraphOS Router, it customizes the operation cost calculation of the [demand control feature](/router/executing-operations/demand-control/).
1001+
1002+
If `@cost` is not specified for a field, a default value is used:
1003+
- Scalars and enums have default cost of 0
1004+
- Composite input and output types have default cost of 1
1005+
1006+
Regardless of whether `@cost` is specified on a field, the field cost for that field also accounts for its arguments and selections.
1007+
1008+
#### Arguments
1009+
1010+
<table class="field-table">
1011+
<thead>
1012+
<tr>
1013+
<th>Name /<br/>Type</th>
1014+
<th>Description</th>
1015+
</tr>
1016+
</thead>
1017+
1018+
<tbody>
1019+
1020+
<tr>
1021+
<td>
1022+
1023+
##### `weight`
1024+
1025+
`Int!`
1026+
</td>
1027+
<td>
1028+
1029+
**Required.** Assigns a custom weight for scoring the current field.
1030+
1031+
</td>
1032+
</tr>
1033+
1034+
</tbody>
1035+
</table>
1036+
1037+
<MinVersion version="2.9">
1038+
1039+
### `@listSize`
1040+
1041+
</MinVersion>
1042+
1043+
<EnterpriseFeature />
1044+
1045+
```graphql
1046+
directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION
1047+
```
1048+
1049+
The `@listSize` directive is used to customize the cost calculation of the [demand control feature](/router/executing-operations/demand-control/) of GraphOS Router.
1050+
1051+
In the static analysis phase, the cost calculator does not know how many entities will be returned by each list field in a given query. By providing an estimated list size for a field with `@listSize`, the cost calculator can produce a more accurate estimate the cost during static analysis.
1052+
1053+
#### Configuring static list sizes
1054+
1055+
The simplest way to define a list size for a field is to use the `assumedSize` argument. This defines a static assumed maximum length for a given list field in the schema.
1056+
1057+
```graphql
1058+
type Query {
1059+
items: [Item!] @listSize(assumedSize: 10)
1060+
}
1061+
1062+
type Item @key(fields: "id") {
1063+
id: ID
1064+
}
1065+
```
1066+
1067+
In this case, all queries for `items` are expected to receive at most ten items in the list.
1068+
1069+
#### Configuring dynamic list sizes
1070+
1071+
When using paging parameters, the length of a list field can be determined by an input value. You can use the `slicingArguments` argument to tell the router to expect as many elements as the query requests.
1072+
1073+
```graphql
1074+
type Query {
1075+
items(first: Int, last: Int): [Item!] @listSize(slicingArguments: ["first", "last"], requireOneSlicingArgument: false)
1076+
}
1077+
```
1078+
1079+
In this example, the `items` field can be requested with paging parameters. If the client sends a query with multiple slicing arguments, the scoring algorithm will use the maximum value of all specified slicing arguments. The following query is assumed to return ten items in the scoring algorithm.
1080+
1081+
```graphql
1082+
query MultipleSlicingArgumentsQuery {
1083+
items(first: 5, last: 10)
1084+
}
1085+
```
1086+
1087+
In some cases, you may want to enforce that only one slicing argument is used. For example, you may want to ensure that clients request either the first _n_ items or the last _n_ items, but not both. You can do this by setting `requireOneSlicingArgument` to `true`.
1088+
1089+
```graphql
1090+
type Query {
1091+
items(first: Int, last: Int): [Item!] @listSize(slicingArguments: ["first", "last"], requireOneSlicingArgument: true)
1092+
}
1093+
```
1094+
1095+
With this updated schema, sending the the above `MultipleSlicingArgumentsQuery` with its two slicing arguments to a graph would result in an error, as would sending a query with no slicing arguments.
1096+
1097+
#### Cursor support
1098+
1099+
Some pagination patterns include extra information along with the requested entities. For example, we may have some schema with a cursor type.
1100+
1101+
```graphql
1102+
type Query {
1103+
items(first: Int): Cursor! @listSize(slicingArguments: ["first"], sizedFields: ["page"])
1104+
}
1105+
1106+
type Cursor {
1107+
page: [Item!]
1108+
nextPageToken: String
1109+
}
1110+
1111+
type Item @key(fields: "id") {
1112+
id: ID
1113+
}
1114+
```
1115+
1116+
This application of `@listSize` indicates that the length of the `page` field inside `Cursor` is determined by the `first` argument.
1117+
1118+
1119+
#### Arguments
1120+
1121+
<table class="field-table">
1122+
<thead>
1123+
<tr>
1124+
<th>Name /<br/>Type</th>
1125+
<th>Description</th>
1126+
</tr>
1127+
</thead>
1128+
1129+
<tbody>
1130+
1131+
<tr>
1132+
<td>
1133+
1134+
##### `assumedSize`
1135+
1136+
`Int`
1137+
</td>
1138+
<td>
1139+
1140+
Indicates that the annotated list field will return at most this many items.
1141+
1142+
</td>
1143+
</tr>
1144+
1145+
<tr>
1146+
<td>
1147+
1148+
##### `slicingArguments`
1149+
1150+
`[String!]`
1151+
1152+
</td>
1153+
<td>
1154+
1155+
Indicates that the annotated list field returns as many items as are requested by a paging argument. If multiple arguments are passed, the maximum value of the arguments is used.
1156+
1157+
If both this and `assumedSize` are specified, the value from `slicingArguments` will take precedence.
1158+
1159+
</td>
1160+
</tr>
1161+
1162+
<tr>
1163+
<td>
1164+
1165+
##### `sizedFields`
1166+
1167+
`[String!]`
1168+
</td>
1169+
<td>
1170+
1171+
Supports cursor objects by indicating that the expected list size should be applied to fields within the returned object.
1172+
1173+
</td>
1174+
</tr>
1175+
1176+
<tr>
1177+
<td>
1178+
1179+
##### `requireOneSlicingArgument`
1180+
1181+
`Boolean`
1182+
</td>
1183+
<td>
1184+
1185+
If `true`, indicates that queries must supply exactly one argument from `slicingArguments`.
1186+
1187+
If `slicingArguments` are not specified, this value is ignored.
1188+
1189+
The default value is `true`.
1190+
1191+
</td>
1192+
</tr>
1193+
1194+
</tbody>
1195+
</table>

docs/source/federation-versions.mdx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,80 @@ For a comprehensive changelog for Apollo Federation and its associated libraries
2626

2727
- If you maintain a [subgraph-compatible library](./building-supergraphs/compatible-subgraphs/), consult this article to stay current with recently added directives. All of these directive definitions are also listed in the [subgraph specification](./subgraph-spec/#subgraph-schema-additions).
2828

29+
## v2.9
30+
31+
<hr/>
32+
33+
<CodeColumns cols="3">
34+
35+
<div>
36+
37+
First release
38+
39+
**August 2024**
40+
41+
</div>
42+
43+
<div>
44+
45+
Minimum router version
46+
47+
**TBD**
48+
49+
</div>
50+
51+
</CodeColumns>
52+
53+
<hr/>
54+
55+
#### Directive changes
56+
57+
<table>
58+
<thead>
59+
<tr>
60+
<th style={{ minWidth: 200 }}>Topic</th>
61+
<th>Description</th>
62+
</tr>
63+
</thead>
64+
65+
<tbody>
66+
<tr>
67+
<td>
68+
69+
#### `@cost`
70+
71+
</td>
72+
<td>
73+
74+
Introduced. [Learn more](./federated-types/federated-directives/#cost).
75+
76+
```graphql
77+
directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR
78+
```
79+
80+
</td>
81+
</tr>
82+
83+
<tr>
84+
<td>
85+
86+
#### `@listSize`
87+
88+
</td>
89+
<td>
90+
91+
Introduced. [Learn more](./federated-types/federated-directives/#listsize).
92+
93+
```graphql
94+
directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION
95+
```
96+
97+
</td>
98+
</tr>
99+
100+
</tbody>
101+
</table>
102+
29103
## v2.8
30104

31105
<hr/>

0 commit comments

Comments
 (0)