Skip to content

Instantly share code, notes, and snippets.

@PascalSenn
Last active February 20, 2025 17:19
Show Gist options
  • Save PascalSenn/f469a42258fb7fcd41e82e983d6d602c to your computer and use it in GitHub Desktop.
Save PascalSenn/f469a42258fb7fcd41e82e983d6d602c to your computer and use it in GitHub Desktop.

As we develop the composite schema specification, we must decide what belongs in the core specification and what should be defined as an extension. Whatever we include in the core specification becomes mandatory for all compliant implementations. If the core spec becomes too large or complicated, it can hinder adoption-especially for teams that do not need advanced governance or collaboration features.

Minimal Executable Composite Schema

Consider the smallest example of a composite schema that can be resolved by an executor. Suppose we have two sub-schemas:

type Product {
  id: ID!
  sku: String!
  name: String!
  price: Int!
}

type Query {
  product(id: ID!): Product
}
type Query {
  product(sku: String!): Product @internal @lookup
}

type Product {
  productDetails: ProductDetails
}

type ProductDetails {
  weight: Int
}

Here, the @internal and @lookup directives provide enough information to the executor to resolve this query:

query {
  product(id: "1") {
    sku
    name
    price
    productDetails {
      weight
    }
  }
}

Introducing a @key for Double Bookkeeping

If we want explicit “double bookkeeping”-that is, every subgraph must declare how an entity is referenced-then each lookup must match a defined @key. For example:

type Product {
  id: ID!
  sku: String! @shareable
  name: String!
  price: Int!
}

type Query {
  product(id: ID!): Product
}
type Query {
  product(sku: String!): Product @internal @lookup
}

type Product @key(fields: "sku") {
  sku: String!
  productDetails: ProductDetails
}

In this scenario, we:

  • Define @key(fields: "sku") in Schema B.
  • Add sku to Schema B so that the @key directive references a valid field.
  • Mark sku as @shareable in Schema A so that other subgraphs can reuse it.

Using @key in Both Subgraphs

We can take this one step further by requiring the @key directive on every subgraph that references the entity. For instance:

type Product @key(fields: "sku") {
  id: ID!
  sku: String!
  name: String!
  price: Int!
}

type Query {
  product(id: ID!): Product
}
type Query {
  product(sku: String!): Product @internal @lookup
}

type Product @key(fields: "sku") {
  sku: String!
  productDetails: ProductDetails
}

This is more explicit but also more complex. For instance:

  • A developer might forget to define @key in Schema A, rendering the overall schema invalid.
  • A different schema (say, Schema C) might omit @key, resulting in a performance issue because the executor cannot directly use the existing primary key.

Adding Shareable Fields with Double Bookkeeping

If we also want to enforce that each subgraph must explicitly allow other subgraphs to share certain fields, we might introduce a more complex @shareable directive:

type Product @key(fields: "sku") {
  id: ID!
  sku: String! @shareable
  name: String! @shareable(from: "SchemaB")
  price: Int!
}

type Query {
  product(id: ID!): Product
}
type Query {
  product(sku: String!): Product @internal @lookup
}

type Product @key(fields: "sku") {
  sku: String!
  name: String! @shareable(from: "SchemaA")
  productDetails: ProductDetails
}

This approach adds still more complexity, but can be useful in large organizations where teams must formally agree (and declare in the schema) which fields they will share.


Deciding What Belongs in the Core Specification

We must determine which features belong in the core composite schema specification-meaning they are mandatory for all implementations-and which features should be optional extensions. Key questions include:

  1. Complexity vs. Adoption

    • Requiring @key or have a more explicit @shareable can be critical for some use cases but may be unnecessary overhead for smaller teams that can coordinate changes informally.
  2. Role of @lookup

    • Does @lookup alone define an entity, making @key redundant? Or is there a scenario where both directives are needed?
  3. Extensions for Entity Definitions and Governance

    • Features like explicit double-bookkeeping with @key or controlled sharing with @shareable(from: "SchemaX") may be best defined in separate extension specifications (e.g., “Composite Schema – Entity Specification” or “Composite Schema – Governance Rules”).
  4. Scalability

    • Large organizations with multiple autonomous teams may need strict governance rules. Smaller teams with daily communication may not.
@martijnwalraven
Copy link

martijnwalraven commented Feb 20, 2025

While I agree more complex governance features don't belong in the core spec, I feel keys are an integral part of federation. No matter how small the team, I wouldn't want to rely on @lookup alone to define an entity. This is partly a matter of discoverability (no need to search for lookup fields to know whether a type is an entity and which fields are part of keys) and 'double bookkeeping' (which is not just about governance, but a way to catch mistakes even a single developer could make), but @key also indicates additional semantics (key fields are always @shareable and should be stable).

You mention these scenarios as added complexity when requiring @key in all subgraphs:

  • A developer might forget to define @key in Schema A, rendering the overall schema invalid.
  • A different schema (say, Schema C) might omit @key, resulting in a performance issue because the executor cannot directly use the existing primary key.

To me, these sound like potential mistakes we want to catch early. And that's a feature! Agreeing on which types are entities, and which keys can be used to identify them, is core to designing a federated schema. I think it would be dangerous to rely on inference and informal coordination for that. If we require @lookup to return an entity with a key that matches the lookup mapping, both issues should be caught by schema validation.

Interfaces are an additional consideration. With the semantics we discussed, any @keys on an interface would act as a constraint on all implementing types, forcing them to support those same keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment