apollographql / apollographql/federation

Supergraph Schema is not currently lossless

Open
#2,250 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
727
Forks
276
Avg merge
1h 47m
Merged PRs (30d)
1

Description

# Description

Currently, when schema is composed into a supergraph schema, some of the schema can be omitted when the schema is merged via intersection, such as with Input Value Definitions or input enum values. This makes the supergraph schema lossy rather than lossless, and does not make it a sufficient source of truth for the current state of the supergraph.

# Example

Say I have two supergraphs with the following schemas, which I will compose into a supergraph schema (currently using rover and federation v2.1):

```gql
# Subgraph A
type Query {
booksByGenre(genre: BookGenreEnum!, address: Address): [String!]! @shareable
}

input Address {
street: String!
city: String
state: String
postalCode: Int!
}

enum BookGenreEnum {
FICTION
NON_FICTION
}

type Book @shareable {
title: String!
genre: String!
}
```

```gql
# Subgraph B
type Query {
booksByGenre(genre: BookGenreEnum!): [String!]! @shareable
}

input Address {
street: String!
postalCode: Int!
}

enum BookGenreEnum {
FICTION
}

type Book @shareable {
title: String!
}
```

# Encountered behavior

The composed supergraph schema looks like this:

```gql
# Supergraph Schema
type Query
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
booksByGenre(genre: BookGenreEnum!): [String!]! @join__field(graph: SUBGRAPH_A) @join__field(graph: SUBGRAPH_B)
}

input Address
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
street: String!
postalCode: Int!
}

enum BookGenreEnum
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
FICTION
}

type Book
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
title: String!
genre: String! @join__field(graph: SUBGRAPH_A)
}
```

Note that `Query.booksByGenre(address)`, `Address.city`, `Address.state`, and `BookGenreEnum.NON_FICTION` are all omitted from the supergraph schema.

# Expected behavior

I would expect `Query.booksByGenre.address`, `Address.city1, `Address.state`, and `BookGenreEnum.NON_FICTION` to still be present in the graph, yet somehow marked that they are inaccessible or have an inconsistent representation across the graph.

Fields perform this merging in a more lossless manner. If you look at `Book.genre`, it is in Subgraph A but not Subgraph B. This is denoted in the supergraph schema via the `@join__field` directive. I would imagine something similar (if not the same directive) could be used for these other child types:

```gql
# Suggested Supergraph Schema
type Query
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
booksByGenre(
genre: BookGenreEnum!
address: Address @join__field(graph: SUBGRAPH_A)
): [String!]! @join__field(graph: SUBGRAPH_A) @join__field(graph: SUBGRAPH_B)
}

input Address
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
street: String!
postalCode: Int!
city: String @join__field(graph: SUBGRAPH_A)
state: String @join__field(graph: SUBGRAPH_A)
}

enum BookGenreEnum
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
FICTION
NON_FICTION @join__field(graph: SUBGRAPH_A)
}

type Book
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
title: String!
genre: String! @join__field(graph: SUBGRAPH_A)
}
```

As an alternative, these child types could be included with the `@inaccessible` directive along with the `@join__type` directive (because I still think we would want to know _where_ the child type appears within the subgraphs).

```gql
enum BookGenreEnum
@join__type(graph: SUBGRAPH_A)
@join__type(graph: SUBGRAPH_B)
{
FICTION
NON_FICTION @join__type(graph: SUBGRAPH_A) @inaccessible
}
```

# Additional Notes

This is important in order to consider the composed schema as the source of truth and layer additional schema rules on top of the loose rules in Federation 2. Running Federation v2 as an organization-wide platform, we want the flexibility to consider the ownership of the schema and when new children are being added to object types (like enum values and input values).

Without a lossless supergraph schema, we would have to consider each subgraph's schema individually and compile our own concept of a "supergraph" in order to consider the lossless state of the graph.

It would be far better if the supergraph was lossless and could act as the source of truth for the current state of the graph.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing composition with the Subgraph A and Subgraph B schemas in the issue, using the noted Rover and Federation v2.1 setup. Compare the resulting supergraph schema with the expected lossless form, focusing on input fields, input values, and enum values. Done means those child definitions remain represented with their subgraph ownership or an agreed inaccessible representation.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, typescript
Domain
api, backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.