apollographql / apollographql/federation
Allow hoisted+parallelized execution of a subgraph's contributed entity fields
- Dominant language
- TypeScript
- Stars
- 725
- Forks
- 276
- Avg merge
- 1h 47m
- Merged PRs (30d)
- 1
Description
# Problem statement
Subgraphs are blocked from resolving contributed entity fields until the parent resolver in the other subgraph yields.
(This makes sense because of the way resolvers receive parent values - but there's a opportunity for optimization.)
# Example
Consider this schema + query:
**Service A**
```graphql
type User @key(fields: "id") {
id: ID
name: String
}
type Query {
user(id: Int): User
}
```
**Service B**
```graphql
type User @key(fields: "id") {
id: ID
profilePhoto: String
}
```
Now consider this query:
```graphql
query {
user(id: 4) {
name
profilePhoto # comes from Service B
}
}
```
This query executes likes this 😱
```
# [user] -> [router] -> [Service A]
# ╰--------------> [Service B]
```
How can we avoid the waterfall to achieve this? 🤔
```
# [user] -> [router] -> [Service A]
# ╰> [Service B]
```
## Breakdown
Our query plan will look like this:
```
QueryPlan {
Sequence {
Fetch(service: "legacy_monolith") {
{
user(id: 4) {
__typename
id
name
}
}
},
Flatten(path: "user") {
Fetch(service: "fancy_new_photos_subgraph") {
{ ... on User { __typename id } } =>
{
... on User {
profilePhoto
}
}
},
},
},
}
```
Pretty standard stuff.
Upon adding/migrating out the `profilePhoto` resolver to the new subgraph, teams notice "hey i have an extra waterfall", and they can't start hitting the database to fetch the profile photo until `legacy_monolith` finishes resolving - but there's no real dependency there.
With larger types, more fields and more subgraphs, we become more sensitive and exposed to this issue.
Very related discussion here https://gist.github.com/magicmark/cbda3eedf1255334caee357fde7680de
## Proposal
In the above example, we have a very simplified example of `Query.user(id: ID): User`. In the initial request to the router, we already have the argument (`id`) that would be required to call `__resolveReference` in `fancy_new_photos_subgraph`.
This won't always be the case - e.g. `Query.searchUser(name: String): User` - this would require the `searchUser` to finish yielding before we can get an ID.
But many of our use cases, our `@key(fields: ...)` information is already available from the request object.
It would be cool to declare schema like this:
```graphql
type User @key(fields: "id", hoistableFrom: "Query.user") {
id: ID!
profilePhoto: String
}
```
If all fields in the child subgraph query's selection set are being referenced via `id` with a matching `hoistable` argument, then the subgraph could call its `__resolveReference` **in parallel** with the query to the monolith.
Contributor guide
Research direction
The issue names no implementation files or tests. Start by examining the query-plan Sequence/Flatten behavior and the proposed hoistableFrom schema change; done would mean safely running eligible contributed entity fields in parallel while preserving cases that require the parent result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100