Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Simplified Key Definition in GraphQL Composite Schemas #168

Open
smyrick opened this issue Feb 7, 2025 · 4 comments
Open

Proposal: Simplified Key Definition in GraphQL Composite Schemas #168

smyrick opened this issue Feb 7, 2025 · 4 comments

Comments

@smyrick
Copy link
Contributor

smyrick commented Feb 7, 2025

Copying from graphql/composite-schemas-wg#440

Introduction

This proposal suggests starting with a simpler definition of keys in GraphQL composite schemas by restricting keys to a single value. This approach aims to simplify the composite schema specification, the gateway-subgraph contract, and gateway implementations.

Proposal

Simplified Key Definition
Instead of complex key definitions, we propose using a single scalar value for keys:

type Foo {
    id: ID! @key
    v: String
}

or

type Foo implements Node {
    id: ID!
    v: String
}

Handling Complex Keys

For cases where the underlying model requires a more complex key, subgraph tooling can provide a helper method to create the composite-schemas key. An object-level key directive could be used as a convenience, interpreted only by the subgraph server:

type Foo @key(fields: "fooId") {
    fooId: String!
    v: String
}

The subgraph's GraphQL framework can automatically add the composite key:

type Foo {
    id: ID! # auto-generated
    fooId: String!
    v: String
}

This feature would be optional and could help subgraphs using the current spec to be grandfathered in.

Pros

  • Simpler Composite Schema Spec: Easier to understand and implement.
  • Simpler Gateway-Subgraph Contract: Reduces complexity in interactions. Easier troubleshooting.
  • Simpler Gateway Implementations: Less complex, potentially fewer bugs.
  • Batch Lookup: Simplifies batch lookup, eliminates need for GraphQL spec changes
  • Simple cases are easy. Complex cases are possible: Only subgraphs that desired complex keys need to take on any additional complexity

Cons

  • Server-Side Tooling: In order to have the same level of key flexibility and convenience, additional tooling is required.
  • Compatibility with Fed2: Subgraphs using complex keys with current Fed2 spec would need adaptation (though this could be done automatically through tooling).
@martijnwalraven
Copy link

martijnwalraven commented Feb 10, 2025

@fotoetienne The main reason we landed on the current @key design with Apollo Federation, is that we found distributed systems often had multiple ways of identifying what conceptually was the same entity. As a result, it isn't always realistic to expect systems to agree on a single type of key for an entity type: subgraphs often differ in the keys they're able to support, and the ability to mix and match keys avoids the need to compromise schema design in these scenarios. This is also important for evolvability.

That wouldn't really be addressed by this proposal: it would simplify complex keys, but not allow for multiple keys and differences between subgraphs in what keys they support.

Also, I don't believe it would eliminate the need for variable batching, because we still need that to support @require. Here's an attempt at clarifying this I wrote earlier (it uses upc as a key, but you could just as well read this as id):


There's yet another reason we originally went with Query._entities: the representations: argument is specified as a list and therefore offers a generic mechanism for batching. This is needed because a separate subgraph request per object is inefficient and would make Federation fairly unusable.

A first thought might be to ask users to define batched lookup fields. Instead of Query.product(upc:), we can imagine a Query.products(upcs:) field that allows you to pass in a list of upcs and returns a list of products. This already gets more complicated when we consider the need to support compound keys, but a more important reason not to go down this path is that it breaks @require.

If you think about what a subquery would look like, it becomes clear this won't allow us to pass in entity-specific additional data. In the example below, each product's shippingCost would receive the same size and weight:

query ($upcs: [String!]!, $size: Float, $weight: Float) {
  products(upcs: $upcs) {
    name
    description
    shippingCost(size: $size, weight: $weight)
  }
}

Defining $size and $weight as lists wouldn't help either, because that means shippingCost would then receive the same lists of sizes and weights for each product. So what we need is a way to associate each execution of shippingCost with the corresponding size and weight.

@fotoetienne
Copy link

distributed systems often had multiple ways of identifying what conceptually was the same entity

Should we separate the concept of multiple keys from that of composite keys? We could support multiple keys, while still requiring that a key is a single scalar field.

@fotoetienne
Copy link

I don't believe it would eliminate the need for variable batching, because we still need that to support @require

That's true. But @require isn't required (pun intended) for many use cases. @require could be an optional composite v1.1 feature that comes later once variable batching is widely available.

@martijnwalraven
Copy link

Should we separate the concept of multiple keys from that of composite keys? We could support multiple keys, while still requiring that a key is a single scalar field.

Yeah, we should at least clearly explain the difference.

That's true. But @require isn't required (pun intended) for many use cases. @require could be an optional composite v1.1 feature that comes later once variable batching is widely available.

I think the underlying issue here is supporting current usage and retaining compatibility with Apollo Federation. Users do rely on composite keys and @requires, so if we decide to leave those features out of an initial version of this spec, we'd be blocking them from upgrading.

We could decide to mark those features as optional in the spec, to allow for simplified implementations, but my worry is that this would fragment the ecosystem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants