apollographql / apollographql/federation
Invalid query plan with nested @requires crossing service boundaries
- Dominant language
- TypeScript
- Stars
- 725
- Forks
- 276
- Avg merge
- 1h 47m
- Merged PRs (30d)
- 1
Description
Consider the schema from the [federation demo](https://github.com/apollographql/federation-demo). Suppose we want to hide (or otherwise modify) the reviews of any product that's out of stock.
What we want to do, ideally, is change the `reviews` field to `@requires` the `inStock` field:
```graphql
extend type Product @key(fields: "upc") {
...
reviews: [Review] @requires(fields: "inStock")
}
```
We can't do this: you can't `@requires` a field that's not defined in the service that owns the type, and `inStock` is an extension from `inventory`, whereas `products` owns the `Product` type. (Ideally, we could! But that's another topic for another thread.)
We might try to work around this limitation by wrapping up the stock data in a separate type, owned by `inventory`. So instead of the `inStock` field, we'd have in `inventory`:
```graphql
type StockData @key(fields: "productUpc") {
productUpc: String!
inStock: Boolean
}
```
The `products` service has to provide some glue:
```graphql
type Product @key(fields: "upc") {
...
stockData: StockData
}
```
Now, we can add our `@requires`:
```graphql
extend type Product @key(fields: "upc") {
...
reviews: [Review] @requires(fields: "stockData { inStock }")
}
```
(I've omitted some `@external` declarations we'd have to add, for brevity.)
This schema validates successfully. But the query we want to write fails at runtime:
```graphql
{
topProducts {
reviews {
body
}
}
}
```
If we look at the query plan, we can see why:
Query Plan
```graphql
QueryPlan {
Sequence {
Fetch(service: "products") {
{
topProducts {
__typename
upc
stockData {
inStock
}
}
}
},
Flatten(path: "topProducts.@") {
Fetch(service: "reviews") {
{
... on Product {
__typename
upc
stockData {
inStock
}
}
} =>
{
... on Product {
reviews {
body
}
}
}
},
},
},
}
```
Apollo ends up asking the `products` service for `inStock`:
```graphql
{
topProducts {
__typename
upc
stockData {
inStock
}
}
}
```
Which, of course, it doesn't have.
If this sort of `@requires` is not allowed, apollo should validate for it and document it! (I'm guessing the rule would be to extend "you can only require fields that live on the original type defijnition" to add "and nested fields within the same service".) Or -- much better for us -- it should work.
I've posted a [full running example](https://github.com/benjaminjkraft/federation-demo/commit/48239b54cfbb5466fe0aee7889fb72bf8d066c7b): Send the above query and you'll see the error.
See also my [thread on Spectrum](https://spectrum.chat/apollo/apollo-federation/using-requires-for-a-field-not-defined-in-the-owner-service~290199d3-2877-4a61-af6a-c0ca022cac9c) with more about the limitation I'm trying to sneak around.
Contributor guide
Research direction
Start with the federation demo and the full running example linked in the issue, then reproduce the query-plan failure for nested @requires across the products, inventory, and reviews services. Compare the generated plan with the service ownership rules; done means either the query executes correctly or validation rejects the unsupported schema with documentation of the limitation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, backend-api-design, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 18/100