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.
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
}
}
}
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.
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.
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.
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:
-
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.
- Requiring
-
Role of
@lookup
- Does
@lookup
alone define an entity, making@key
redundant? Or is there a scenario where both directives are needed?
- Does
-
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”).
- Features like explicit double-bookkeeping with
-
Scalability
- Large organizations with multiple autonomous teams may need strict governance rules. Smaller teams with daily communication may not.
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: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
@key
s on an interface would act as a constraint on all implementing types, forcing them to support those same keys.