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

How should work @external exactly #127

Open
PascalSenn opened this issue Jan 9, 2025 · 3 comments
Open

How should work @external exactly #127

PascalSenn opened this issue Jan 9, 2025 · 3 comments

Comments

@PascalSenn
Copy link
Contributor

We need to answer the following questions:

  • What's the current behaviour of @external in other federated systems (e.g. apollo federation v1 & v2)
  • What are it's usecase in the composite schema spec
  • Examples for all possible usecases
@smyrick
Copy link

smyrick commented Jan 9, 2025

In Fed 2, the external directive is used to refer to fields where a subgraph needs to reference a type or field but the subgraph itself does not have a resolver for it (or the gateway should assume it doesn't) OR it may only have a resolver for it in a conditional path

Example with requires,

type Location @key(fields: "id") {
    id: ID!
    overallRating: Float @requires(fields: "foo { a }")
    foo: Foo @external
}

type Foo {
    a: String @external
    b: String @external
}

Example with provides, always external unless going through @provides path

type Product @key(fields: "id") {
  id: ID!
  name: String! @external
  inStock: Boolean!
}

type Query {
  outOfStockProducts: [Product!]! @provides(fields: "name")
  discontinuedProducts: [Product!]!
}

Example of key, allowed but not need in Fed 2

type Product @key(fields: "id") @key(fields: "upc") {
    id: ID! @external
    upc: String! @external
    name: String
}

@Aenimus
Copy link

Aenimus commented Jan 9, 2025

I think part of the problem is that @external does many different things all at once:

  1. Legacy syntax (always resolvable by that subgraph in V1 and V2)
  • An @external field that is referenced by a @key(fields: ...) FieldSet on an extension definition.
  1. Declares a field unresolvable by that subgraph
  • An @external field that is referenced only in a @requires(fields: ...) FieldSet.
  • An @external field that exists only to satisfy an interface.
  • An @external field that is NOT referenced by a @key(fields: ...) FieldSet on an extension definition.
  1. Declares a field conditionally resolvable by that subgraph
  • An @external field that is referenced in a @provides(fields: ...) FieldSet. Such fields are conditionally resolved by that subgraph at certain paths. This could be in conjunction with @requires.

In a V2 subgraph, @external fields are more strictly validated. An @external field must be referenced in @key, @provides, and/or @requires FieldSet, or defined to satisfy an interface. Moreover, an @external field must have a non-external counterpart, i.e., the same field defined in another subgraph without @external.

In a V1 subgraph, at least when composed with a system that supports V2, basically any field can be defined @external. If it's the only instance of the field, i.e., there is no non-external counterpart, it's simply removed silently from the subgraph. There is validation to prevent all fields on a type being defined @external, however.

I can provide examples to illustrate these points if wanted/required.

@Aenimus
Copy link

Aenimus commented Jan 9, 2025

@smyrick I believe what you said is only true of entity extensions:

Example of key, allowed but not need in Fed 2

type Product @key(fields: "id") @key(fields: "upc") {
    id: ID! @external
    upc: String! @external
    name: String
}

While it's allowed, choosing to definekey fields as @external on any type definition (not extension) is meaningful. In this case, Product.id and Product.upc are both truly unresolvable. Were the entity an extension, @external on the key fields wouldn't really mean or do anything (legacy syntax). Consequently, composing the following graphs produces satisfiability errors:

# subgraph A
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@external", "@key"])

type Query {
  products: [Product!]!
}

type Product @key(fields: "id") @key(fields: "upc") {
  id: ID! @external
  upc: String! @external
  name: String!
}
# subgraph B
extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key"])

type Product @key(fields: "id") @key(fields: "upc") {
  id: ID!
  upc: String!
  stock: Int!
}

Errors:

Error: A valid schema couldn't be composed. The following composition errors were found:
    The following supergraph API query:
{
  products {
    id
  }
}
cannot be satisfied by the subgraphs because:
- from subgraph "A":
  - field "Product.id" is not resolvable because marked @external.
  - cannot move to subgraph "B" using @key(fields: "id") of "Product", the key field(s) cannot be resolved from subgraph "A".
  - cannot move to subgraph "B" using @key(fields: "upc") of "Product", the key field(s) cannot be resolved from subgraph "A".
    The following supergraph API query:
{
  products {
    upc
  }
}
cannot be satisfied by the subgraphs because:
- from subgraph "A":
  - field "Product.upc" is not resolvable because marked @external.
  - cannot move to subgraph "B" using @key(fields: "id") of "Product", the key field(s) cannot be resolved from subgraph "A".
  - cannot move to subgraph "B" using @key(fields: "upc") of "Product", the key field(s) cannot be resolved from subgraph "A".
    The following supergraph API query:
{
  products {
    stock
  }
}
cannot be satisfied by the subgraphs because:
- from subgraph "A":
  - cannot find field "Product.stock".
  - cannot move to subgraph "B" using @key(fields: "id") of "Product", the key field(s) cannot be resolved from subgraph "A".
  - cannot move to subgraph "B" using @key(fields: "upc") of "Product", the key field(s) cannot be resolved from subgraph "A".

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