Define Advanced Keys
Define and use compound keys, nested key fields, and more
Depending on your entities' fields and usage, you may need to use more advanced @key
s.
For example, you may need to define a compound @key
if multiple fields are required to uniquely identify an entity.
If different subgraphs interact with different fields of an entity, you may need to define multiple—and sometimes differing—@key
s for the entity.
Compound @key
s
A single @key
can consist of multiple fields, the combination of which uniquely identifies an entity. This is called a compound or composite key. In the following example, the combination of both username
and domain
fields is required to uniquely identify the User
entity:
1type User @key(fields: "username domain") {
2 username: String!
3 domain: String!
4}
Nested fields in compound @key
s
Nested fields are often used in compound keys.
In the following example, the User
entity's @key
consists of both a user's id
and the id
of that user's associated Organization
:
1type User @key(fields: "id organization { id }") {
2 id: ID!
3 organization: Organization!
4}
5
6type Organization {
7 id: ID!
8}
@key
field.Multiple @key
s
When different subgraphs interact with different fields of an entity, you may need to define multiple @key
s for the entity. For example, a Reviews subgraph might refer to products by their ID, whereas an Inventory subgraph might use SKUs.
In the following example, the Product
entity can be uniquely identified by either its id
or its sku
:
1type Product @key(fields: "id") @key(fields: "sku") {
2 id: ID!
3 sku: String!
4 name: String!
5 price: Int
6}
@key
fields, the query planner uses the most efficient set for entity resolution. For example, suppose you allow a type to be identified by @key(fields: "id")
or @key(fields: "id sku")
:1type Product @key(fields: "id") @key(fields: "id sku") {
2 # ...
3}
id
or (id
and sku
) is enough to uniquely identify the entity. Since id
alone is enough, the query planner will use only that field to resolve the entity, and @key(fields: "id sku")
is effectively ignored.Referencing entities with multiple keys
A subgraph that references an entity without contributing any fields can use any @key
fields in its stub definition. For example, if the Products subgraph defines the Product
entity like this:
1type Product @key(fields: "id") @key(fields: "sku") {
2 id: ID!
3 sku: String!
4 name: String!
5 price: Int
6}
Then, a Reviews subgraph can use either id
or sku
in the stub definition:
1# Either:
2type Product @key(fields: "id", resolvable: false) {
3 id: ID!
4}
5
6# Or:
7type Product @key(fields: "sku", resolvable: false) {
8 sku: String!
9}
When resolving a reference for an entity with multiple keys, you can determine how to resolve it based on which key is present. For example, if you're using @apollo/subgraph
, it could look like this:
1// Products subgraph
2const resolvers = {
3 Product: {
4 __resolveReference(productRepresentation) {
5 if(productRepresentation.sku){
6 return fetchProductBySku(productRepresentation.sku);
7 } else {
8 return fetchProductByID(productRepresentation.id);
9 }
10 }
11 },
12 // ...other resolvers...
13}
Differing @key
s across subgraphs
Although an entity commonly uses the exact same @key
field(s) across subgraphs, you can alternatively use different @key
s with different fields. For example, you can define a Product
entity shared between subgraphs, one with sku
and upc
as its @key
s, and the other with only upc
as the @key
field:
1type Product @key(fields: "sku") @key(fields: "upc") {
2 sku: ID!
3 upc: String!
4 name: String!
5 price: Int
6}
1type Product @key(fields: "upc") {
2 upc: String!
3 inStock: Boolean!
4}
To merge entities between subgraphs, the entity must have at least one shared field between subgraphs. For example, operations can't merge the Product
entity defined in the following subgraphs because they don't share any fields specified in the @key
selection set:
❌
1type Product @key(fields: "sku") {
2 sku: ID!
3 name: String!
4 price: Int
5}
1type Product @key(fields: "upc") {
2 upc: String!
3 inStock: Boolean!
4}
Operations with differing @key
s
Differing keys across subgraphs affect which of the entity's fields can be resolved from each subgraph. Requests can resolve fields if there is a traversable path from the root query to the fields.
Take these subgraph schemas as an example:
1type Product @key(fields: "sku") {
2 sku: ID!
3 upc: String!
4 name: String!
5 price: Int
6}
7
8type Query {
9 product(sku: ID!): Product
10 products: [Product!]!
11}
12
1type Product @key(fields: "upc") {
2 upc: String!
3 inStock: Boolean!
4}
The queries defined in the Products subgraph can always resolve all product fields because the product entity can be joined via the upc
field present in both schemas.
On the other hand, queries added to the Inventory subgraph can't resolve fields from the Products subgraph:
1type Product @key(fields: "sku") {
2 sku: ID!
3 upc: String!
4 name: String!
5 price: Int
6}
1type Product @key(fields: "upc") {
2 upc: String!
3 inStock: Boolean!
4}
5
6type Query {
7 productsInStock: [Product!]!
8}
The productsInStock
query can't resolve fields from the Products subgraph since the Products subgraph's Product
type definition doesn't include upc
as a key field, and sku
isn't present in the Inventory subgraph.
If the Products subgraph includes @key(fields: "upc")
, all queries from the Inventory subgraph can resolve all product fields:
1type Product @key(fields: "sku") @key(fields: "upc") {
2 sku: ID!
3 upc: String!
4 name: String!
5 price: Int
6}
1type Product @key(fields: "upc") {
2 upc: String!
3 inStock: Boolean!
4}
5
6type Query {
7 productsInStock: [Product!]!
8}
Next steps
If you haven't already, learn how to contribute entity fields to the supergraph and reference them from subgraphs that don't contribute any fields.